131 lines
7.6 KiB
Python
131 lines
7.6 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import date, datetime, timezone
|
|
|
|
from fastapi import APIRouter, Form, Request
|
|
from fastapi.responses import RedirectResponse
|
|
from sqlalchemy import select
|
|
|
|
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_models import AccountingHistoricalLearningRun
|
|
from app.modules.accounting.historical_learning_service import active_natures, classifiable_ledgers, evidence_rows, ingest_completed_run, ledger_mappings, party_suggestions, remove_ledger_mapping, save_ledger_mapping
|
|
from app.modules.accounting.ui import _accounting_storage_payload, _find_visible_client, _require_partner, _visible_clients
|
|
from app.modules.accounting.historical_sync_support import eligible_tally_workstations, resolve_workstation_company, workstation_companies
|
|
from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
|
|
from app.modules.core.rbac.permission_guard import require_permission
|
|
from app.modules.documents.agent_jobs import enqueue_agent_job
|
|
from app.modules.documents.models import ERPAgentJob, ERPWorkstationAgent
|
|
|
|
router = APIRouter(prefix="/tools/accounting/historical-learning", tags=["accounting-historical-learning-ui"])
|
|
|
|
|
|
def _denied():
|
|
from app.core.http_responses import ui_access_denied
|
|
return ui_access_denied()
|
|
|
|
|
|
def _fy_dates():
|
|
today=date.today(); y=today.year if today.month>=4 else today.year-1
|
|
return date(y,4,1).isoformat(), today.isoformat()
|
|
|
|
|
|
|
|
def _sync_runs(db, tenant_id: int, client_id: int):
|
|
runs=list(db.execute(select(AccountingHistoricalLearningRun).where(AccountingHistoricalLearningRun.tenant_id==tenant_id, AccountingHistoricalLearningRun.client_id==client_id).order_by(AccountingHistoricalLearningRun.id.desc()).limit(10)).scalars().all())
|
|
for run in runs:
|
|
if run.status not in {"completed","failed"}: ingest_completed_run(db, run)
|
|
return runs
|
|
|
|
|
|
@router.get("")
|
|
def historical_learning(request: Request, client_id: int | None=None, collected: int=0, mapped: 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)
|
|
runs=[]; evid=[]; ledgers=[]; suggestions=[]; mappings=[]
|
|
workstations=eligible_tally_workstations(db, scope.tenant_id, scope.branch_id)
|
|
if selected:
|
|
runs=_sync_runs(db,scope.tenant_id,selected.id)
|
|
evid=evidence_rows(db,scope.tenant_id,selected.id)
|
|
ledgers=classifiable_ledgers(db,scope.tenant_id,selected.id)
|
|
suggestions=party_suggestions(db,scope.tenant_id,selected.id)
|
|
mappings=ledger_mappings(db,scope.tenant_id,selected.id)
|
|
df,dt=_fy_dates()
|
|
return templates.TemplateResponse("modules/accounting/templates/accounting/historical_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":"Historical Tally Learning","clients":clients,"selected_client":selected,"workstations":workstations,"workstation_companies":{w.id:workstation_companies(w) for w in workstations},
|
|
"runs":runs,"evidence":evid,"classifiable_ledgers":ledgers,"suggestions":suggestions,"mappings":mappings,"natures":active_natures(db,scope.tenant_id),
|
|
"date_from":df,"date_to":dt,"collected":bool(collected),"mapped":bool(mapped),"error":error,
|
|
})
|
|
finally: db.close()
|
|
|
|
|
|
@router.post("/collect")
|
|
def collect(request: Request, client_id: int=Form(...), workstation_id: int=Form(...), tally_guid: str=Form(...), company_name: str=Form(""), date_from: str=Form(""), date_to: 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:return _denied()
|
|
guid=str(tally_guid or "").strip()
|
|
if not guid:
|
|
return RedirectResponse(url=f"/tools/accounting/historical-learning?client_id={client_id}&error=company_required",status_code=303)
|
|
workstation, company = resolve_workstation_company(
|
|
db,
|
|
workstation_id=workstation_id,
|
|
tenant_id=scope.tenant_id,
|
|
branch_id=scope.branch_id,
|
|
tally_guid=guid,
|
|
)
|
|
payload=_accounting_storage_payload(client); payload.update({
|
|
"tenant_id":scope.tenant_id,
|
|
"requested_by_user_id":user.id,
|
|
"tally_guid":guid,
|
|
"company_name":str(company.get("name") or company_name or "").strip(),
|
|
"date_from":date_from,
|
|
"date_to":date_to,
|
|
"voucher_scope":"purchase",
|
|
"refresh_transactions":True,
|
|
})
|
|
job=enqueue_agent_job(db,workstation_agent_id=workstation.id,action="accounting_historical_evidence",payload=payload,priority=6,max_attempts=2,created_by_user_id=user.id)
|
|
db.flush()
|
|
run=AccountingHistoricalLearningRun(tenant_id=scope.tenant_id,client_id=client.id,workstation_agent_id=workstation.id,agent_job_id=job.id,tally_guid=guid,company_name=str(company.get("name") or company_name or ""),date_from=date_from or None,date_to=date_to or None,status="queued",requested_by_user_id=user.id)
|
|
db.add(run); db.commit()
|
|
return RedirectResponse(url=f"/tools/accounting/historical-learning?client_id={client_id}&collected=1",status_code=303)
|
|
except Exception as exc:
|
|
db.rollback(); return RedirectResponse(url=f"/tools/accounting/historical-learning?client_id={client_id}&error={str(exc)[:120]}",status_code=303)
|
|
finally: db.close()
|
|
|
|
|
|
@router.post("/map-ledger")
|
|
def map_ledger(request: Request, client_id: int=Form(...), tally_guid: str=Form(...), company_name: str=Form(""), ledger_name: str=Form(...), parent_group: str=Form(""), nature_id: int=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:return _denied()
|
|
save_ledger_mapping(db,tenant_id=scope.tenant_id,client_id=client.id,tally_guid=tally_guid,company_name=company_name,ledger_name=ledger_name,parent_group=parent_group,nature_id=nature_id,user_id=user.id)
|
|
return RedirectResponse(url=f"/tools/accounting/historical-learning?client_id={client_id}&mapped=1",status_code=303)
|
|
finally: db.close()
|
|
|
|
|
|
@router.post("/unmap-ledger")
|
|
def unmap_ledger(request: Request, client_id: int=Form(...), mapping_id: int=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:return _denied()
|
|
remove_ledger_mapping(db,tenant_id=scope.tenant_id,client_id=client.id,mapping_id=mapping_id)
|
|
return RedirectResponse(url=f"/tools/accounting/historical-learning?client_id={client_id}",status_code=303)
|
|
finally: db.close()
|