from __future__ import annotations from io import BytesIO from fastapi import APIRouter, File, Form, Request, UploadFile from fastapi.responses import RedirectResponse, StreamingResponse from app.core.db.common import CommonSessionLocal from app.core.http_responses import ui_access_denied 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.firm_admin_dashboard.service import ( build_dashboard_payload, build_user_onboarding_payload, can_access_firm_admin_dashboard, build_firm_user_import_template, create_firm_internal_user, get_onboarding_role_config, import_firm_internal_users, ) router = APIRouter(prefix="/firm-admin", tags=["firm-admin-dashboard-ui"]) VALID_TABS = { "overview": "modules/firm_admin_dashboard/templates/firm_admin_dashboard/partials/overview.html", "branches": "modules/firm_admin_dashboard/templates/firm_admin_dashboard/partials/branches.html", "users": "modules/firm_admin_dashboard/templates/firm_admin_dashboard/partials/users.html", "firm-settings": "modules/firm_admin_dashboard/templates/firm_admin_dashboard/partials/firm_settings.html", "services": "modules/firm_admin_dashboard/templates/firm_admin_dashboard/partials/services.html", "financial-years": "modules/firm_admin_dashboard/templates/firm_admin_dashboard/partials/financial_years.html", "reports": "modules/firm_admin_dashboard/templates/firm_admin_dashboard/partials/reports.html", "wizards": "modules/firm_admin_dashboard/templates/firm_admin_dashboard/partials/wizards.html", "audit-logs": "modules/firm_admin_dashboard/templates/firm_admin_dashboard/partials/audit_logs.html", } def _redirect_login(): return RedirectResponse(url="/login", status_code=303) def _ctx(request: Request, db, current_user, *, active_tab: str = "overview", **extra): payload = build_dashboard_payload(db, request, current_user) ctx = { "request": request, "current_user": current_user, "current_user_roles": get_user_roles(db, current_user.id), "current_user_permissions": get_user_permissions(db, current_user.id), "csrf_token": get_or_create_csrf_token(request), "title": "Firm Administration", "active_tab": active_tab, **payload, } ctx.update(extra) return ctx def _onboarding_ctx(request: Request, db, current_user, role: str, **extra): payload = build_user_onboarding_payload(db, request, current_user, role) ctx = { "request": request, "current_user": current_user, "current_user_roles": get_user_roles(db, current_user.id), "current_user_permissions": get_user_permissions(db, current_user.id), "csrf_token": get_or_create_csrf_token(request), "title": "Add Firm User", **payload, } ctx.update(extra) return ctx @router.get("/dashboard") def dashboard(request: Request, tab: str = "overview"): db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() if not can_access_firm_admin_dashboard(db, current_user): return ui_access_denied() active_tab = tab if tab in VALID_TABS else "overview" return templates.TemplateResponse( "modules/firm_admin_dashboard/templates/firm_admin_dashboard/dashboard.html", _ctx(request, db, current_user, active_tab=active_tab), ) finally: db.close() @router.get("/dashboard/tab/{tab_name}") def dashboard_tab(request: Request, tab_name: str): db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() if not can_access_firm_admin_dashboard(db, current_user): return ui_access_denied() active_tab = tab_name if tab_name in VALID_TABS else "overview" return templates.TemplateResponse(VALID_TABS[active_tab], _ctx(request, db, current_user, active_tab=active_tab)) finally: db.close() @router.get("/users/new") def user_onboarding_form(request: Request, role: str = "staff"): db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() if not can_access_firm_admin_dashboard(db, current_user): return ui_access_denied() if not get_onboarding_role_config(role): return RedirectResponse(url="/firm-admin/dashboard?tab=users", status_code=303) return templates.TemplateResponse( "modules/firm_admin_dashboard/templates/firm_admin_dashboard/user_onboarding.html", _onboarding_ctx(request, db, current_user, role), ) finally: db.close() @router.post("/users/new") def user_onboarding_submit( request: Request, role: str = Form(...), email: str = Form(...), full_name: str = Form(...), branch_id: int = Form(...), employee_code: str = Form(""), mobile: str = Form(""), department: str = Form(""), designation: str = Form(""), date_of_joining: str = Form(""), employment_type: str = Form("full_time"), consultant_type: str = Form("external_consultant"), firm_name: str = Form(""), specialisation: str = Form(""), gstin: str = Form(""), pan: str = Form(""), address: str = Form(""), remarks: str = Form(""), csrf_token: str = Form(...), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() if not can_access_firm_admin_dashboard(db, current_user): return ui_access_denied() if not get_onboarding_role_config(role): return RedirectResponse(url="/firm-admin/dashboard?tab=users", status_code=303) try: result = create_firm_internal_user( db, request=request, actor=current_user, role_key=role, email=email, full_name=full_name, branch_id=branch_id, employee_code=employee_code, mobile=mobile, department=department, designation=designation, date_of_joining=date_of_joining, employment_type=employment_type, consultant_type=consultant_type, firm_name=firm_name, specialisation=specialisation, gstin=gstin, pan=pan, address=address, remarks=remarks, ) except Exception as exc: db.rollback() return templates.TemplateResponse( "modules/firm_admin_dashboard/templates/firm_admin_dashboard/user_onboarding.html", _onboarding_ctx( request, db, current_user, role, flash=str(exc), form_data={ "email": email, "full_name": full_name, "branch_id": branch_id, "employee_code": employee_code, "mobile": mobile, "department": department, "designation": designation, "date_of_joining": date_of_joining, "employment_type": employment_type, "consultant_type": consultant_type, "firm_name": firm_name, "specialisation": specialisation, "gstin": gstin, "pan": pan, "address": address, "remarks": remarks, }, ), status_code=400, ) return templates.TemplateResponse( "modules/firm_admin_dashboard/templates/firm_admin_dashboard/user_onboarding_done.html", _ctx(request, db, current_user, active_tab="users", title="Invite Link", onboarding_result=result), ) finally: db.close() @router.get("/users/import") def user_import_form(request: Request): db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() if not can_access_firm_admin_dashboard(db, current_user): return ui_access_denied() return templates.TemplateResponse( "modules/firm_admin_dashboard/templates/firm_admin_dashboard/user_import.html", _ctx(request, db, current_user, active_tab="users", title="Import Firm Users"), ) finally: db.close() @router.get("/users/import/template") def user_import_template(request: Request): db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() if not can_access_firm_admin_dashboard(db, current_user): return ui_access_denied() content = build_firm_user_import_template(db, request, current_user) return StreamingResponse( BytesIO(content), media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": 'attachment; filename="firm_admin_user_import_template.xlsx"'}, ) finally: db.close() @router.post("/users/import") async def user_import_submit(request: Request, import_file: UploadFile = File(...), csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: current_user = get_current_user(request, db=db) if not current_user: return _redirect_login() if not can_access_firm_admin_dashboard(db, current_user): return ui_access_denied() if not import_file.filename or not import_file.filename.lower().endswith((".xlsx", ".xlsm")): return templates.TemplateResponse( "modules/firm_admin_dashboard/templates/firm_admin_dashboard/user_import.html", _ctx(request, db, current_user, active_tab="users", title="Import Firm Users", flash="Please upload an .xlsx Excel file."), status_code=400, ) try: content = await import_file.read() result = import_firm_internal_users(db, request=request, actor=current_user, content=content) except Exception as exc: db.rollback() return templates.TemplateResponse( "modules/firm_admin_dashboard/templates/firm_admin_dashboard/user_import.html", _ctx(request, db, current_user, active_tab="users", title="Import Firm Users", flash=str(exc)), status_code=400, ) return templates.TemplateResponse( "modules/firm_admin_dashboard/templates/firm_admin_dashboard/user_import_done.html", _ctx(request, db, current_user, active_tab="users", title="Import Result", import_result=result), ) finally: db.close()