Add bank statement analyzer with automatic work storage
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user