Add Phase 11 bank to Tally with multi-bank contra intelligence
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
from __future__ import annotations
|
||||
|
||||
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_service import (
|
||||
confirm_review, import_completed_job, preflight_choices, queue_post, queue_preflight,
|
||||
queue_rows, sync_posting, visible_workstations,
|
||||
)
|
||||
from app.modules.accounting.ledger_learning_service import active_natures
|
||||
from app.modules.accounting.ui import _find_visible_client, _require_partner, _visible_clients
|
||||
from app.modules.bank_statement_analyzer.models import BankStatementAnalysisJob
|
||||
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
|
||||
from sqlalchemy import select
|
||||
|
||||
router = APIRouter(prefix="/tools/accounting/bank-posting", tags=["accounting-bank-posting-ui"])
|
||||
|
||||
|
||||
def _go(client_id=0, message="", error=""):
|
||||
q = {"client_id": client_id} if client_id else {}
|
||||
if message: q["message"] = message[:250]
|
||||
if error: q["error"] = error[:250]
|
||||
return RedirectResponse("/tools/accounting/bank-posting" + ("?" + urlencode(q) if q else ""), status_code=303)
|
||||
|
||||
|
||||
@router.get("")
|
||||
def page(request: Request, client_id: int | None = None, status: str = "", page: int = 1, per_page: int = 25, 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)
|
||||
rows = []; total = 0; pages = 1
|
||||
choices = {}; workstations = []
|
||||
if selected:
|
||||
rows, total, page, pages = queue_rows(db, tenant_id=scope.tenant_id, client_id=selected.id, status=status, page=page, per_page=per_page)
|
||||
for row in rows:
|
||||
sync_posting(db, row)
|
||||
choices = {r.id: preflight_choices(r) for r in rows if r.preflight_result_json}
|
||||
workstations = visible_workstations(db, scope.tenant_id, getattr(user, "branch_id", None))
|
||||
jobs = list(db.execute(select(BankStatementAnalysisJob).where(
|
||||
BankStatementAnalysisJob.user_id == user.id,
|
||||
BankStatementAnalysisJob.status == "completed",
|
||||
).order_by(BankStatementAnalysisJob.completed_at_utc.desc()).limit(30)).scalars().all())
|
||||
return templates.TemplateResponse("modules/accounting/templates/accounting/bank_posting.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 to Tally", "clients": clients, "selected_client": selected,
|
||||
"rows": rows, "total": total, "page": page, "pages": pages, "per_page": per_page,
|
||||
"status_filter": status, "jobs": jobs, "natures": active_natures(db, scope.tenant_id),
|
||||
"choices": choices, "workstations": workstations, "message": message, "error": error,
|
||||
})
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/import")
|
||||
def import_job(request: Request, client_id: int = Form(...), job_id: 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, _, scope = _find_visible_client(db, request, user, client_id)
|
||||
if not client: return _go(error="Client is not visible.")
|
||||
added, skipped = import_completed_job(db, tenant_id=scope.tenant_id, client_id=client.id, job_id=job_id, user_id=user.id)
|
||||
return _go(client.id, message=f"Imported {added} bank transaction(s); {skipped} duplicate fingerprint(s) skipped.")
|
||||
except Exception as exc:
|
||||
db.rollback(); return _go(client_id, error=str(exc))
|
||||
finally: db.close()
|
||||
|
||||
|
||||
@router.post("/{tx_id}/review")
|
||||
def review(request: Request, tx_id: int, client_id: int = Form(...), nature_id: int | None = Form(None), ledger_name: str = Form(""), voucher_type: str = Form(...), review_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, _, scope = _find_visible_client(db, request, user, client_id)
|
||||
confirm_review(db, tx_id=tx_id, tenant_id=scope.tenant_id, client_id=client.id, nature_id=nature_id, ledger_name=ledger_name, voucher_type=voucher_type, user_id=user.id, note=review_note)
|
||||
return _go(client.id, message="Bank transaction review saved.")
|
||||
except Exception as exc:
|
||||
db.rollback(); return _go(client_id, error=str(exc))
|
||||
finally: db.close()
|
||||
|
||||
|
||||
@router.post("/{tx_id}/preflight")
|
||||
def preflight(request: Request, tx_id: int, client_id: int = 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, _, scope = _find_visible_client(db, request, user, client_id)
|
||||
queue_preflight(db, tx_id=tx_id, tenant_id=scope.tenant_id, client_id=client.id, workstation_id=workstation_id, user_id=user.id)
|
||||
return _go(client.id, message="Bank posting preflight queued.")
|
||||
except Exception as exc:
|
||||
db.rollback(); return _go(client_id, error=str(exc))
|
||||
finally: db.close()
|
||||
|
||||
|
||||
@router.post("/{tx_id}/post")
|
||||
def post(request: Request, tx_id: int, client_id: int = Form(...), bank_ledger_name: str = Form(...), counter_ledger_name: str = Form(""), other_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, _, scope = _find_visible_client(db, request, user, client_id)
|
||||
queue_post(db, tx_id=tx_id, tenant_id=scope.tenant_id, client_id=client.id, bank_ledger_name=bank_ledger_name, counter_ledger_name=counter_ledger_name, other_bank_ledger_name=other_bank_ledger_name, user_id=user.id)
|
||||
return _go(client.id, message="Controlled bank voucher job queued.")
|
||||
except Exception as exc:
|
||||
db.rollback(); return _go(client_id, error=str(exc))
|
||||
finally: db.close()
|
||||
Reference in New Issue
Block a user