From aa1a288dd79f3c1c7c1a1cb037fcf4c790c1bb8c Mon Sep 17 00:00:00 2001 From: Horilla Date: Thu, 18 Sep 2025 10:58:57 +0530 Subject: [PATCH] [FIX] LEAVE: #779 --- leave/methods.py | 41 +++++++++++++++++++++- leave/views.py | 88 +++++++++++++++++++++++++++++------------------- 2 files changed, 94 insertions(+), 35 deletions(-) diff --git a/leave/methods.py b/leave/methods.py index 0c9c2d258..d1de13c0b 100644 --- a/leave/methods.py +++ b/leave/methods.py @@ -1,6 +1,7 @@ import calendar -from datetime import datetime, timedelta +from datetime import date, datetime, timedelta +import pandas as pd from django.apps import apps from django.db.models import Q @@ -158,3 +159,41 @@ def filter_conditional_leave_request(request): else: leave_request_ids.append(instance.leave_request_id.id) return LeaveRequest.objects.filter(pk__in=leave_request_ids) + + +def parse_excel_date(value): + """ + Convert Excel date values into a valid Python date object. + Supports multiple formats: YYYY-MM-DD, DD-MM-YYYY, DD Month YYYY, MM/DD/YYYY, etc. + """ + if not value or pd.isna(value): + return None + + if isinstance(value, date): + # Already a date (from Excel or pandas) + return value + + if isinstance(value, datetime): + # Datetime object → convert to date + return value.date() + + if isinstance(value, str): + value = value.strip() + # Try multiple formats + date_formats = [ + "%Y-%m-%d", # 2025-07-22 + "%d-%m-%Y", # 22-07-2025 + "%d/%m/%Y", # 22/07/2025 + "%m/%d/%Y", # 07/22/2025 + "%d %B %Y", # 22 July 2025 + "%d %B, %Y", # 22 July, 2025 + "%d-%b-%Y", # 22-Jul-2025 + ] + for fmt in date_formats: + try: + return datetime.strptime(value, fmt).date() + except ValueError: + continue + + # If nothing matches, return None (caller should handle error) + return None diff --git a/leave/views.py b/leave/views.py index 6f2c49597..c7ebe3329 100644 --- a/leave/views.py +++ b/leave/views.py @@ -60,6 +60,7 @@ from leave.methods import ( company_leave_dates_list, filter_conditional_leave_request, holiday_dates_list, + parse_excel_date, ) from leave.models import * from leave.models import leave_requested_dates @@ -1776,6 +1777,10 @@ def assign_leave_type_excel(_request): columns = [ "Employee Badge ID", "Leave Type", + "Available Days", # 779 + "Carryforward Days", + "Total Leave Days", + "Assigned Date", ] data_frame = pd.DataFrame(columns=columns) response = HttpResponse(content_type="application/ms-excel") @@ -1801,9 +1806,9 @@ def assign_leave_type_import(request): "Leave Type": [], "Badge ID Error": [], "Leave Type Error": [], - "Assigned Error": [], "Available Days": [], "Carry Forward Days": [], + "Assigned Date Error": [], "Other Errors": [], } @@ -1817,62 +1822,77 @@ def assign_leave_type_import(request): emp.badge_id.lower(): emp for emp in Employee.objects.all() if emp.badge_id } leave_types = {lt.name.lower(): lt for lt in LeaveType.objects.all()} - available_leaves = { + existing = { (al.leave_type_id.id, al.employee_id.id): al for al in AvailableLeave.objects.all() } - assign_leave_list = [] - error_list = [] + assign_leave_list, error_list = [], [] - for assign_leave in assign_leave_dicts: - badge_id = assign_leave.get("Employee Badge ID", "").strip().lower() - assign_leave_type = assign_leave.get("Leave Type", "").strip().lower() - available_days = assign_leave.get("Available Days", "0") - cfd = assign_leave.get("Carry Forward Days", "0") + for row in assign_leave_dicts: + badge_id = str(row.get("Employee Badge ID", "")).strip().lower() + leave_type_name = str(row.get("Leave Type", "")).strip().lower() employee = employees.get(badge_id) - leave_type = leave_types.get(assign_leave_type) + leave_type = leave_types.get(leave_type_name) - errors = [] - if employee is None: - errors.append(_("This badge id does not exist.")) - if leave_type is None: - errors.append(_("This leave type does not exist.")) - if errors: - assign_leave[ - "Badge ID Error" if "badge id" in errors[0] else "Leave Type Error" - ] = " ".join(force_str(error) for error in errors) - error_list.append(assign_leave) + if not employee: + row["Badge ID Error"] = _("This badge id does not exist.") + error_list.append(row) + continue + if not leave_type: + row["Leave Type Error"] = _("This leave type does not exist.") + error_list.append(row) continue - # Check if leave type has already been assigned to the employee - if (leave_type.id, employee.id) in available_leaves: - assign_leave["Assigned Error"] = _( + if (leave_type.id, employee.id) in existing: + row["Assigned Error"] = _( "Leave type has already been assigned to the employee." ) - error_list.append(assign_leave) + error_list.append(row) continue - # If no errors, create the AvailableLeave instance - if available_days == 0: + # Extract optional fields # 779 + available_days = row.get("Available Days") + carryforward_days = row.get("Carryforward Days") + total_leave_days = row.get("Total Leave Days") + assigned_date_raw = row.get("Assigned Date") + + # Apply defaults when missing + if pd.isna(available_days) or available_days == "": available_days = leave_type.total_days + if pd.isna(carryforward_days) or carryforward_days == "": + carryforward_days = 0 + if pd.isna(total_leave_days) or total_leave_days == "": + total_leave_days = available_days + carryforward_days + + assigned_date = parse_excel_date(assigned_date_raw) or ( + timezone.now().date() + if isinstance(assigned_date_raw, float) + and math.isnan(assigned_date_raw) + else None + ) + if not assigned_date: + row["Other Errors"] = _( + "Invalid date format. Please use YYYY-MM-DD or a supported format." + ) + error_list.append(row) + continue available_leave = AvailableLeave( leave_type_id=leave_type, employee_id=employee, - available_days=available_days, + available_days=float(available_days), + carryforward_days=float(carryforward_days), + total_leave_days=float(total_leave_days), + assigned_date=assigned_date, ) - if cfd: - available_leave.carryforward_days = cfd + if carryforward_days: available_leave.expired_date = leave_type.carryforward_expire_date try: available_leave.reset_date = leave_type.leave_type_next_reset_date() - except: + except Exception: pass - available_leave.assigned_date = datetime.today() - available_leave.total_leave_days = ( - available_leave.carryforward_days + available_leave.available_days - ) + assign_leave_list.append(available_leave) # Bulk create available leaves