Add Phase 20 stored bank reuse and richer reconciliation
This commit is contained in:
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user