Add Phase 20 stored bank reuse and richer reconciliation

This commit is contained in:
A R R R Associates
2026-08-24 18:58:58 +05:30
parent 769add8cb8
commit b8fbaf55ec
12 changed files with 1419 additions and 78 deletions
+101 -3
View File
@@ -17,6 +17,11 @@ from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
from .parsers.registry import BANK_OPTIONS
from .client_context import analyzer_client_context, validate_selected_client_and_engagement
from .stored_statement_service import (
deduplicate_source_paths,
list_stored_bank_statements,
prepare_stored_bank_statements,
)
from .service import (
can_use,
create_job_folder,
@@ -107,7 +112,7 @@ def _auth(request, db):
@router.get("")
def index(request: Request, job: str | None = None):
def index(request: Request, job: str | None = None, client_id: int | None = None):
ensure_worker_started()
db = CommonSessionLocal()
try:
@@ -119,6 +124,19 @@ def index(request: Request, job: str | None = None):
recent = [_localised_job_view(item, timezone_name) for item in list_user_jobs(user.id, limit=8)]
active_job = _localised_job_view(selected_job, timezone_name) if selected_job else None
client_context = analyzer_client_context(db, request=request, user=user, roles=roles)
visible_client_ids = {int(row.id) for row in client_context["clients"]}
selected_client_id = int(client_id) if client_id and int(client_id) in visible_client_ids else None
stored_statements = (
list_stored_bank_statements(
db,
request=request,
user=user,
roles=roles,
client_id=selected_client_id,
)
if selected_client_id
else []
)
return templates.TemplateResponse(
"modules/bank_statement_analyzer/templates/bank_statement_analyzer/index.html",
_ctx(
@@ -131,14 +149,38 @@ def index(request: Request, job: str | None = None):
display_timezone=timezone_name,
analyzer_clients=client_context["clients"],
analyzer_engagements=client_context["engagements"],
selected_client_id=selected_client_id,
stored_bank_statements=stored_statements,
),
)
finally:
db.close()
@router.get("/stored-statements")
def stored_statements(request: Request, client_id: int):
db = CommonSessionLocal()
try:
user, roles, denied = _auth(request, db)
if denied:
return JSONResponse({"detail": "Access denied"}, status_code=403)
try:
rows = list_stored_bank_statements(
db,
request=request,
user=user,
roles=roles,
client_id=int(client_id),
)
return JSONResponse({"items": rows})
except Exception as exc:
return JSONResponse({"detail": str(exc)}, status_code=400)
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(""), client_id: str = Form(""), engagement_id: str = Form(""), confirm_same_client: str | None = Form(None), enable_classification: str | None = Form(None), purpose: str = Form("analyze_only"), statements: list[UploadFile] = File(...)):
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(""), client_id: str = Form(""), engagement_id: str = Form(""), confirm_same_client: str | None = Form(None), enable_classification: str | None = Form(None), purpose: str = Form("analyze_only"), stored_version_ids: list[int] = Form([]), statements: list[UploadFile] = File(default=[])):
db = CommonSessionLocal()
selected_bank = bank_selection if bank_selection in dict(BANK_OPTIONS) else "auto"
classification_enabled = enable_classification == "1"
@@ -169,7 +211,50 @@ async def analyze(request: Request, csrf_token: str = Form(...), bank_selection:
job_id, input_dir, _output_dir = create_job_folder(user, roles)
job_dir = input_dir.parent
paths = await save_uploads(statements, input_dir)
uploaded = []
usable_uploads = [item for item in (statements or []) if item and (item.filename or "").strip()]
if usable_uploads:
uploaded = await save_uploads(usable_uploads, input_dir)
stored_paths = []
stored_provenance = []
pending_retrieval = []
if stored_version_ids:
if not resolved_client_id:
raise ValueError("Select an ERP client before reusing stored bank statements.")
stored_paths, stored_provenance, pending_retrieval = prepare_stored_bank_statements(
db,
request=request,
user=user,
roles=roles,
client_id=resolved_client_id,
version_ids=stored_version_ids,
input_dir=input_dir,
)
if pending_retrieval:
db.commit()
details = ", ".join(
f"{row['filename']} (retrieval #{row.get('request_id') or 'pending'})"
for row in pending_retrieval
)
raise ValueError(
"Stored statement retrieval has been queued from branch local storage: "
+ details
+ ". Wait for the Local Agent to return the file, then submit the analysis again."
)
paths, source_hashes = deduplicate_source_paths(uploaded + stored_paths)
retained_names = {path.name for path in paths}
stored_provenance = [
row
for row in stored_provenance
if row.get("copied_filename") in retained_names
]
if not paths:
raise ValueError("Select at least one new PDF or one stored bank statement.")
enqueue_job(
user=user,
roles=roles,
@@ -185,6 +270,8 @@ async def analyze(request: Request, csrf_token: str = Form(...), bank_selection:
engagement_id=resolved_engagement_id,
ownership_confirmation=(confirm_same_client == "1"),
purpose=purpose,
stored_source_versions=stored_provenance,
source_hashes=source_hashes,
)
return RedirectResponse(f"/tools/bank-statement-analyzer?job={job_id}#analysis-status", status_code=303)
except Exception as exc:
@@ -215,6 +302,17 @@ async def analyze(request: Request, csrf_token: str = Form(...), bank_selection:
analyzer_engagements=client_context["engagements"],
selected_client_id=(int(client_id) if str(client_id).strip().isdigit() else None),
selected_engagement_id=(int(engagement_id) if str(engagement_id).strip().isdigit() else None),
stored_bank_statements=(
list_stored_bank_statements(
db,
request=request,
user=user,
roles=get_user_roles(db, user.id),
client_id=int(client_id),
)
if str(client_id).strip().isdigit()
else []
),
),
status_code=400,
)