From 1ce4e3f21df82fbe1766e5afd0a32c442628b1ea Mon Sep 17 00:00:00 2001 From: A R R R Associates Date: Fri, 7 Aug 2026 16:56:32 +0530 Subject: [PATCH] Show trade name in Staff My Work --- app/modules/employees/service.py | 276 ++++++++++++++++++++++++++++++- 1 file changed, 270 insertions(+), 6 deletions(-) diff --git a/app/modules/employees/service.py b/app/modules/employees/service.py index 820764c..05c70e9 100644 --- a/app/modules/employees/service.py +++ b/app/modules/employees/service.py @@ -21,9 +21,10 @@ from app.modules.core.rbac.models import Role, UserRole from app.modules.core.tenancy.models import Branch, Tenant from app.modules.core.tenancy.settings_models import BranchSettings from app.modules.employees.models import Employee, EmployeeAttendance, EmployeeRegistrationRequest, EmployeeLeaveType, EmployeeLeaveBalance, EmployeeLeaveRequest, EmployeeDocumentType, EmployeeDocument, EmployeeOnboardingChecklistItem, EmployeeOnboardingTask, EmployeeOffboardingRequest, EmployeeOffboardingTask, EmployeeSalaryStructure, EmployeePayrollRun, EmployeePayslip -from app.modules.clients.models import Client +from app.modules.clients.models import Client, ClientBusinessUnit, ClientBranch from app.modules.documents.models import EngagementDocument from app.modules.services.models import ClientServiceTaskInstance, ClientServiceSubscription, ServiceCatalogue, ServiceTaskComment +from app.modules.registrations.models import ClientRegistration, RegistrationType from app.modules.services.execution import ( CLOSED_TASK_STATUSES, TASK_PRIORITIES, @@ -2868,6 +2869,240 @@ def resume_employee_engagement_workflow(db: Session, scope: EmployeeScope, engag db.commit(); db.refresh(subscription); return subscription + +def _employee_work_scope_display_map( + db: Session, + *, + tenant_id: int, + subscriptions: list[ClientServiceSubscription], +) -> dict[int, dict[str, str]]: + """Resolve the display identity for Employee My Work without changing ownership. + + Display priority: + - registration scope: registration trade name -> business-unit trade/name -> + branch name -> registration legal name -> client trade name -> client name + - business-unit scope: business-unit trade/name -> client trade name -> client name + - client-branch scope: branch name -> business-unit trade/name -> client trade/name + - client scope: client trade name -> client name + + The legal client remains available as context for scoped engagements. + """ + subscriptions = [row for row in subscriptions if row is not None] + if not subscriptions: + return {} + + business_ids = { + int(row.business_unit_id) + for row in subscriptions + if getattr(row, "business_unit_id", None) + } + branch_ids = { + int(row.client_branch_id) + for row in subscriptions + if getattr(row, "client_branch_id", None) + } + registration_ids = { + int(row.registration_id) + for row in subscriptions + if getattr(row, "registration_id", None) + } + + businesses: dict[int, ClientBusinessUnit] = {} + if business_ids: + businesses = { + row.id: row + for row in db.execute( + select(ClientBusinessUnit).where( + ClientBusinessUnit.tenant_id == tenant_id, + ClientBusinessUnit.id.in_(business_ids), + ) + ).scalars().all() + } + + branches: dict[int, ClientBranch] = {} + if branch_ids: + branches = { + row.id: row + for row in db.execute( + select(ClientBranch).where( + ClientBranch.tenant_id == tenant_id, + ClientBranch.id.in_(branch_ids), + ) + ).scalars().all() + } + + registrations: dict[int, ClientRegistration] = {} + registration_types: dict[int, RegistrationType] = {} + if registration_ids: + for registration, registration_type in db.execute( + select(ClientRegistration, RegistrationType) + .join( + RegistrationType, + RegistrationType.id == ClientRegistration.registration_type_id, + ) + .where( + ClientRegistration.tenant_id == tenant_id, + ClientRegistration.id.in_(registration_ids), + ) + ).all(): + registrations[registration.id] = registration + registration_types[registration.id] = registration_type + + # Registration scope can carry business/branch references even when the + # engagement's denormalised scope columns are blank. + extra_business_ids = { + int(row.business_unit_id) + for row in registrations.values() + if row.business_unit_id and row.business_unit_id not in businesses + } + if extra_business_ids: + businesses.update({ + row.id: row + for row in db.execute( + select(ClientBusinessUnit).where( + ClientBusinessUnit.tenant_id == tenant_id, + ClientBusinessUnit.id.in_(extra_business_ids), + ) + ).scalars().all() + }) + + extra_branch_ids = { + int(row.client_branch_id) + for row in registrations.values() + if row.client_branch_id and row.client_branch_id not in branches + } + if extra_branch_ids: + branches.update({ + row.id: row + for row in db.execute( + select(ClientBranch).where( + ClientBranch.tenant_id == tenant_id, + ClientBranch.id.in_(extra_branch_ids), + ) + ).scalars().all() + }) + + result: dict[int, dict[str, str]] = {} + + for subscription in subscriptions: + client = getattr(subscription, "client", None) + client_name = (getattr(client, "client_name", None) or "").strip() + client_trade_name = (getattr(client, "trade_name", None) or "").strip() + client_code = (getattr(client, "client_code", None) or "").strip() + + primary = client_trade_name or client_name or "Unlinked Client" + secondary = "" + context = "" + scope_label = "Client" + + scope_type = (getattr(subscription, "scope_type", None) or "client").strip().lower() + + if scope_type == "registration": + registration = registrations.get(getattr(subscription, "registration_id", None)) + registration_type = registration_types.get(getattr(subscription, "registration_id", None)) + + if registration: + business = businesses.get( + getattr(registration, "business_unit_id", None) + or getattr(subscription, "business_unit_id", None) + ) + branch = branches.get( + getattr(registration, "client_branch_id", None) + or getattr(subscription, "client_branch_id", None) + ) + + business_trade_name = ( + (getattr(business, "trade_name", None) or "").strip() + if business else "" + ) + business_name = ( + (getattr(business, "business_name", None) or "").strip() + if business else "" + ) + branch_name = ( + (getattr(branch, "branch_name", None) or "").strip() + if branch else "" + ) + + primary = ( + (getattr(registration, "trade_name", None) or "").strip() + or business_trade_name + or business_name + or branch_name + or (getattr(registration, "legal_name", None) or "").strip() + or client_trade_name + or client_name + or "Unlinked Client" + ) + + registration_code = ( + (getattr(registration_type, "code", None) or "").strip() + if registration_type else "" + ) + registration_number = ( + getattr(registration, "registration_number", None) or "" + ).strip() + + if registration_code and registration_number: + secondary = f"{registration_code} · {registration_number}" + else: + secondary = registration_number or registration_code + + if client_name and client_name != primary: + context = f"Client: {client_name}" + + scope_label = "Registration" + + elif scope_type == "business_unit": + business = businesses.get(getattr(subscription, "business_unit_id", None)) + if business: + primary = ( + (getattr(business, "trade_name", None) or "").strip() + or (getattr(business, "business_name", None) or "").strip() + or primary + ) + secondary = (getattr(business, "business_code", None) or "").strip() + if client_name and client_name != primary: + context = f"Client: {client_name}" + scope_label = "Business Unit" + + elif scope_type == "client_branch": + branch = branches.get(getattr(subscription, "client_branch_id", None)) + business = businesses.get( + getattr(branch, "business_unit_id", None) + if branch else getattr(subscription, "business_unit_id", None) + ) + if branch: + primary = (getattr(branch, "branch_name", None) or "").strip() or primary + secondary = (getattr(branch, "branch_code", None) or "").strip() + + business_display = "" + if business: + business_display = ( + (getattr(business, "trade_name", None) or "").strip() + or (getattr(business, "business_name", None) or "").strip() + ) + + context_parts: list[str] = [] + if business_display and business_display != primary: + context_parts.append(business_display) + if client_name and client_name not in {primary, business_display}: + context_parts.append(f"Client: {client_name}") + context = " · ".join(context_parts) + scope_label = "Client Branch" + + result[int(subscription.id)] = { + "primary": primary, + "secondary": secondary, + "context": context, + "scope_label": scope_label, + "client_code": client_code, + "client_name": client_name, + "client_trade_name": client_trade_name, + } + + return result + def list_employee_work_kanban( db: Session, scope: EmployeeScope, @@ -2905,7 +3140,7 @@ def list_employee_work_kanban( ClientServiceTaskInstance.task_name.ilike(like), ClientServiceTaskInstance.description.ilike(like), ClientServiceTaskInstance.client.has( - or_(Client.client_name.ilike(like), Client.client_code.ilike(like)) + or_(Client.client_name.ilike(like), Client.trade_name.ilike(like), Client.client_code.ilike(like)) ), ClientServiceTaskInstance.catalogue.has( or_( @@ -2926,6 +3161,18 @@ def list_employee_work_kanban( ) ).scalars().all() + work_subscriptions: dict[int, ClientServiceSubscription] = {} + for task in tasks: + subscription = getattr(task, "subscription", None) + if subscription is not None and getattr(subscription, "id", None): + work_subscriptions[int(subscription.id)] = subscription + + scope_display_map = _employee_work_scope_display_map( + db, + tenant_id=scope.tenant_id, + subscriptions=list(work_subscriptions.values()), + ) + columns = [ {"code": "pending", "label": "Pending", "cards": []}, {"code": "in_progress", "label": "In Progress", "cards": []}, @@ -2945,12 +3192,29 @@ def list_employee_work_kanban( ) if engagement_id not in engagement_lookup: client = getattr(task, "client", None) + scope_display = scope_display_map.get( + int(engagement_id), + { + "primary": ( + (getattr(client, "trade_name", None) or "").strip() + or getattr(client, "client_name", None) + or "Unlinked Client" + ), + "secondary": "", + "context": "", + "scope_label": "Client", + "client_code": getattr(client, "client_code", None) or "", + }, + ) engagement_lookup[engagement_id] = { "engagement_id": engagement_id, "subscription": subscription, "label": _subscription_label(subscription, task), - "client_name": getattr(client, "client_name", None) or "Unlinked Client", - "client_code": getattr(client, "client_code", None) or "", + "client_name": scope_display["primary"], + "client_code": scope_display.get("client_code", "") or getattr(client, "client_code", None) or "", + "scope_secondary": scope_display.get("secondary", ""), + "scope_context": scope_display.get("context", ""), + "scope_label": scope_display.get("scope_label", "Client"), "service_name": ( getattr(getattr(subscription, "catalogue", None), "service_name", None) or getattr(getattr(task, "catalogue", None), "service_name", None) @@ -3635,7 +3899,7 @@ def list_visible_work_assignment_dashboard( or_( ClientServiceTaskInstance.task_name.ilike(like), ClientServiceTaskInstance.description.ilike(like), - ClientServiceTaskInstance.client.has(or_(Client.client_name.ilike(like), Client.client_code.ilike(like))), + ClientServiceTaskInstance.client.has(or_(Client.client_name.ilike(like), Client.trade_name.ilike(like), Client.client_code.ilike(like))), ClientServiceTaskInstance.catalogue.has(or_(ServiceCatalogue.service_name.ilike(like), ServiceCatalogue.service_code.ilike(like))), ) ) @@ -4029,7 +4293,7 @@ def list_engagement_progress_dashboard( or_( ClientServiceTaskInstance.task_name.ilike(like), ClientServiceTaskInstance.description.ilike(like), - ClientServiceTaskInstance.client.has(or_(Client.client_name.ilike(like), Client.client_code.ilike(like))), + ClientServiceTaskInstance.client.has(or_(Client.client_name.ilike(like), Client.trade_name.ilike(like), Client.client_code.ilike(like))), ClientServiceTaskInstance.catalogue.has(or_(ServiceCatalogue.service_name.ilike(like), ServiceCatalogue.service_code.ilike(like))), ) )