from __future__ import annotations from datetime import date, datetime, timezone from fastapi import APIRouter, Form, Request from fastapi.responses import RedirectResponse from sqlalchemy import func, or_, select from sqlalchemy.orm import selectinload from app.core.db.common import CommonSessionLocal from app.core.security.csrf import get_or_create_csrf_token, validate_csrf from app.core.security.session_auth import get_current_user from app.core.templating import templates from app.modules.core.rbac.deps import get_user_permissions, get_user_roles from app.modules.core.rbac.permission_guard import require_permission from app.modules.core.tenancy.year_control import redirect_if_financial_year_locked from app.modules.services.client_services import ( SUBSCRIPTION_STATUSES, assessment_year_from_financial_year, attach_engagement_to_plan, ensure_engagement_quality_workflow, enforce_quality_gate_on_subscription, get_enabled_firm_service, get_existing_subscription, get_or_create_client_service_plan, list_assignable_users, list_clients_for_assignment, list_enabled_services_for_assignment, list_review_partners, normalize_financial_year, normalize_period_label, parse_date, period_choices_for_service, recurrence_requires_period, review_partner_required_for_engagement, ) from app.modules.clients.models import Client from app.modules.services.due_dates import apply_due_date_rule_to_subscription from app.modules.services.models import ( ClientServicePlan, ClientServiceSubscription, ClientServiceTaskInstance, ) router = APIRouter(prefix="/services/subscriptions", tags=["client-service-subscriptions-ui"]) def _tenant_id(request, user): return int( request.session.get("active_tenant_id") or request.session.get("selected_tenant_id") or request.session.get("tenant_id") or user.tenant_id ) def _branch_id(request, user): value = request.session.get("active_branch_id") if value in (None, "", 0, "0"): return int(getattr(user, "branch_id", 0) or 0) or None return int(value) def _active_financial_year(request: Request) -> str: return normalize_financial_year( request.session.get("active_financial_year") or getattr(request.state, "year_code", None) ) def _partner_scope_id(db, user) -> int | None: roles = set(get_user_roles(db, user.id)) return int(user.id) if "Partner" in roles else None def _ctx(request, db, user, **extra): data = { "request": request, "current_user": user, "current_user_roles": get_user_roles(db, user.id), "current_user_permissions": get_user_permissions(db, user.id), "csrf_token": get_or_create_csrf_token(request), "subscription_statuses": SUBSCRIPTION_STATUSES, } data.update(extra) return data def _denied(): from app.core.http_responses import ui_access_denied return ui_access_denied() def _load_plan(db, *, plan_id: int, tenant_id: int, branch_id: int | None): stmt = ( select(ClientServicePlan) .options( selectinload(ClientServicePlan.client), selectinload(ClientServicePlan.catalogue), selectinload(ClientServicePlan.default_partner), selectinload(ClientServicePlan.default_performing_partner), selectinload(ClientServicePlan.default_manager), selectinload(ClientServicePlan.default_staff), selectinload(ClientServicePlan.default_review_partner), ) .where( ClientServicePlan.id == plan_id, ClientServicePlan.tenant_id == tenant_id, ) ) if branch_id: stmt = stmt.where(ClientServicePlan.branch_id == branch_id) return db.execute(stmt).scalar_one_or_none() def _optional_int(value: str) -> int | None: value = (value or "").strip() return int(value) if value else None def _allowed_id(value: int | None, allowed: set[int], field_name: str) -> int | None: if value is not None and value not in allowed: raise ValueError(field_name) return value def _engagement_is_open(row: ClientServiceSubscription) -> bool: return ( not bool(getattr(row, "is_locked", False)) and (getattr(row, "status", "") or "").lower() not in {"completed", "cancelled", "inactive"} ) def _engagement_is_effective(row: ClientServiceSubscription, effective_from: date | None) -> bool: if not effective_from: return True comparison_date = ( getattr(row, "start_date", None) or getattr(row, "current_due_date", None) or getattr(row, "original_due_date", None) ) return comparison_date is None or comparison_date >= effective_from @router.get("") def subscription_master_list(request: Request, q: str = "", include_inactive: bool = False): db = CommonSessionLocal() try: user = get_current_user(request, db=db) if not user: return RedirectResponse("/login", 303) try: require_permission(db, user, "clients.view") except Exception: return _denied() tenant_id = _tenant_id(request, user) branch_id = _branch_id(request, user) counts = ( select( ClientServiceSubscription.service_plan_id, func.count(ClientServiceSubscription.id).label("engagement_count"), func.max(ClientServiceSubscription.current_due_date).label("latest_due_date"), ) .where(ClientServiceSubscription.service_plan_id.is_not(None)) .group_by(ClientServiceSubscription.service_plan_id) .subquery() ) stmt = ( select(ClientServicePlan, counts.c.engagement_count, counts.c.latest_due_date) .options( selectinload(ClientServicePlan.client), selectinload(ClientServicePlan.catalogue), selectinload(ClientServicePlan.default_partner), selectinload(ClientServicePlan.default_performing_partner), selectinload(ClientServicePlan.default_manager), selectinload(ClientServicePlan.default_staff), selectinload(ClientServicePlan.default_review_partner), ) .outerjoin(counts, counts.c.service_plan_id == ClientServicePlan.id) .where(ClientServicePlan.tenant_id == tenant_id) ) if branch_id: stmt = stmt.where(ClientServicePlan.branch_id == branch_id) if not include_inactive: stmt = stmt.where(ClientServicePlan.is_active.is_(True)) if q.strip(): from app.modules.clients.models import Client from app.modules.services.models import ServiceCatalogue term = f"%{q.strip()}%" stmt = ( stmt.join(Client, Client.id == ClientServicePlan.client_id) .join(ServiceCatalogue, ServiceCatalogue.id == ClientServicePlan.service_catalogue_id) .where( (Client.client_name.ilike(term)) | (Client.client_code.ilike(term)) | (ServiceCatalogue.service_name.ilike(term)) | (ServiceCatalogue.service_code.ilike(term)) ) ) rows = db.execute( stmt.order_by(ClientServicePlan.is_active.desc(), ClientServicePlan.id.desc()) ).all() return templates.TemplateResponse( "modules/services/templates/services/subscriptions/list.html", _ctx( request, db, user, title="Client Service Subscriptions", rows=rows, q=q, include_inactive=include_inactive, can_edit_subscription="clients.edit" in set(get_user_permissions(db, user.id)), ), ) finally: db.close() @router.get("/bulk") def subscription_bulk_page( request: Request, error: str = "", subscriptions_created: int = 0, subscriptions_reused: int = 0, engagements_created: int = 0, engagements_skipped: int = 0, ): db = CommonSessionLocal() try: user = get_current_user(request, db=db) if not user: return RedirectResponse("/login", 303) try: require_permission(db, user, "clients.edit") except Exception: return _denied() tenant_id = _tenant_id(request, user) branch_id = _branch_id(request, user) clients = list_clients_for_assignment( db, tenant_id=tenant_id, branch_id=branch_id, partner_id=_partner_scope_id(db, user), ) enabled_services = list_enabled_services_for_assignment(db, tenant_id=tenant_id) partners = list_assignable_users( db, tenant_id=tenant_id, branch_id=branch_id, role_names=("Partner",) ) managers = list_assignable_users( db, tenant_id=tenant_id, branch_id=branch_id, role_names=("Branch Manager",) ) staff_users = list_assignable_users( db, tenant_id=tenant_id, branch_id=branch_id, role_names=("Staff",) ) review_partners = list_review_partners( db, tenant_id=tenant_id, branch_id=branch_id ) error_messages = { "clients": "Select at least one permitted client.", "service": "Select a valid enabled firm service.", "partner": "Select a valid Engagement Partner.", "performing_partner": "Select a valid Performing Partner.", "manager": "Select a valid Manager.", "staff": "Select a valid Staff member.", "review_partner": "Select a valid Review Partner.", "review_partner_required": "Review Partner is mandatory for this assurance service.", "review_partner_independence": "Review Partner must differ from the Engagement and Performing Partners.", "period": "Select a valid month or quarter.", "generation": "Select a valid engagement generation option.", "financial_year": "The selected financial year is locked.", } return templates.TemplateResponse( "modules/services/templates/services/subscriptions/bulk.html", _ctx( request, db, user, title="Bulk Client Subscriptions", clients=clients, enabled_services=enabled_services, partners=partners, managers=managers, staff_users=staff_users, review_partners=review_partners, client_partner_names={ row.id: (row.full_name or row.email) for row in partners }, financial_year=_active_financial_year(request), error_message=error_messages.get(error, ""), subscriptions_created=subscriptions_created, subscriptions_reused=subscriptions_reused, engagements_created=engagements_created, engagements_skipped=engagements_skipped, ), ) finally: db.close() @router.post("/bulk") def subscription_bulk_submit( request: Request, client_ids: list[int] = Form([]), service_catalogue_id: int = Form(...), default_partner_user_id: int = Form(...), default_performing_partner_user_id: str = Form(""), default_manager_user_id: str = Form(""), default_staff_user_id: str = Form(""), default_review_partner_user_id: str = Form(""), effective_from: str = Form(""), effective_to: str = Form(""), auto_generate_periods: str | None = Form(None), remarks: str = Form(""), generation_mode: str = Form("subscription_only"), financial_year: str = Form(""), period_label: str = Form(""), csrf_token: str = Form(...), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() subscriptions_created = 0 subscriptions_reused = 0 engagements_created = 0 engagements_skipped = 0 try: user = get_current_user(request, db=db) if not user: return RedirectResponse("/login", 303) try: require_permission(db, user, "clients.edit") except Exception: return _denied() tenant_id = _tenant_id(request, user) branch_id = _branch_id(request, user) permitted_clients = list_clients_for_assignment( db, tenant_id=tenant_id, branch_id=branch_id, partner_id=_partner_scope_id(db, user), ) permitted_client_map = {row.id: row for row in permitted_clients} selected_client_ids = list(dict.fromkeys(int(value) for value in client_ids)) if not selected_client_ids or any(value not in permitted_client_map for value in selected_client_ids): return RedirectResponse("/services/subscriptions/bulk?error=clients", 303) firm_selection = get_enabled_firm_service( db, tenant_id=tenant_id, service_catalogue_id=service_catalogue_id, ) if not firm_selection: return RedirectResponse("/services/subscriptions/bulk?error=service", 303) plan_branch_id = ( branch_id or getattr(firm_selection, "default_branch_id", None) or getattr(user, "branch_id", None) ) partners = list_assignable_users( db, tenant_id=tenant_id, branch_id=plan_branch_id, role_names=("Partner",) ) managers = list_assignable_users( db, tenant_id=tenant_id, branch_id=plan_branch_id, role_names=("Branch Manager",) ) staff_users = list_assignable_users( db, tenant_id=tenant_id, branch_id=plan_branch_id, role_names=("Staff",) ) review_partners = list_review_partners( db, tenant_id=tenant_id, branch_id=plan_branch_id ) partner_ids = {row.id for row in partners} manager_ids = {row.id for row in managers} staff_ids = {row.id for row in staff_users} review_partner_ids = {row.id for row in review_partners} if default_partner_user_id not in partner_ids: return RedirectResponse("/services/subscriptions/bulk?error=partner", 303) performing_partner_id = _optional_int(default_performing_partner_user_id) or default_partner_user_id if performing_partner_id not in partner_ids: return RedirectResponse("/services/subscriptions/bulk?error=performing_partner", 303) manager_id = _optional_int(default_manager_user_id) if manager_id is not None and manager_id not in manager_ids: return RedirectResponse("/services/subscriptions/bulk?error=manager", 303) staff_id = _optional_int(default_staff_user_id) if staff_id is not None and staff_id not in staff_ids: return RedirectResponse("/services/subscriptions/bulk?error=staff", 303) review_partner_id = _optional_int(default_review_partner_user_id) if review_partner_id is not None and review_partner_id not in review_partner_ids: return RedirectResponse("/services/subscriptions/bulk?error=review_partner", 303) engagement_type = ( getattr(firm_selection.catalogue, "engagement_type", None) or "non_assurance" ) review_required = review_partner_required_for_engagement( db, tenant_id=tenant_id, engagement_type=engagement_type ) if review_required and review_partner_id is None: return RedirectResponse("/services/subscriptions/bulk?error=review_partner_required", 303) if review_required and review_partner_id in {default_partner_user_id, performing_partner_id}: return RedirectResponse("/services/subscriptions/bulk?error=review_partner_independence", 303) if generation_mode not in {"subscription_only", "current_period", "all_periods"}: return RedirectResponse("/services/subscriptions/bulk?error=generation", 303) selected_financial_year = normalize_financial_year( financial_year or _active_financial_year(request) ) if generation_mode != "subscription_only": locked_response = redirect_if_financial_year_locked( db, tenant_id=tenant_id, year_code=selected_financial_year, redirect_url="/services/subscriptions/bulk?error=financial_year", ) if locked_response: return locked_response recurrence_type = getattr(firm_selection.catalogue, "recurrence_type", None) if generation_mode == "subscription_only": requested_periods: list[str] = [] elif recurrence_requires_period(recurrence_type): if generation_mode == "all_periods": requested_periods = [ code for code, _label in period_choices_for_service( selected_financial_year, recurrence_type ) ] else: try: requested_periods = [ normalize_period_label( period_label, financial_year=selected_financial_year, recurrence_type=recurrence_type, ) ] except ValueError: return RedirectResponse("/services/subscriptions/bulk?error=period", 303) else: requested_periods = [""] plan_effective_from = parse_date(effective_from) plan_effective_to = parse_date(effective_to) if plan_effective_from and plan_effective_to and plan_effective_to < plan_effective_from: return RedirectResponse("/services/subscriptions/bulk?error=period", 303) for client_id in selected_client_ids: client = permitted_client_map[client_id] existing_plan = db.execute( select(ClientServicePlan).where( ClientServicePlan.tenant_id == tenant_id, ClientServicePlan.client_id == client.id, ClientServicePlan.service_catalogue_id == service_catalogue_id, ) ).scalar_one_or_none() plan = get_or_create_client_service_plan( db, tenant_id=tenant_id, client=client, catalogue=firm_selection.catalogue, firm_selection=firm_selection, branch_id=plan_branch_id or getattr(client, "branch_id", None), partner_user_id=default_partner_user_id, performing_partner_user_id=performing_partner_id, manager_user_id=manager_id, staff_user_id=staff_id, review_partner_user_id=review_partner_id, actor_user_id=user.id, remarks=remarks.strip() or None, ) plan.effective_from = plan_effective_from plan.effective_to = plan_effective_to plan.auto_generate_periods = auto_generate_periods is not None plan.status = "active" plan.is_active = True plan.updated_by_user_id = user.id if remarks.strip(): plan.remarks = remarks.strip() if existing_plan is None: subscriptions_created += 1 else: subscriptions_reused += 1 for requested_period in requested_periods: existing = get_existing_subscription( db, tenant_id=tenant_id, client_id=client.id, service_catalogue_id=service_catalogue_id, financial_year=selected_financial_year, period_label=requested_period, ) if existing: if existing.service_plan_id is None: existing.service_plan_id = plan.id engagements_skipped += 1 continue engagement = ClientServiceSubscription( tenant_id=tenant_id, branch_id=plan.branch_id or getattr(client, "branch_id", None), service_plan_id=plan.id, client_id=client.id, service_catalogue_id=service_catalogue_id, firm_service_selection_id=firm_selection.id, assigned_partner_user_id=default_partner_user_id, performing_partner_user_id=performing_partner_id, assigned_manager_user_id=manager_id, assigned_staff_user_id=staff_id, review_partner_user_id=review_partner_id if review_required else None, financial_year=selected_financial_year, period_label=requested_period, assessment_year=assessment_year_from_financial_year(selected_financial_year), engagement_type=engagement_type, start_date=plan_effective_from, end_date=plan_effective_to, status="active", remarks=remarks.strip() or None, is_active=True, created_by_user_id=user.id, updated_by_user_id=user.id, ) db.add(engagement) db.flush() attach_engagement_to_plan( db, engagement=engagement, client=client, catalogue=firm_selection.catalogue, firm_selection=firm_selection, actor_user_id=user.id, ) apply_due_date_rule_to_subscription(db, engagement, force=True) ensure_engagement_quality_workflow( db, subscription=engagement, actor_user_id=user.id, create_declarations=False, ) enforce_quality_gate_on_subscription(engagement) engagements_created += 1 db.commit() return RedirectResponse( "/services/subscriptions/bulk" f"?subscriptions_created={subscriptions_created}" f"&subscriptions_reused={subscriptions_reused}" f"&engagements_created={engagements_created}" f"&engagements_skipped={engagements_skipped}", status_code=303, ) except Exception: db.rollback() raise finally: db.close() @router.get("/{plan_id}/edit") def subscription_master_edit_page( request: Request, plan_id: int, saved: int = 0, updated_engagements: int = 0, updated_tasks: int = 0, error: str = "", ): db = CommonSessionLocal() try: user = get_current_user(request, db=db) if not user: return RedirectResponse("/login", 303) try: require_permission(db, user, "clients.edit") except Exception: return _denied() tenant_id = _tenant_id(request, user) branch_id = _branch_id(request, user) plan = _load_plan(db, plan_id=plan_id, tenant_id=tenant_id, branch_id=branch_id) if not plan: return RedirectResponse("/services/subscriptions", 303) partners = list_assignable_users( db, tenant_id=tenant_id, branch_id=plan.branch_id, role_names=("Partner",) ) managers = list_assignable_users( db, tenant_id=tenant_id, branch_id=plan.branch_id, role_names=("Branch Manager",) ) staff_users = list_assignable_users( db, tenant_id=tenant_id, branch_id=plan.branch_id, role_names=("Staff",) ) review_partners = list_review_partners( db, tenant_id=tenant_id, branch_id=plan.branch_id ) return templates.TemplateResponse( "modules/services/templates/services/subscriptions/edit.html", _ctx( request, db, user, title="Edit Client Service Subscription", plan=plan, partners=partners, managers=managers, staff_users=staff_users, review_partners=review_partners, saved=saved, updated_engagements=updated_engagements, updated_tasks=updated_tasks, error=error, ), ) finally: db.close() @router.post("/{plan_id}/edit") def subscription_master_edit_submit( request: Request, plan_id: int, default_partner_user_id: str = Form(""), default_performing_partner_user_id: str = Form(""), default_manager_user_id: str = Form(""), default_staff_user_id: str = Form(""), default_review_partner_user_id: str = Form(""), effective_from: str = Form(""), effective_to: str = Form(""), status: str = Form("active"), auto_generate_periods: str | None = Form(None), remarks: str = Form(""), propagation_mode: str = Form("future_only"), reassignment_effective_from: str = Form(""), reassignment_reason: str = Form(""), csrf_token: str = Form(...), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user = get_current_user(request, db=db) if not user: return RedirectResponse("/login", 303) try: require_permission(db, user, "clients.edit") except Exception: return _denied() tenant_id = _tenant_id(request, user) branch_id = _branch_id(request, user) plan = _load_plan(db, plan_id=plan_id, tenant_id=tenant_id, branch_id=branch_id) if not plan: return RedirectResponse("/services/subscriptions", 303) partners = list_assignable_users( db, tenant_id=tenant_id, branch_id=plan.branch_id, role_names=("Partner",) ) managers = list_assignable_users( db, tenant_id=tenant_id, branch_id=plan.branch_id, role_names=("Branch Manager",) ) staff_users = list_assignable_users( db, tenant_id=tenant_id, branch_id=plan.branch_id, role_names=("Staff",) ) review_partners = list_review_partners( db, tenant_id=tenant_id, branch_id=plan.branch_id ) try: new_partner_id = _allowed_id( _optional_int(default_partner_user_id), {row.id for row in partners}, "partner" ) requested_performing_id = _allowed_id( _optional_int(default_performing_partner_user_id), {row.id for row in partners}, "performing_partner", ) new_performing_id = requested_performing_id or new_partner_id new_manager_id = _allowed_id( _optional_int(default_manager_user_id), {row.id for row in managers}, "manager" ) new_staff_id = _allowed_id( _optional_int(default_staff_user_id), {row.id for row in staff_users}, "staff" ) new_review_partner_id = _allowed_id( _optional_int(default_review_partner_user_id), {row.id for row in review_partners}, "review_partner", ) plan_effective_from = parse_date(effective_from) plan_effective_to = parse_date(effective_to) change_effective_from = parse_date(reassignment_effective_from) except (TypeError, ValueError) as exc: code = str(exc) if str(exc) else "validation" db.rollback() return RedirectResponse( f"/services/subscriptions/{plan.id}/edit?error={code}", status_code=303 ) allowed_statuses = {item[0] for item in SUBSCRIPTION_STATUSES} normalized_status = (status or "active").strip().lower() if normalized_status not in allowed_statuses: return RedirectResponse( f"/services/subscriptions/{plan.id}/edit?error=status", status_code=303 ) if plan_effective_from and plan_effective_to and plan_effective_to < plan_effective_from: return RedirectResponse( f"/services/subscriptions/{plan.id}/edit?error=dates", status_code=303 ) if propagation_mode not in {"future_only", "open_engagements"}: return RedirectResponse( f"/services/subscriptions/{plan.id}/edit?error=propagation", status_code=303 ) old_staff_id = plan.default_staff_user_id plan.default_partner_user_id = new_partner_id plan.default_performing_partner_user_id = new_performing_id plan.default_manager_user_id = new_manager_id plan.default_staff_user_id = new_staff_id plan.default_review_partner_user_id = new_review_partner_id plan.effective_from = plan_effective_from plan.effective_to = plan_effective_to plan.auto_generate_periods = auto_generate_periods is not None plan.status = normalized_status plan.is_active = normalized_status not in {"inactive", "cancelled"} plan.remarks = remarks.strip() or None plan.updated_by_user_id = user.id plan.updated_at_utc = datetime.now(timezone.utc) updated_engagement_count = 0 updated_task_count = 0 if propagation_mode == "open_engagements": engagements = db.execute( select(ClientServiceSubscription).where( ClientServiceSubscription.service_plan_id == plan.id, ClientServiceSubscription.tenant_id == tenant_id, ) ).scalars().all() affected_ids: list[int] = [] for row in engagements: if not _engagement_is_open(row): continue if not _engagement_is_effective(row, change_effective_from): continue row.assigned_partner_user_id = new_partner_id row.performing_partner_user_id = new_performing_id row.assigned_manager_user_id = new_manager_id row.assigned_staff_user_id = new_staff_id row.review_partner_user_id = new_review_partner_id row.updated_by_user_id = user.id row.updated_at_utc = datetime.now(timezone.utc) if reassignment_reason.strip(): note = ( f"Subscription team updated on {datetime.now(timezone.utc).date().isoformat()} " f"by user {user.id}: {reassignment_reason.strip()}" ) row.remarks = f"{row.remarks.strip()}\n{note}" if row.remarks else note affected_ids.append(row.id) updated_engagement_count += 1 if affected_ids and old_staff_id != new_staff_id: task_stmt = select(ClientServiceTaskInstance).where( ClientServiceTaskInstance.subscription_id.in_(affected_ids), ClientServiceTaskInstance.is_active.is_(True), ClientServiceTaskInstance.is_locked.is_(False), ClientServiceTaskInstance.status.notin_(("completed", "cancelled")), ) if old_staff_id is None: task_stmt = task_stmt.where( ClientServiceTaskInstance.assigned_to_user_id.is_(None), func.lower(func.coalesce(ClientServiceTaskInstance.default_role_name, "")) == "staff", ) else: task_stmt = task_stmt.where( ClientServiceTaskInstance.assigned_to_user_id == old_staff_id ) tasks = db.execute(task_stmt).scalars().all() for task in tasks: task.assigned_to_user_id = new_staff_id task.updated_by_user_id = user.id task.updated_at_utc = datetime.now(timezone.utc) updated_task_count += 1 db.commit() return RedirectResponse( f"/services/subscriptions/{plan.id}/edit?saved=1" f"&updated_engagements={updated_engagement_count}" f"&updated_tasks={updated_task_count}", status_code=303, ) except Exception: db.rollback() raise finally: db.close() @router.get("/{plan_id}") def subscription_master_detail(request: Request, plan_id: int): db = CommonSessionLocal() try: user = get_current_user(request, db=db) if not user: return RedirectResponse("/login", 303) try: require_permission(db, user, "clients.view") except Exception: return _denied() tenant_id = _tenant_id(request, user) branch_id = _branch_id(request, user) plan = _load_plan(db, plan_id=plan_id, tenant_id=tenant_id, branch_id=branch_id) if not plan: return RedirectResponse("/services/subscriptions", 303) engagements = db.execute( select(ClientServiceSubscription) .where(ClientServiceSubscription.service_plan_id == plan.id) .order_by( ClientServiceSubscription.financial_year.desc(), ClientServiceSubscription.period_label.asc(), ClientServiceSubscription.id.desc(), ) ).scalars().all() permissions = set(get_user_permissions(db, user.id)) return templates.TemplateResponse( "modules/services/templates/services/subscriptions/detail.html", _ctx( request, db, user, title="Client Service Subscription", plan=plan, engagements=engagements, can_edit_subscription="clients.edit" in permissions, ), ) finally: db.close()