From eb9d65cd8b720256238d8c372fb02034b8ad44ed Mon Sep 17 00:00:00 2001 From: A R R R Associates Date: Fri, 7 Aug 2026 12:49:17 +0530 Subject: [PATCH] Show scope identity for client service subscriptions --- app/modules/services/subscriptions_ui.py | 254 ++++++++++++++++++ .../services/subscriptions/detail.html | 5 +- .../services/subscriptions/edit.html | 5 +- .../services/subscriptions/list.html | 27 +- .../services/subscriptions/scope.html | 5 +- 5 files changed, 288 insertions(+), 8 deletions(-) diff --git a/app/modules/services/subscriptions_ui.py b/app/modules/services/subscriptions_ui.py index c271cfc..a1505cb 100644 --- a/app/modules/services/subscriptions_ui.py +++ b/app/modules/services/subscriptions_ui.py @@ -143,6 +143,239 @@ def _registration_scope_context(db, *, tenant_id: int, plan: ClientServicePlan): } + +def _subscription_scope_display_map( + db, + *, + tenant_id: int, + plans: list[ClientServicePlan], +) -> dict[int, dict[str, str]]: + """Resolve human-friendly subscription names without changing stored scope data.""" + if not plans: + return {} + + business_ids = {int(row.business_unit_id) for row in plans if row.business_unit_id} + branch_ids = {int(row.client_branch_id) for row in plans if row.client_branch_id} + registration_ids = {int(row.registration_id) for row in plans if row.registration_id} + + businesses = {} + 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 = {} + 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 = {} + registration_types = {} + if registration_ids: + registration_rows = 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() + for registration, registration_type in registration_rows: + registrations[registration.id] = registration + registration_types[registration.id] = registration_type + + # A mapped registration can carry its own Business Unit / Client Branch. + # Load those as fallbacks even when the plan's denormalised IDs are absent. + 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 plan in plans: + client = plan.client + client_name = (getattr(client, "client_name", None) or "").strip() + client_code = (getattr(client, "client_code", None) or "").strip() + client_trade_name = (getattr(client, "trade_name", None) or "").strip() + scope_type = (getattr(plan, "scope_type", None) or "client").strip().lower() + + primary = client_name or client_trade_name or client_code or f"Client {plan.client_id}" + secondary = client_code + context = "" + scope_label = "Client" + + if scope_type == "business_unit": + business = businesses.get(plan.business_unit_id) + 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() + context = client_name if client_name and client_name != primary else "" + scope_label = "Business Unit" + + elif scope_type == "client_branch": + branch = branches.get(plan.client_branch_id) + business = businesses.get( + getattr(branch, "business_unit_id", None) if branch else plan.business_unit_id + ) + if branch: + primary = (getattr(branch, "branch_name", None) or "").strip() or primary + secondary = (getattr(branch, "branch_code", None) or "").strip() + business_name = "" + if business: + business_name = ( + (getattr(business, "trade_name", None) or "").strip() + or (getattr(business, "business_name", None) or "").strip() + ) + context_parts = [] + if business_name and business_name != primary: + context_parts.append(business_name) + if client_name and client_name not in {primary, business_name}: + context_parts.append(f"Client: {client_name}") + context = " · ".join(context_parts) + scope_label = "Client Branch" + + elif scope_type == "registration": + registration = registrations.get(plan.registration_id) + registration_type = registration_types.get(plan.registration_id) + if registration: + business = businesses.get( + getattr(registration, "business_unit_id", None) or plan.business_unit_id + ) + branch = branches.get( + getattr(registration, "client_branch_id", None) or plan.client_branch_id + ) + + 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 "" + ) + + # Registration-level subscriptions are identified by the + # registration's own trade name first. This prevents one + # individual/legal client with multiple registrations from + # appearing as several indistinguishable rows. + 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 primary + ) + registration_number = ( + getattr(registration, "registration_number", None) or "" + ).strip() + registration_code = ( + getattr(registration_type, "code", None) or "" + ).strip() + secondary = ( + f"{registration_code} · {registration_number}" + if registration_code and registration_number + else registration_number or registration_code + ) + context_parts = [] + if branch_name and branch_name != primary: + context_parts.append(branch_name) + if client_name and client_name != primary: + context_parts.append(f"Client: {client_name}") + context = " · ".join(context_parts) + scope_label = "Registration" + else: + # Legacy/unmapped rows retain their existing client display. + # No stored subscription or engagement data is altered. + scope_label = "Registration" + + result[int(plan.id)] = { + "primary": primary, + "secondary": secondary, + "context": context, + "scope_label": scope_label, + "client_name": client_name, + "client_code": client_code, + } + + return result + + +def _subscription_scope_display( + db, + *, + tenant_id: int, + plan: ClientServicePlan, +) -> dict[str, str]: + return _subscription_scope_display_map( + db, + tenant_id=tenant_id, + plans=[plan], + ).get( + int(plan.id), + { + "primary": plan.client.client_name, + "secondary": plan.client.client_code or "", + "context": "", + "scope_label": "Client", + "client_name": plan.client.client_name, + "client_code": plan.client.client_code or "", + }, + ) + def _scope_key_for(scope_type: str, target_id: int) -> str: prefixes = { "client": "CLIENT", @@ -282,6 +515,11 @@ def subscription_master_list(request: Request, q: str = "", include_inactive: bo rows = db.execute( stmt.order_by(ClientServicePlan.is_active.desc(), ClientServicePlan.id.desc()) ).all() + scope_displays = _subscription_scope_display_map( + db, + tenant_id=tenant_id, + plans=[row[0] for row in rows], + ) return templates.TemplateResponse( "modules/services/templates/services/subscriptions/list.html", _ctx( @@ -290,6 +528,7 @@ def subscription_master_list(request: Request, q: str = "", include_inactive: bo user, title="Client Service Subscriptions", rows=rows, + scope_displays=scope_displays, q=q, include_inactive=include_inactive, can_edit_subscription="clients.edit" in set(get_user_permissions(db, user.id)), @@ -659,6 +898,11 @@ def subscription_scope_edit_page( saved=saved, updated_engagements=updated_engagements, error_message=error_messages.get(error, ""), + scope_display=_subscription_scope_display( + db, + tenant_id=tenant_id, + plan=plan, + ), **scope_context, ), ) @@ -971,6 +1215,11 @@ def subscription_master_edit_page( updated_engagements=updated_engagements, updated_tasks=updated_tasks, error=error, + scope_display=_subscription_scope_display( + db, + tenant_id=tenant_id, + plan=plan, + ), ), ) finally: @@ -1192,6 +1441,11 @@ def subscription_master_detail(request: Request, plan_id: int): plan=plan, engagements=engagements, can_edit_subscription="clients.edit" in permissions, + scope_display=_subscription_scope_display( + db, + tenant_id=tenant_id, + plan=plan, + ), **_registration_scope_context( db, tenant_id=tenant_id, diff --git a/app/modules/services/templates/services/subscriptions/detail.html b/app/modules/services/templates/services/subscriptions/detail.html index d90c363..e422735 100644 --- a/app/modules/services/templates/services/subscriptions/detail.html +++ b/app/modules/services/templates/services/subscriptions/detail.html @@ -9,10 +9,13 @@

- {{ plan.client.client_name }} — {{ plan.catalogue.service_name }} + {{ scope_display.primary if scope_display else plan.client.client_name }} — {{ plan.catalogue.service_name }}

Client service subscription master and its period-wise engagements. + {% if scope_display and scope_display.context %} + {{ scope_display.context }} + {% endif %}

diff --git a/app/modules/services/templates/services/subscriptions/edit.html b/app/modules/services/templates/services/subscriptions/edit.html index 79a8225..279c5b6 100644 --- a/app/modules/services/templates/services/subscriptions/edit.html +++ b/app/modules/services/templates/services/subscriptions/edit.html @@ -10,7 +10,10 @@

Edit Client Service Subscription

- {{ plan.client.client_name }} — {{ plan.catalogue.service_name }} + {{ scope_display.primary if scope_display else plan.client.client_name }} — {{ plan.catalogue.service_name }} + {% if scope_display and scope_display.context %} + {{ scope_display.context }} + {% endif %}

Client Service Subscriptions

- Persistent client-level services. Period-wise engagements are generated and tracked separately. + Persistent client services shown by their assigned scope. Period-wise engagements are generated and tracked separately.