Add Phase 20 stored bank reuse and richer reconciliation
This commit is contained in:
@@ -11,9 +11,14 @@ from app.core.security.csrf import get_or_create_csrf_token, validate_csrf
|
||||
from app.core.templating import templates
|
||||
from app.modules.accounting.bank_reconciliation_service import (
|
||||
completed_client_jobs,
|
||||
list_bank_mappings,
|
||||
list_runs,
|
||||
queue_all_mapped_reconciliations,
|
||||
queue_reconciliation,
|
||||
resolve_reconciliation_item,
|
||||
run_items,
|
||||
save_bank_mapping,
|
||||
source_accounts,
|
||||
visible_bank_ledgers,
|
||||
visible_workstations,
|
||||
)
|
||||
@@ -91,6 +96,9 @@ def page(
|
||||
runs = []
|
||||
items = []
|
||||
selected_run = None
|
||||
accounts = []
|
||||
mappings = []
|
||||
run_summaries = {}
|
||||
|
||||
if selected:
|
||||
jobs = completed_client_jobs(
|
||||
@@ -99,6 +107,13 @@ def page(
|
||||
client_id=selected.id,
|
||||
limit=50,
|
||||
)
|
||||
if job_id:
|
||||
accounts = source_accounts(
|
||||
db,
|
||||
tenant_id=scope.tenant_id,
|
||||
client_id=selected.id,
|
||||
job_id=job_id,
|
||||
)
|
||||
companies = _company_options(db, scope.tenant_id, selected.id)
|
||||
if not tally_guid and len(companies) == 1:
|
||||
tally_guid = companies[0]["guid"]
|
||||
@@ -109,6 +124,12 @@ def page(
|
||||
client_id=selected.id,
|
||||
tally_guid=tally_guid,
|
||||
)
|
||||
mappings = list_bank_mappings(
|
||||
db,
|
||||
tenant_id=scope.tenant_id,
|
||||
client_id=selected.id,
|
||||
tally_guid=tally_guid,
|
||||
)
|
||||
workstations = visible_workstations(
|
||||
db,
|
||||
tenant_id=scope.tenant_id,
|
||||
@@ -136,6 +157,12 @@ def page(
|
||||
status=status,
|
||||
)
|
||||
|
||||
for row in runs:
|
||||
try:
|
||||
run_summaries[int(row.id)] = json.loads(row.summary_json or "{}")
|
||||
except Exception:
|
||||
run_summaries[int(row.id)] = {}
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"modules/accounting/templates/accounting/bank_reconciliation.html",
|
||||
{
|
||||
@@ -157,6 +184,9 @@ def page(
|
||||
"selected_run": selected_run,
|
||||
"items": items,
|
||||
"status_filter": status,
|
||||
"source_accounts": accounts,
|
||||
"bank_mappings": mappings,
|
||||
"run_summaries": run_summaries,
|
||||
"message": message,
|
||||
"error": error,
|
||||
},
|
||||
@@ -173,6 +203,8 @@ def create_run(
|
||||
tally_guid: str = Form(...),
|
||||
company_name: str = Form(...),
|
||||
bank_ledger_name: str = Form(...),
|
||||
account_number: str = Form(""),
|
||||
date_tolerance_days: int = Form(15),
|
||||
workstation_id: int = Form(...),
|
||||
csrf_token: str = Form(...),
|
||||
):
|
||||
@@ -197,6 +229,8 @@ def create_run(
|
||||
bank_ledger_name=bank_ledger_name,
|
||||
workstation_id=workstation_id,
|
||||
user_id=user.id,
|
||||
account_number=account_number,
|
||||
date_tolerance_days=date_tolerance_days,
|
||||
)
|
||||
return _go(
|
||||
client.id,
|
||||
@@ -216,3 +250,153 @@ def create_run(
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/mapping")
|
||||
def save_mapping(
|
||||
request: Request,
|
||||
client_id: int = Form(...),
|
||||
source_job_id: str = Form(""),
|
||||
tally_guid: str = Form(...),
|
||||
company_name: str = Form(...),
|
||||
account_number: str = Form(...),
|
||||
bank_name: str = Form(""),
|
||||
bank_ledger_name: str = Form(...),
|
||||
csrf_token: str = Form(...),
|
||||
):
|
||||
validate_csrf(request, csrf_token)
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user, denied = _require_partner(request, db, "accounting.learning.manage")
|
||||
if denied:
|
||||
return denied
|
||||
client, _clients, scope = _find_visible_client(db, request, user, client_id)
|
||||
if not client:
|
||||
return _go(error="Client is not visible.")
|
||||
|
||||
save_bank_mapping(
|
||||
db,
|
||||
tenant_id=scope.tenant_id,
|
||||
client_id=client.id,
|
||||
account_number=account_number,
|
||||
bank_name=bank_name,
|
||||
tally_guid=tally_guid,
|
||||
company_name=company_name,
|
||||
bank_ledger_name=bank_ledger_name,
|
||||
user_id=user.id,
|
||||
)
|
||||
return _go(
|
||||
client.id,
|
||||
job_id=source_job_id,
|
||||
tally_guid=tally_guid,
|
||||
message=f"Bank account {account_number} mapped to Tally ledger '{bank_ledger_name}'.",
|
||||
)
|
||||
except Exception as exc:
|
||||
db.rollback()
|
||||
return _go(client_id, job_id=source_job_id, tally_guid=tally_guid, error=str(exc))
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/run-all")
|
||||
def create_all_runs(
|
||||
request: Request,
|
||||
client_id: int = Form(...),
|
||||
source_job_id: str = Form(...),
|
||||
tally_guid: str = Form(...),
|
||||
company_name: str = Form(...),
|
||||
date_tolerance_days: int = Form(15),
|
||||
workstation_id: int = Form(...),
|
||||
csrf_token: str = Form(...),
|
||||
):
|
||||
validate_csrf(request, csrf_token)
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user, denied = _require_partner(request, db, "accounting.learning.manage")
|
||||
if denied:
|
||||
return denied
|
||||
client, _clients, scope = _find_visible_client(db, request, user, client_id)
|
||||
if not client:
|
||||
return _go(error="Client is not visible.")
|
||||
|
||||
runs = queue_all_mapped_reconciliations(
|
||||
db,
|
||||
tenant_id=scope.tenant_id,
|
||||
client_id=client.id,
|
||||
source_job_id=source_job_id,
|
||||
tally_guid=tally_guid,
|
||||
company_name=company_name,
|
||||
workstation_id=workstation_id,
|
||||
user_id=user.id,
|
||||
date_tolerance_days=date_tolerance_days,
|
||||
)
|
||||
return _go(
|
||||
client.id,
|
||||
job_id=source_job_id,
|
||||
tally_guid=tally_guid,
|
||||
run_id=(runs[0].id if runs else None),
|
||||
message=f"Queued {len(runs)} reconciliation run(s), one for each mapped bank account.",
|
||||
)
|
||||
except Exception as exc:
|
||||
db.rollback()
|
||||
return _go(client_id, job_id=source_job_id, tally_guid=tally_guid, error=str(exc))
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/runs/{run_id}/items/{item_id}/resolve")
|
||||
def resolve_item(
|
||||
request: Request,
|
||||
run_id: int,
|
||||
item_id: int,
|
||||
client_id: int = Form(...),
|
||||
tally_guid: str = Form(""),
|
||||
action: str = Form(...),
|
||||
note: str = Form(""),
|
||||
csrf_token: str = Form(...),
|
||||
):
|
||||
validate_csrf(request, csrf_token)
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user, denied = _require_partner(request, db, "accounting.learning.manage")
|
||||
if denied:
|
||||
return denied
|
||||
client, _clients, scope = _find_visible_client(db, request, user, client_id)
|
||||
if not client:
|
||||
return _go(error="Client is not visible.")
|
||||
|
||||
run = next(
|
||||
(
|
||||
row
|
||||
for row in list_runs(
|
||||
db,
|
||||
tenant_id=scope.tenant_id,
|
||||
client_id=client.id,
|
||||
limit=100,
|
||||
)
|
||||
if int(row.id) == int(run_id)
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not run:
|
||||
raise ValueError("Reconciliation run was not found.")
|
||||
|
||||
resolve_reconciliation_item(
|
||||
db,
|
||||
run_id=run.id,
|
||||
item_id=item_id,
|
||||
action=action,
|
||||
note=note,
|
||||
user_id=user.id,
|
||||
)
|
||||
return _go(
|
||||
client.id,
|
||||
tally_guid=(tally_guid or run.tally_guid),
|
||||
run_id=run.id,
|
||||
message="Reconciliation review decision saved.",
|
||||
)
|
||||
except Exception as exc:
|
||||
db.rollback()
|
||||
return _go(client_id, tally_guid=tally_guid, run_id=run_id, error=str(exc))
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
Reference in New Issue
Block a user