Add Phase 11B client aware multi bank ownership and engagement archive
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user