Files
arrr-erp/app/modules/accounting/ledger_learning_ui.py
T
2026-08-22 13:30:38 +05:30

178 lines
6.4 KiB
Python

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.historical_learning_service import active_natures, ledger_mappings
from app.modules.accounting.ledger_learning_service import (
available_tally_guids,
learning_summary,
rank_suggestions,
recent_events,
record_review,
)
from app.modules.accounting.ui import _find_visible_client, _require_partner, _visible_clients
from app.modules.clients.business_profile_service import get_profile
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
router = APIRouter(prefix="/tools/accounting/ledger-learning", tags=["accounting-ledger-learning-ui"])
def _float_or_none(value):
try:
return float(value) if str(value or "").strip() else None
except Exception:
return None
@router.get("")
def ledger_learning(
request: Request,
client_id: int | None = None,
tally_guid: str = "",
supplier_name: str = "",
supplier_gstin: str = "",
hsn_code: str = "",
description: str = "",
amount: str = "",
reviewed: int = 0,
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)
suggestions = []
mappings = []
tally_companies = []
profile = None
summary = []
events = []
if selected:
profile = get_profile(db, selected.id)
mappings = ledger_mappings(db, scope.tenant_id, selected.id, tally_guid)
tally_companies = available_tally_guids(db, scope.tenant_id, selected.id)
summary = learning_summary(db, scope.tenant_id, selected.id)
events = recent_events(db, scope.tenant_id, selected.id, 20)
if any([supplier_name.strip(), supplier_gstin.strip(), hsn_code.strip(), description.strip()]):
suggestions = rank_suggestions(
db,
tenant_id=scope.tenant_id,
client_id=selected.id,
tally_guid=tally_guid.strip(),
supplier_name=supplier_name,
supplier_gstin=supplier_gstin,
hsn_code=hsn_code,
description=description,
amount=_float_or_none(amount),
)
return templates.TemplateResponse("modules/accounting/templates/accounting/ledger_learning.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": "Self-improving Ledger Selection",
"clients": clients,
"selected_client": selected,
"profile": profile,
"tally_companies": tally_companies,
"mappings": mappings,
"natures": active_natures(db, scope.tenant_id),
"suggestions": suggestions,
"learning_summary": summary,
"events": events,
"query": {
"tally_guid": tally_guid,
"supplier_name": supplier_name,
"supplier_gstin": supplier_gstin,
"hsn_code": hsn_code,
"description": description,
"amount": amount,
},
"reviewed": bool(reviewed),
"error": error,
})
finally:
db.close()
@router.post("/review")
def review(
request: Request,
client_id: int = Form(...),
tally_guid: str = Form(""),
supplier_name: str = Form(""),
supplier_gstin: str = Form(""),
hsn_code: str = Form(""),
description: str = Form(""),
amount: str = Form(""),
suggested_nature_id: str = Form(""),
suggested_ledger_name: str = Form(""),
suggested_confidence: int = Form(0),
final_nature_id: int = Form(...),
final_ledger_name: str = Form(""),
explanation: str = Form(""),
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()
suggested_id = int(suggested_nature_id) if str(suggested_nature_id or "").isdigit() else None
explanations = [x.strip() for x in str(explanation or "").split("||") if x.strip()]
record_review(
db,
tenant_id=scope.tenant_id,
client_id=client.id,
tally_guid=tally_guid.strip(),
supplier_name=supplier_name,
supplier_gstin=supplier_gstin,
hsn_code=hsn_code,
description=description,
amount=_float_or_none(amount),
suggested_nature_id=suggested_id,
suggested_ledger_name=suggested_ledger_name,
suggested_confidence=suggested_confidence,
final_nature_id=final_nature_id,
final_ledger_name=final_ledger_name,
user_id=user.id,
explanation=explanations,
)
params = {
"client_id": client_id,
"tally_guid": tally_guid,
"supplier_name": supplier_name,
"supplier_gstin": supplier_gstin,
"hsn_code": hsn_code,
"description": description,
"amount": amount,
"reviewed": 1,
}
return RedirectResponse(url="/tools/accounting/ledger-learning?" + urlencode(params), status_code=303)
except Exception as exc:
db.rollback()
params = {"client_id": client_id, "error": str(exc)[:120]}
return RedirectResponse(url="/tools/accounting/ledger-learning?" + urlencode(params), status_code=303)
finally:
db.close()