from __future__ import annotations from fastapi import APIRouter, Form, Request from fastapi.responses import RedirectResponse 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.services.models import ClientServiceSubscription from app.modules.services.client_services import ( SUBSCRIPTION_STATUSES, get_enabled_firm_service, get_existing_subscription, get_subscription, list_assignable_users, list_clients_for_assignment, list_enabled_services_for_assignment, list_subscription_payload, parse_date, ) from app.modules.core.rbac.deps import get_user_permissions, get_user_roles from app.modules.core.rbac.permission_guard import require_permission router = APIRouter(prefix="/services/client-services", tags=["services-client-services-ui"]) def _base_ctx(request: Request, user, db, **ctx): base = { "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, } base.update(ctx) return base def _render(request: Request, template: str, db, user, **ctx): return templates.TemplateResponse(template, _base_ctx(request, user, db, **ctx)) def _redirect_denied(): return RedirectResponse(url="/system-settings", status_code=303) def _has_perm(db, user, code: str) -> bool: try: require_permission(db, user, code) return True except Exception: return False def _active_tenant_id(request: Request, user) -> int: 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 _active_branch_id(request: Request, user, db) -> int | None: value = request.session.get("active_branch_id") if value in (None, "", 0, "0"): if _has_perm(db, user, "clients.cross_branch"): return None return int(getattr(user, "branch_id", 0) or 0) or None return int(value) def _locked_partner_id(db, user) -> int | None: return int(user.id) if _has_perm(db, user, "clients.view.own_only") else None def _can_manage_client_services(db, user) -> bool: return _has_perm(db, user, "clients.edit") @router.get("") def subscription_list(request: Request, q: str = "", include_inactive: bool = True): db = CommonSessionLocal() try: user = get_current_user(request, db=db) if not user: return RedirectResponse(url="/login", status_code=303) try: require_permission(db, user, "clients.view") except Exception: return _redirect_denied() tenant_id = _active_tenant_id(request, user) branch_id = _active_branch_id(request, user, db) rows = list_subscription_payload( db, tenant_id=tenant_id, branch_id=branch_id, q=q, include_inactive=include_inactive, ) return _render( request, "modules/services/templates/services/client_services/list.html", db, user, title="Client Service Subscriptions", rows=rows, q=q, include_inactive=include_inactive, can_manage=_can_manage_client_services(db, user), ) finally: db.close() @router.get("/new") def subscription_create_page(request: Request, client_id: int | None = None): db = CommonSessionLocal() try: user = get_current_user(request, db=db) if not user: return RedirectResponse(url="/login", status_code=303) try: require_permission(db, user, "clients.edit") except Exception: return _redirect_denied() tenant_id = _active_tenant_id(request, user) branch_id = _active_branch_id(request, user, db) clients = list_clients_for_assignment( db, tenant_id=tenant_id, branch_id=branch_id, partner_id=_locked_partner_id(db, user), ) enabled_services = list_enabled_services_for_assignment(db, tenant_id=tenant_id) assignable_users = list_assignable_users(db, tenant_id=tenant_id, branch_id=branch_id) return _render( request, "modules/services/templates/services/client_services/form.html", db, user, title="Assign Service to Client", mode="create", subscription=None, clients=clients, enabled_services=enabled_services, assignable_users=assignable_users, selected_client_id=client_id, ) finally: db.close() @router.post("/new") def subscription_create_submit( request: Request, client_id: int = Form(...), service_catalogue_id: int = Form(...), assigned_partner_user_id: str = Form(""), assigned_manager_user_id: str = Form(""), assigned_staff_user_id: str = Form(""), start_date: str = Form(""), end_date: str = Form(""), status: str = Form("active"), remarks: str = Form(""), is_active: str | None = Form(None), csrf_token: str = Form(...), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user = get_current_user(request, db=db) if not user: return RedirectResponse(url="/login", status_code=303) try: require_permission(db, user, "clients.edit") except Exception: return _redirect_denied() tenant_id = _active_tenant_id(request, user) firm_selection = get_enabled_firm_service(db, tenant_id=tenant_id, service_catalogue_id=service_catalogue_id) if not firm_selection: return RedirectResponse(url="/services/client-services/new", status_code=303) existing = get_existing_subscription( db, tenant_id=tenant_id, client_id=client_id, service_catalogue_id=service_catalogue_id, ) if existing: row = existing else: row = ClientServiceSubscription( tenant_id=tenant_id, client_id=client_id, service_catalogue_id=service_catalogue_id, created_by_user_id=user.id, ) db.add(row) row.branch_id = firm_selection.default_branch_id or getattr(user, "branch_id", None) row.firm_service_selection_id = firm_selection.id row.assigned_partner_user_id = int(assigned_partner_user_id) if assigned_partner_user_id.strip() else None row.assigned_manager_user_id = int(assigned_manager_user_id) if assigned_manager_user_id.strip() else None row.assigned_staff_user_id = int(assigned_staff_user_id) if assigned_staff_user_id.strip() else None row.start_date = parse_date(start_date) row.end_date = parse_date(end_date) row.status = status or "active" row.remarks = remarks.strip() or None row.is_active = is_active is not None row.updated_by_user_id = user.id db.commit() db.refresh(row) return RedirectResponse(url=f"/services/client-services/{row.id}", status_code=303) finally: db.close() @router.get("/{subscription_id}") def subscription_detail(request: Request, subscription_id: int): db = CommonSessionLocal() try: user = get_current_user(request, db=db) if not user: return RedirectResponse(url="/login", status_code=303) try: require_permission(db, user, "clients.view") except Exception: return _redirect_denied() tenant_id = _active_tenant_id(request, user) row = get_subscription(db, subscription_id=subscription_id, tenant_id=tenant_id) if not row: return RedirectResponse(url="/services/client-services", status_code=303) return _render( request, "modules/services/templates/services/client_services/detail.html", db, user, title="Client Service Subscription", row=row, can_manage=_can_manage_client_services(db, user), ) finally: db.close() @router.get("/{subscription_id}/edit") def subscription_edit_page(request: Request, subscription_id: int): db = CommonSessionLocal() try: user = get_current_user(request, db=db) if not user: return RedirectResponse(url="/login", status_code=303) try: require_permission(db, user, "clients.edit") except Exception: return _redirect_denied() tenant_id = _active_tenant_id(request, user) branch_id = _active_branch_id(request, user, db) row = get_subscription(db, subscription_id=subscription_id, tenant_id=tenant_id) if not row: return RedirectResponse(url="/services/client-services", status_code=303) clients = list_clients_for_assignment( db, tenant_id=tenant_id, branch_id=branch_id, partner_id=_locked_partner_id(db, user), ) enabled_services = list_enabled_services_for_assignment(db, tenant_id=tenant_id) assignable_users = list_assignable_users(db, tenant_id=tenant_id, branch_id=branch_id) return _render( request, "modules/services/templates/services/client_services/form.html", db, user, title="Edit Client Service Subscription", mode="edit", subscription=row, clients=clients, enabled_services=enabled_services, assignable_users=assignable_users, selected_client_id=row.client_id, ) finally: db.close() @router.post("/{subscription_id}/edit") def subscription_edit_submit( request: Request, subscription_id: int, assigned_partner_user_id: str = Form(""), assigned_manager_user_id: str = Form(""), assigned_staff_user_id: str = Form(""), start_date: str = Form(""), end_date: str = Form(""), status: str = Form("active"), remarks: str = Form(""), is_active: str | None = Form(None), csrf_token: str = Form(...), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user = get_current_user(request, db=db) if not user: return RedirectResponse(url="/login", status_code=303) try: require_permission(db, user, "clients.edit") except Exception: return _redirect_denied() tenant_id = _active_tenant_id(request, user) row = get_subscription(db, subscription_id=subscription_id, tenant_id=tenant_id) if not row: return RedirectResponse(url="/services/client-services", status_code=303) row.assigned_partner_user_id = int(assigned_partner_user_id) if assigned_partner_user_id.strip() else None row.assigned_manager_user_id = int(assigned_manager_user_id) if assigned_manager_user_id.strip() else None row.assigned_staff_user_id = int(assigned_staff_user_id) if assigned_staff_user_id.strip() else None row.start_date = parse_date(start_date) row.end_date = parse_date(end_date) row.status = status or "active" row.remarks = remarks.strip() or None row.is_active = is_active is not None row.updated_by_user_id = user.id db.commit() return RedirectResponse(url=f"/services/client-services/{row.id}", status_code=303) finally: db.close()