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.purchase_posting_service import ( attempts_for_purchases, preflight_choices, queue_post, queue_preflight, sync_attempts, visible_workstations, ) from app.modules.accounting.purchase_review_service import review_queue 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 router = APIRouter(prefix="/tools/accounting/purchase-posting", tags=["accounting-purchase-posting-ui"]) def _redirect(client_id: int, *, message: str = "", error: str = "", page: int = 1, per_page: int = 25): params = {"client_id": client_id, "page": page, "per_page": per_page} if message: params["message"] = message[:240] if error: params["error"] = error[:240] return RedirectResponse( url="/tools/accounting/purchase-posting?" + urlencode(params), status_code=303, ) @router.get("") def page( request: Request, client_id: int | None = None, page: int = 1, per_page: int = 25, message: str = "", error: str = "", ): db = CommonSessionLocal() try: user, response = _require_partner(request, db, "accounting.learning.view") if response: return response 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) result = None attempts = {} choices = {} workstations = [] if selected: # Phase 10 intentionally starts only from reviewed purchases. result = review_queue( db, tenant_id=scope.tenant_id, client_id=selected.id, status="reviewed", page=page, per_page=per_page, ) ids = [row[0].id for row in result.rows] attempts = attempts_for_purchases( db, tenant_id=scope.tenant_id, client_id=selected.id, purchase_ids=ids ) sync_attempts(db, list(attempts.values())) attempts = attempts_for_purchases( db, tenant_id=scope.tenant_id, client_id=selected.id, purchase_ids=ids ) choices = { pid: preflight_choices(attempt) for pid, attempt in attempts.items() if attempt.preflight_result_json } branch_id = getattr(user, "branch_id", None) workstations = visible_workstations( db, tenant_id=scope.tenant_id, branch_id=branch_id ) return templates.TemplateResponse( "modules/accounting/templates/accounting/purchase_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": "Controlled Tally Purchase Posting", "clients": clients, "selected_client": selected, "result": result, "attempts": attempts, "choices": choices, "workstations": workstations, "message": message, "error": error, }, ) finally: db.close() @router.post("/purchase/{purchase_id}/preflight") def preflight( request: Request, purchase_id: int, client_id: int = Form(...), workstation_id: int = Form(...), page: int = Form(1), per_page: int = Form(25), csrf_token: str = Form(...), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_partner(request, db, "accounting.learning.manage") if response: return response client, _, scope = _find_visible_client(db, request, user, client_id) if not client: from app.core.http_responses import ui_access_denied return ui_access_denied() queue_preflight( db, tenant_id=scope.tenant_id, client_id=client.id, purchase_id=purchase_id, workstation_id=workstation_id, user_id=user.id, ) return _redirect( client.id, message="Workstation preflight queued. Refresh this page after the agent processes the job.", page=page, per_page=per_page, ) except Exception as exc: db.rollback() return _redirect(client_id, error=str(exc), page=page, per_page=per_page) finally: db.close() @router.post("/purchase/{purchase_id}/post") def post( request: Request, purchase_id: int, client_id: int = Form(...), party_ledger_name: str = Form(...), input_igst_ledger_name: str = Form(""), input_cgst_ledger_name: str = Form(""), input_sgst_ledger_name: str = Form(""), input_cess_ledger_name: str = Form(""), page: int = Form(1), per_page: int = Form(25), csrf_token: str = Form(...), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_partner(request, db, "accounting.learning.manage") if response: return response client, _, scope = _find_visible_client(db, request, user, client_id) if not client: from app.core.http_responses import ui_access_denied return ui_access_denied() queue_post( db, tenant_id=scope.tenant_id, client_id=client.id, purchase_id=purchase_id, party_ledger_name=party_ledger_name, input_igst_ledger_name=input_igst_ledger_name, input_cgst_ledger_name=input_cgst_ledger_name, input_sgst_ledger_name=input_sgst_ledger_name, input_cess_ledger_name=input_cess_ledger_name, user_id=user.id, ) return _redirect( client.id, message="Controlled Purchase voucher job queued. Refresh to see the workstation/Tally result.", page=page, per_page=per_page, ) except Exception as exc: db.rollback() return _redirect(client_id, error=str(exc), page=page, per_page=per_page) finally: db.close()