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.core.rbac.deps import get_user_permissions, get_user_roles from app.modules.core.rbac.permission_guard import require_permission from app.modules.services.execution import ( TASK_PRIORITIES, TASK_STATUSES, apply_task_update, dashboard_stats, generate_tasks_for_subscription, get_subscription_for_execution, get_task, list_assignees_for_execution, list_subscription_execution_payload, list_tasks_payload, ) router = APIRouter(prefix="/services/execution", tags=["services-execution-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), "task_statuses": TASK_STATUSES, "task_priorities": TASK_PRIORITIES, } 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 _assigned_user_filter(db, user) -> int | None: # Partner/Staff style users with own-only permission see only their assigned tasks. return int(user.id) if _has_perm(db, user, "clients.view.own_only") else None def _can_manage_execution(db, user) -> bool: return _has_perm(db, user, "service_tasks.edit") or _has_perm(db, user, "service_tasks.create") @router.get("") def execution_dashboard(request: Request, q: str = "", status: str = ""): 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, "service_tasks.view") except Exception: return _redirect_denied() tenant_id = _active_tenant_id(request, user) branch_id = _active_branch_id(request, user, db) assigned_to_user_id = _assigned_user_filter(db, user) stats = dashboard_stats(db, tenant_id=tenant_id, branch_id=branch_id, assigned_to_user_id=assigned_to_user_id) tasks = list_tasks_payload( db, tenant_id=tenant_id, branch_id=branch_id, assigned_to_user_id=assigned_to_user_id, status=status, q=q, ) return _render( request, "modules/services/templates/services/execution/dashboard.html", db, user, title="Service Execution Dashboard", stats=stats, tasks=tasks, q=q, status=status, can_manage=_can_manage_execution(db, user), ) finally: db.close() @router.get("/subscriptions") def subscription_execution_list(request: Request, q: str = ""): 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_execution_payload(db, tenant_id=tenant_id, branch_id=branch_id, q=q) return _render( request, "modules/services/templates/services/execution/subscriptions.html", db, user, title="Generate Service Tasks", rows=rows, q=q, can_generate=_has_perm(db, user, "service_tasks.create"), ) finally: db.close() @router.post("/subscriptions/{subscription_id}/generate") def generate_subscription_tasks(request: Request, subscription_id: int, 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, "service_tasks.create") except Exception: return _redirect_denied() tenant_id = _active_tenant_id(request, user) subscription = get_subscription_for_execution(db, tenant_id=tenant_id, subscription_id=subscription_id) if not subscription or not subscription.is_active or subscription.status != "active": return RedirectResponse(url="/services/execution/subscriptions", status_code=303) generate_tasks_for_subscription(db, subscription=subscription, user_id=user.id) db.commit() return RedirectResponse(url="/services/execution", status_code=303) finally: db.close() @router.get("/tasks/{task_id}/edit") def task_edit_page(request: Request, task_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, "service_tasks.view") except Exception: return _redirect_denied() tenant_id = _active_tenant_id(request, user) task = get_task(db, tenant_id=tenant_id, task_id=task_id) if not task: return RedirectResponse(url="/services/execution", status_code=303) own_only_user_id = _assigned_user_filter(db, user) if own_only_user_id and task.assigned_to_user_id != own_only_user_id: return _redirect_denied() branch_id = _active_branch_id(request, user, db) assignees = list_assignees_for_execution(db, tenant_id=tenant_id, branch_id=branch_id) return _render( request, "modules/services/templates/services/execution/task_form.html", db, user, title="Update Service Task", task=task, assignees=assignees, can_edit=_has_perm(db, user, "service_tasks.edit"), ) finally: db.close() @router.post("/tasks/{task_id}/edit") def task_edit_submit( request: Request, task_id: int, status: str = Form("pending"), priority: str = Form("normal"), assigned_to_user_id: str = Form(""), 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, "service_tasks.edit") except Exception: return _redirect_denied() tenant_id = _active_tenant_id(request, user) task = get_task(db, tenant_id=tenant_id, task_id=task_id) if not task: return RedirectResponse(url="/services/execution", status_code=303) own_only_user_id = _assigned_user_filter(db, user) if own_only_user_id and task.assigned_to_user_id != own_only_user_id: return _redirect_denied() apply_task_update( task, status=status, priority=priority, assigned_to_user_id=int(assigned_to_user_id) if assigned_to_user_id.strip() else None, remarks=remarks, is_active=is_active is not None, user_id=user.id, ) db.commit() return RedirectResponse(url="/services/execution", status_code=303) finally: db.close()