from __future__ import annotations from fastapi import APIRouter, Request from sqlalchemy import select from app.core.db.common import CommonSessionLocal from app.core.security.csrf import get_or_create_csrf_token from app.core.templating import templates from app.modules.accounting.sales_learning_models import AccountingSalesHistoricalRun from app.modules.accounting.sales_learning_service import historical_sales_rows, ingest_completed_sales_run, learning_summary, sales_mappings from app.modules.accounting.ui import _require_partner, _visible_clients from app.modules.core.rbac.deps import get_user_permissions, get_user_roles router = APIRouter(prefix="/tools/accounting", tags=["accounting-sales-menu-ui"]) def _context(db, request, user, title, client_id): 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) return selected, scope, { "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": title, "clients": clients, "selected_client": selected, } def _sync_runs(db, tenant_id, client_id): runs = list(db.execute(select(AccountingSalesHistoricalRun).where( AccountingSalesHistoricalRun.tenant_id == int(tenant_id), AccountingSalesHistoricalRun.client_id == int(client_id), ).order_by(AccountingSalesHistoricalRun.id.desc()).limit(20)).scalars().all()) for run in runs: if run.status not in {"completed", "failed"}: ingest_completed_sales_run(db, run) return runs @router.get("/customer-intelligence") def customer_intelligence(request: Request, client_id: int | None = None): db = CommonSessionLocal() try: user, denied = _require_partner(request, db, "accounting.learning.view") if denied: return denied selected, scope, ctx = _context(db, request, user, "Customer Intelligence", client_id) mappings = [] summary = {"mapping_count": 0, "historical_rows": 0, "review_confirmed": 0, "high_confidence": 0} if selected: mappings = sales_mappings(db, tenant_id=scope.tenant_id, client_id=selected.id) summary = learning_summary(db, tenant_id=scope.tenant_id, client_id=selected.id) ctx.update(mappings=mappings, summary=summary) return templates.TemplateResponse("modules/accounting/templates/accounting/customer_intelligence.html", ctx) finally: db.close() @router.get("/historical-sales-learning") def historical_sales_learning(request: Request, client_id: int | None = None): db = CommonSessionLocal() try: user, denied = _require_partner(request, db, "accounting.learning.view") if denied: return denied selected, scope, ctx = _context(db, request, user, "Historical Sales Learning", client_id) evidence, runs = [], [] summary = {"mapping_count": 0, "historical_rows": 0, "review_confirmed": 0, "high_confidence": 0} if selected: runs = _sync_runs(db, scope.tenant_id, selected.id) evidence = historical_sales_rows(db, tenant_id=scope.tenant_id, client_id=selected.id)[:500] summary = learning_summary(db, tenant_id=scope.tenant_id, client_id=selected.id) ctx.update(evidence=evidence, runs=runs, summary=summary) return templates.TemplateResponse("modules/accounting/templates/accounting/historical_sales_learning.html", ctx) finally: db.close() @router.get("/sales-ledger-learning") def sales_ledger_learning(request: Request, client_id: int | None = None): db = CommonSessionLocal() try: user, denied = _require_partner(request, db, "accounting.learning.view") if denied: return denied selected, scope, ctx = _context(db, request, user, "Sales Ledger Learning", client_id) mappings, evidence = [], [] summary = {"mapping_count": 0, "historical_rows": 0, "review_confirmed": 0, "high_confidence": 0} if selected: _sync_runs(db, scope.tenant_id, selected.id) mappings = sales_mappings(db, tenant_id=scope.tenant_id, client_id=selected.id) evidence = historical_sales_rows(db, tenant_id=scope.tenant_id, client_id=selected.id)[:500] summary = learning_summary(db, tenant_id=scope.tenant_id, client_id=selected.id) hist = {} for e in evidence: hist.setdefault(str(e.party_ledger_name or '').strip().casefold(), []).append(e) rows = [] for m in mappings: key = str(m.tally_customer_ledger_name or m.customer_name or '').strip().casefold() h = hist.get(key, []) rows.append({"mapping": m, "history": h[0] if h else None, "history_count": len(h)}) ctx.update(learning_rows=rows, summary=summary) return templates.TemplateResponse("modules/accounting/templates/accounting/sales_ledger_learning.html", ctx) finally: db.close()