from __future__ import annotations import shutil from pathlib import Path from fastapi import APIRouter, File, Form, Request, UploadFile from fastapi.responses import FileResponse, RedirectResponse from starlette.background import BackgroundTask from app.core.db.common import CommonSessionLocal from app.core.http_responses import ui_access_denied, not_found_response 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 .service import can_use, create_job, save_uploads, analyze_job, resolve_owned_job, delete_job router = APIRouter(prefix="/tools/bank-statement-analyzer", tags=["bank-statement-analyzer-ui"]) 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), "title": "Bank Statement Analyzer", } data.update(extra) return data def _auth(request, db): user = get_current_user(request, db=db) if not user: return None, None, RedirectResponse("/login", status_code=303) roles = get_user_roles(db, user.id) if not can_use(roles): return user, roles, ui_access_denied("Bank Statement Analyzer is available only to Partner, Manager, Staff and Consultant roles.") return user, roles, None @router.get("") def index(request: Request): db = CommonSessionLocal() try: user, roles, denied = _auth(request, db) if denied: return denied return templates.TemplateResponse("modules/bank_statement_analyzer/templates/bank_statement_analyzer/index.html", _ctx(request, db, user, error="")) finally: db.close() @router.post("/analyze") async def analyze(request: Request, csrf_token: str = Form(...), customer_name: str = Form(""), account_number: str = Form(""), statements: list[UploadFile] = File(...)): db = CommonSessionLocal() job_dir: Path | None = None try: user, roles, denied = _auth(request, db) if denied: return denied validate_csrf(request, csrf_token) job_id, input_dir, output_dir = create_job(user, roles) job_dir = input_dir.parent paths = await save_uploads(statements, input_dir) summary = analyze_job(user=user, roles=roles, job_id=job_id, paths=paths, output_dir=output_dir, customer_override=customer_name, account_override=account_number) return templates.TemplateResponse("modules/bank_statement_analyzer/templates/bank_statement_analyzer/result.html", _ctx(request, db, user, summary=summary)) except Exception as exc: if job_dir and job_dir.exists(): # Failed jobs are retained for the configured short retention period for troubleshooting/retry. pass user = get_current_user(request, db=db) if not user: return RedirectResponse("/login", status_code=303) return templates.TemplateResponse("modules/bank_statement_analyzer/templates/bank_statement_analyzer/index.html", _ctx(request, db, user, error=str(exc)), status_code=400) finally: db.close() @router.get("/{job_id}/download") def download(job_id: str, request: Request): db = CommonSessionLocal() try: user, roles, denied = _auth(request, db) if denied: return denied try: job, meta = resolve_owned_job(user, roles, job_id) except FileNotFoundError: return not_found_response(request, "Analysis job not found or already cleaned up.") output = job / "Output" / meta["output_file"] if not output.is_file(): return not_found_response(request, "Analysis workbook not found.") return FileResponse(path=output, filename="Bank_Statement_Analysis.xlsx", media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", background=BackgroundTask(delete_job, job)) finally: db.close() @router.post("/{job_id}/delete") def delete(job_id: str, request: Request, csrf_token: str = Form(...)): db = CommonSessionLocal() try: user, roles, denied = _auth(request, db) if denied: return denied validate_csrf(request, csrf_token) try: job, _ = resolve_owned_job(user, roles, job_id) delete_job(job) except FileNotFoundError: pass return RedirectResponse("/tools/bank-statement-analyzer", status_code=303) finally: db.close()