from __future__ import annotations 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 .parsers.registry import BANK_OPTIONS 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", "bank_options": BANK_OPTIONS, "selected_bank": "auto", "financial_year": "", "classification_enabled": True, } 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(...), bank_selection: str = Form("auto"), financial_year: str = Form(""), customer_name: str = Form(""), account_number: str = Form(""), enable_classification: str | None = Form(None), statements: list[UploadFile] = File(...), ): db = CommonSessionLocal() job_dir: Path | None = None selected_bank = bank_selection if bank_selection in dict(BANK_OPTIONS) else "auto" classification_enabled = enable_classification == "1" 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, bank_selection=selected_bank, financial_year=financial_year, classification_enabled=classification_enabled, ) return templates.TemplateResponse( "modules/bank_statement_analyzer/templates/bank_statement_analyzer/result.html", _ctx(request, db, user, summary=summary), ) except Exception as exc: 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), selected_bank=selected_bank, financial_year=financial_year, classification_enabled=classification_enabled, ), 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()