From dd3061d8a1c5acee31f000b505067675f6d8fc7d Mon Sep 17 00:00:00 2001 From: Horilla Date: Mon, 20 Oct 2025 11:31:16 +0530 Subject: [PATCH] [UPDT] BASE : Updated the hierarchy of employee,manger and admin for requests --- attendance/cbv/attendance_request.py | 8 +- base/methods.py | 214 ++++++++++++++++++++------ base/views.py | 3 +- helpdesk/forms.py | 12 +- leave/cbv/leave_allocation_request.py | 8 +- 5 files changed, 189 insertions(+), 56 deletions(-) diff --git a/attendance/cbv/attendance_request.py b/attendance/cbv/attendance_request.py index 094975686..25ae19f32 100644 --- a/attendance/cbv/attendance_request.py +++ b/attendance/cbv/attendance_request.py @@ -370,9 +370,11 @@ class NewAttendanceRequestFormView(HorillaFormView): self.form = choosesubordinates( self.request, self.form, "attendance.change_attendance" ) - self.form.fields["employee_id"].queryset = self.form.fields[ - "employee_id" - ].queryset | Employee.objects.filter(employee_user_id=self.request.user) + self.form.fields["employee_id"].queryset = ( + self.form.fields["employee_id"].queryset + ).distinct() | ( + Employee.objects.filter(employee_user_id=self.request.user) + ).distinct() self.form.fields["employee_id"].initial = self.request.user.employee_get.id if self.request.GET.get("emp_id"): emp_id = self.request.GET.get("emp_id") diff --git a/base/methods.py b/base/methods.py index 19cf3658d..b68f22695 100644 --- a/base/methods.py +++ b/base/methods.py @@ -138,54 +138,96 @@ def users_count(self): Group.add_to_class("users_count", property(users_count)) -def filtersubordinates(request, queryset, perm=None, field="employee_id"): +# def filtersubordinates(request, queryset, perm=None, field="employee_id"): +# """ +# This method is used to filter out subordinates queryset element. +# """ +# user = request.user +# if user.has_perm(perm): +# return queryset + +# if not request: +# return queryset +# if NESTED_SUBORDINATE_VISIBILITY: +# current_managers = [ +# request.user.employee_get.id, +# ] +# all_subordinates = Q( +# **{ +# f"{field}__employee_work_info__reporting_manager_id__in": current_managers +# } +# ) + +# while True: +# sub_managers = queryset.filter( +# **{ +# f"{field}__employee_work_info__reporting_manager_id__in": current_managers +# } +# ).values_list(f"{field}__id", flat=True) +# if not sub_managers.exists(): +# break +# current_managers = sub_managers +# all_subordinates |= Q( +# **{ +# f"{field}__employee_work_info__reporting_manager_id__in": sub_managers +# } +# ) + +# return queryset.filter(all_subordinates) + +# manager = Employee.objects.filter(employee_user_id=user).first() + +# if field: +# filter_expression = f"{field}__employee_work_info__reporting_manager_id" +# queryset = queryset.filter(**{filter_expression: manager}) +# return queryset + +# queryset = queryset.filter( +# employee_id__employee_work_info__reporting_manager_id=manager +# ) +# return queryset + + +def filtersubordinates( + request, + queryset, + perm=None, + field="employee_id", + nested=NESTED_SUBORDINATE_VISIBILITY, +): """ - This method is used to filter out subordinates queryset element. + Filters a queryset to include only the current user's subordinates. + Respects the user's permission: if the user has `perm`, returns full queryset. + + Args: + request: HttpRequest + queryset: Django queryset to filter + perm: permission codename string + field: ForeignKey field pointing to Employee (default "employee_id") + nested: if True, include all nested subordinates; else only direct subordinates + + Returns: + Filtered queryset """ user = request.user - if user.has_perm(perm): - return queryset - if not request: - return queryset - if NESTED_SUBORDINATE_VISIBILITY: - current_managers = [ - request.user.employee_get.id, - ] - all_subordinates = Q( - **{ - f"{field}__employee_work_info__reporting_manager_id__in": current_managers - } - ) + if perm and user.has_perm(perm): + return queryset # User has permission to view all - while True: - sub_managers = queryset.filter( - **{ - f"{field}__employee_work_info__reporting_manager_id__in": current_managers - } - ).values_list(f"{field}__id", flat=True) - if not sub_managers.exists(): - break - current_managers = sub_managers - all_subordinates |= Q( - **{ - f"{field}__employee_work_info__reporting_manager_id__in": sub_managers - } - ) + if not hasattr(user, "employee_get") or user.employee_get is None: + return queryset.none() # No employee associated, return empty - return queryset.filter(all_subordinates) + # Get subordinate employee IDs + sub_ids = get_subordinate_employee_ids(request, nested=nested) - manager = Employee.objects.filter(employee_user_id=user).first() + # Include own records explicitly + own_id = user.employee_get.id - if field: - filter_expression = f"{field}__employee_work_info__reporting_manager_id" - queryset = queryset.filter(**{filter_expression: manager}) - return queryset + # Build filter + filter_ids = sub_ids + [own_id] if sub_ids else [own_id] - queryset = queryset.filter( - employee_id__employee_work_info__reporting_manager_id=manager - ) - return queryset + # Return filtered queryset + return queryset.filter(**{f"{field}__id__in": filter_ids}) def filter_own_records(request, queryset, perm=None): @@ -265,20 +307,100 @@ def is_reportingmanager(request): return False -def choosesubordinates( - request, - form, - perm, -): +# def choosesubordinates( +# request, +# form, +# perm, +# ): +# user = request.user +# if user.has_perm(perm): +# return form +# manager = Employee.objects.filter(employee_user_id=user).first() +# queryset = Employee.objects.filter(employee_work_info__reporting_manager_id=manager) +# form.fields["employee_id"].queryset = queryset +# return form + + +def choosesubordinates(request, form, perm): + """ + Dynamically set subordinate choices for employee field based on permissions + and nested subordinate visibility. + """ user = request.user if user.has_perm(perm): return form manager = Employee.objects.filter(employee_user_id=user).first() - queryset = Employee.objects.filter(employee_work_info__reporting_manager_id=manager) - form.fields["employee_id"].queryset = queryset + if not manager: + return form + + # Start with direct subordinates + current_managers = [manager.id] + all_subordinates = Q(employee_work_info__reporting_manager_id__in=current_managers) + + if NESTED_SUBORDINATE_VISIBILITY: + # Recursively find all subordinates in the chain + while True: + sub_managers = Employee.objects.filter( + employee_work_info__reporting_manager_id__in=current_managers + ).values_list("id", flat=True) + + if not sub_managers.exists(): + break + + current_managers = sub_managers + all_subordinates |= Q( + employee_work_info__reporting_manager_id__in=sub_managers + ) + + queryset = Employee.objects.filter(all_subordinates).distinct() + + # Assign to form field + if "employee_id" in form.fields: + form.fields["employee_id"].queryset = queryset + return form +def get_subordinate_employee_ids(request, nested=NESTED_SUBORDINATE_VISIBILITY): + """ + Returns a list of subordinate Employee IDs under the current user. + + If nested=True, includes all subordinates recursively across the reporting hierarchy. + If nested=False, includes only direct subordinates. + """ + user = request.user + if not hasattr(user, "employee_get"): + return [] + + manager_id = user.employee_get.id + + if nested: + # Recursive approach for all levels + current_managers = [manager_id] + all_sub_ids = set() + + while current_managers: + sub_ids = list( + Employee.objects.filter( + employee_work_info__reporting_manager_id__in=current_managers + ).values_list("id", flat=True) + ) + if not sub_ids: + break + all_sub_ids.update(sub_ids) + current_managers = sub_ids + + return list(all_sub_ids) + else: + # Only direct subordinates + direct_sub_ids = list( + Employee.objects.filter( + employee_work_info__reporting_manager_id=manager_id + ).values_list("id", flat=True) + ) + return direct_sub_ids + + def choosesubordinatesemployeemodel(request, form, perm): user = request.user if user.has_perm(perm): diff --git a/base/views.py b/base/views.py index ca523dbfa..b9c5fb95c 100644 --- a/base/views.py +++ b/base/views.py @@ -667,7 +667,8 @@ def include_employee_instance(request, form): employee = Employee.objects.filter(employee_user_id=request.user) if employee.first() is not None: if queryset.filter(id=employee.first().id).first() is None: - queryset = queryset | employee + # queryset = queryset | employee + queryset = queryset.distinct() | employee.distinct() form.fields["employee_id"].queryset = queryset return form diff --git a/helpdesk/forms.py b/helpdesk/forms.py index 4d5949ebd..4832df38d 100644 --- a/helpdesk/forms.py +++ b/helpdesk/forms.py @@ -144,9 +144,15 @@ class TicketForm(ModelForm): else: employee = request.user.employee_get # initialising employee queryset according to the user - self.fields["employee_id"].queryset = filtersubordinatesemployeemodel( - request, Employee.objects.filter(is_active=True), perm="helpdesk.add_ticket" - ) | Employee.objects.filter(employee_user_id=request.user) + self.fields["employee_id"].queryset = ( + filtersubordinatesemployeemodel( + request, + Employee.objects.filter(is_active=True), + perm="helpdesk.add_ticket", + ) + ).distinct() | ( + Employee.objects.filter(employee_user_id=request.user) + ).distinct() self.fields["employee_id"].initial = employee # appending dynamic create option according to user if is_reportingmanager(request) or request.user.has_perm( diff --git a/leave/cbv/leave_allocation_request.py b/leave/cbv/leave_allocation_request.py index 13b4bce18..e59faae15 100644 --- a/leave/cbv/leave_allocation_request.py +++ b/leave/cbv/leave_allocation_request.py @@ -307,9 +307,11 @@ class LeaveAllocationRequestFormView(HorillaFormView): self.form = choosesubordinates( self.request, self.form, "leave.add_leaveallocationrequest" ) - self.form.fields["employee_id"].queryset = self.form.fields[ - "employee_id" - ].queryset | Employee.objects.filter(employee_user_id=self.request.user) + self.form.fields["employee_id"].queryset = ( + self.form.fields["employee_id"].queryset + ).distinct() | ( + Employee.objects.filter(employee_user_id=self.request.user) + ).distinct() context["form"] = self.form return context