Add Phase 11B client aware multi bank ownership and engagement archive

This commit is contained in:
A R R R Associates
2026-08-22 20:45:36 +05:30
parent 8748ee9539
commit 016299cf0c
9 changed files with 714 additions and 8 deletions
+53 -3
View File
@@ -20,9 +20,16 @@ from app.core.db.common import CommonSessionLocal
from .analyzer import analyze_files, export_excel
from .models import BankStatementAnalysisJob
from .client_context import (
archive_analysis_to_engagement,
validate_statement_ownership,
)
from app.modules.core.iam.models import User
from app.modules.services.models import ClientServiceSubscription
ALLOWED_ROLES = {"Partner", "Manager", "Branch Manager", "Staff", "Employee", "Consultant"}
MAX_FILES = int(os.getenv("BANK_ANALYZER_MAX_FILES", "24"))
# 0 means no fixed statement-count ceiling; per-file size and queue controls remain.
MAX_FILES = int(os.getenv("BANK_ANALYZER_MAX_FILES", "0"))
MAX_FILE_BYTES = int(os.getenv("BANK_ANALYZER_MAX_FILE_MB", "50")) * 1024 * 1024
MAX_GLOBAL_PROCESSING = 3
MAX_PENDING_PER_USER = 3
@@ -117,7 +124,7 @@ async def save_uploads(files: list[UploadFile], input_dir: Path) -> list[Path]:
usable = [item for item in files if item and (item.filename or "").strip()]
if not usable:
raise ValueError("Please select at least one PDF bank statement.")
if len(usable) > MAX_FILES:
if MAX_FILES > 0 and len(usable) > MAX_FILES:
raise ValueError(f"A maximum of {MAX_FILES} PDF files can be analyzed in one job.")
saved: list[Path] = []
try:
@@ -155,7 +162,7 @@ def pending_count_for_user(user_id: int) -> int:
db.close()
def enqueue_job(*, user, roles: Iterable[str], job_id: str, paths: list[Path], job_dir: Path, bank_selection: str, financial_year: str, customer_override: str, account_override: str, classification_enabled: bool) -> BankStatementAnalysisJob:
def enqueue_job(*, user, roles: Iterable[str], job_id: str, paths: list[Path], job_dir: Path, bank_selection: str, financial_year: str, customer_override: str, account_override: str, classification_enabled: bool, client_id: int | None = None, engagement_id: int | None = None, ownership_confirmation: bool = False) -> BankStatementAnalysisJob:
if pending_count_for_user(int(user.id)) >= MAX_PENDING_PER_USER:
shutil.rmtree(job_dir, ignore_errors=True)
raise ValueError("You already have three queued or processing analyses. Please wait for one to complete before submitting another.")
@@ -169,6 +176,11 @@ def enqueue_job(*, user, roles: Iterable[str], job_id: str, paths: list[Path], j
branch_id=getattr(user, "branch_id", None),
user_id=int(user.id),
role_bucket=bucket,
client_id=int(client_id) if client_id else None,
engagement_id=int(engagement_id) if engagement_id else None,
ownership_confirmation=bool(ownership_confirmation),
ownership_status="pending" if client_id else "not_checked",
engagement_archive_status="pending" if engagement_id else "not_requested",
selected_bank=bank_selection,
financial_year=(financial_year or "").strip() or None,
customer_override=(customer_override or "").strip() or None,
@@ -245,6 +257,18 @@ def _process_job(job_id: str) -> None:
bank_hint=job.selected_bank,
classification_enabled=job.classification_enabled,
)
ownership = validate_statement_ownership(
db,
tenant_id=int(job.tenant_id or 0),
client_id=job.client_id,
metas=metas,
owner_override=job.customer_override or "",
ownership_confirmation=bool(job.ownership_confirmation),
)
job.ownership_status = str(ownership.get("status") or "not_checked")
job.ownership_validation_json = json.dumps(ownership, ensure_ascii=False)
job.progress_percent = 75
db.commit()
output = output_dir / _workbook_filename(metas)
@@ -257,6 +281,28 @@ def _process_job(job_id: str) -> None:
selected_bank=job.selected_bank,
classification_enabled=job.classification_enabled,
)
archive_result = {"status": "not_requested", "documents": []}
if job.client_id and job.engagement_id:
engagement = db.get(ClientServiceSubscription, int(job.engagement_id))
uploader = db.get(User, int(job.user_id))
if not engagement or int(engagement.client_id) != int(job.client_id):
raise ValueError("Selected engagement is no longer valid for the selected client.")
if not uploader:
raise ValueError("Bank Analyzer uploader could not be resolved for document archive.")
archive_result = archive_analysis_to_engagement(
db,
job=job,
engagement=engagement,
metas=metas,
source_paths=paths,
output_path=output,
user=uploader,
)
job.engagement_archive_status = str(archive_result.get("status") or "failed")
job.engagement_archive_json = json.dumps(archive_result, ensure_ascii=False)
db.commit()
summary = {
"job_id": job.id,
"statement_count": len(metas),
@@ -272,6 +318,10 @@ def _process_job(job_id: str) -> None:
"account_number": next((meta.account_number for meta in metas if meta.account_number), ""),
"financial_year": job.financial_year or "",
"classification_enabled": job.classification_enabled,
"client_id": job.client_id,
"engagement_id": job.engagement_id,
"ownership_status": job.ownership_status,
"engagement_archive_status": job.engagement_archive_status,
}
# Keep the original uploaded statements until the job expiry time.
# This applies equally to completed and failed jobs and allows the