Add Phase 19 bank accounting and reconciliation workflow

This commit is contained in:
A R R R Associates
2026-08-24 15:23:49 +05:30
parent 67ebd587e9
commit 769add8cb8
17 changed files with 1363 additions and 10 deletions
+50 -1
View File
@@ -162,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, client_id: int | None = None, engagement_id: int | None = None, ownership_confirmation: bool = False) -> 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, purpose: str = "analyze_only") -> 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.")
@@ -186,6 +186,12 @@ def enqueue_job(*, user, roles: Iterable[str], job_id: str, paths: list[Path], j
customer_override=(customer_override or "").strip() or None,
account_override=(account_override or "").strip() or None,
classification_enabled=bool(classification_enabled),
purpose=(purpose if purpose in {"analyze_only", "accounting_entries", "bank_reconciliation"} else "analyze_only"),
accounting_import_status=(
"pending"
if purpose in {"accounting_entries", "bank_reconciliation"}
else "not_requested"
),
status="queued",
progress_percent=0,
file_count=len(paths),
@@ -333,6 +339,46 @@ def _process_job(job_id: str) -> None:
job.completed_at_utc = _now()
job.expires_at_utc = _now() + timedelta(hours=COMPLETED_RETENTION_HOURS)
db.commit()
# Phase 19 reuses the existing AccountingBankTransaction import path.
# Analyze-only remains unchanged. Accounting/Reconciliation jobs import
# only after ownership has passed and the workbook has completed.
if job.purpose in {"accounting_entries", "bank_reconciliation"}:
try:
if not job.client_id:
raise ValueError(
"Accounting / Bank Reconciliation purpose requires an ERP client."
)
from app.modules.accounting.bank_service import import_completed_job
added, skipped = import_completed_job(
db,
tenant_id=int(job.tenant_id or 0),
client_id=int(job.client_id),
job_id=job.id,
user_id=int(job.user_id),
)
job = db.get(BankStatementAnalysisJob, job.id)
job.accounting_import_status = "completed"
job.accounting_import_json = json.dumps(
{
"added": int(added),
"skipped_duplicate_fingerprints": int(skipped),
"purpose": job.purpose,
},
ensure_ascii=False,
)
db.commit()
except Exception as import_exc:
db.rollback()
job = db.get(BankStatementAnalysisJob, job_id)
if job:
job.accounting_import_status = "failed"
job.accounting_import_json = json.dumps(
{"error": str(import_exc)[:2000]},
ensure_ascii=False,
)
db.commit()
except Exception as exc:
db.rollback()
job = db.get(BankStatementAnalysisJob, job_id)
@@ -481,6 +527,9 @@ def job_view(job: BankStatementAnalysisJob) -> dict:
"file_count": job.file_count,
"selected_bank": job.selected_bank,
"financial_year": job.financial_year or "",
"purpose": getattr(job, "purpose", "analyze_only"),
"accounting_import_status": getattr(job, "accounting_import_status", "not_requested"),
"accounting_import": json.loads(job.accounting_import_json) if getattr(job, "accounting_import_json", None) else {},
"submitted_at": job.submitted_at_utc,
"completed_at": job.completed_at_utc,
"expires_at": job.expires_at_utc,