Files
arrr-erp/app/modules/accounting/bank_reconciliation_ui.py
T
2026-08-24 15:23:49 +05:30

219 lines
6.7 KiB
Python

from __future__ import annotations
import json
from urllib.parse import urlencode
from fastapi import APIRouter, Form, Request
from fastapi.responses import RedirectResponse
from app.core.db.common import CommonSessionLocal
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_runs,
queue_reconciliation,
run_items,
visible_bank_ledgers,
visible_workstations,
)
from app.modules.accounting.chart_models import AccountingChartLedger
from app.modules.accounting.ui import _find_visible_client, _require_partner, _visible_clients
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
from sqlalchemy import select
router = APIRouter(
prefix="/tools/accounting/bank-reconciliation",
tags=["accounting-bank-reconciliation-ui"],
)
def _go(client_id=0, message="", error="", **extra):
params = {}
if client_id:
params["client_id"] = client_id
if message:
params["message"] = message[:350]
if error:
params["error"] = error[:350]
for key, value in extra.items():
if value not in ("", None):
params[key] = value
return RedirectResponse(
"/tools/accounting/bank-reconciliation"
+ ("?" + urlencode(params) if params else ""),
status_code=303,
)
def _company_options(db, tenant_id, client_id):
rows = list(
db.execute(
select(AccountingChartLedger.tally_guid, AccountingChartLedger.company_name)
.where(
AccountingChartLedger.tenant_id == int(tenant_id),
AccountingChartLedger.client_id == int(client_id),
)
.distinct()
.order_by(AccountingChartLedger.company_name)
).all()
)
return [{"guid": guid, "name": name or guid} for guid, name in rows if guid]
@router.get("")
def page(
request: Request,
client_id: int | None = None,
job_id: str = "",
tally_guid: str = "",
run_id: int | None = None,
status: str = "",
message: str = "",
error: str = "",
):
db = CommonSessionLocal()
try:
user, denied = _require_partner(request, db, "accounting.learning.view")
if denied:
return denied
clients, scope = _visible_clients(db, request, user)
selected = next(
(c for c in clients if client_id and int(c.id) == int(client_id)),
None,
)
jobs = []
companies = []
bank_ledgers = []
workstations = []
runs = []
items = []
selected_run = None
if selected:
jobs = completed_client_jobs(
db,
tenant_id=scope.tenant_id,
client_id=selected.id,
limit=50,
)
companies = _company_options(db, scope.tenant_id, selected.id)
if not tally_guid and len(companies) == 1:
tally_guid = companies[0]["guid"]
if tally_guid:
bank_ledgers = visible_bank_ledgers(
db,
tenant_id=scope.tenant_id,
client_id=selected.id,
tally_guid=tally_guid,
)
workstations = visible_workstations(
db,
tenant_id=scope.tenant_id,
branch_id=getattr(user, "branch_id", None),
)
runs = list_runs(
db,
tenant_id=scope.tenant_id,
client_id=selected.id,
limit=30,
)
if run_id:
selected_run = next(
(row for row in runs if int(row.id) == int(run_id)),
None,
)
elif runs:
selected_run = runs[0]
if selected_run and selected_run.status == "completed":
items = run_items(
db,
run_id=selected_run.id,
status=status,
)
return templates.TemplateResponse(
"modules/accounting/templates/accounting/bank_reconciliation.html",
{
"request": request,
"current_user": user,
"current_user_roles": get_user_roles(db, user.id),
"current_user_permissions": get_user_permissions(db, user.id),
"csrf_token": get_or_create_csrf_token(request),
"title": "Bank Reconciliation",
"clients": clients,
"selected_client": selected,
"jobs": jobs,
"selected_job_id": job_id,
"companies": companies,
"selected_tally_guid": tally_guid,
"bank_ledgers": bank_ledgers,
"workstations": workstations,
"runs": runs,
"selected_run": selected_run,
"items": items,
"status_filter": status,
"message": message,
"error": error,
},
)
finally:
db.close()
@router.post("/run")
def create_run(
request: Request,
client_id: int = Form(...),
source_job_id: str = Form(...),
tally_guid: str = Form(...),
company_name: str = Form(...),
bank_ledger_name: str = Form(...),
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.")
run = queue_reconciliation(
db,
tenant_id=scope.tenant_id,
client_id=client.id,
source_job_id=source_job_id,
tally_guid=tally_guid,
company_name=company_name,
bank_ledger_name=bank_ledger_name,
workstation_id=workstation_id,
user_id=user.id,
)
return _go(
client.id,
run_id=run.id,
tally_guid=tally_guid,
message=(
f"Bank reconciliation run #{run.id} queued. "
"The Local Agent will read the selected Tally bank ledger; no voucher is posted."
),
)
except Exception as exc:
db.rollback()
return _go(
client_id,
tally_guid=tally_guid,
error=str(exc),
)
finally:
db.close()