from __future__ import annotations import json from datetime import datetime, timedelta, timezone from sqlalchemy import select from app.modules.documents.models import ERPWorkstationAgent ONLINE_WINDOW = timedelta(minutes=5) def workstation_companies(workstation) -> list[dict]: try: rows = json.loads(workstation.tally_companies_json or "[]") except Exception: rows = [] if not isinstance(rows, list): return [] result = [] seen = set() for row in rows: if not isinstance(row, dict): continue guid = str(row.get("guid") or "").strip() name = str(row.get("name") or "").strip() if not guid or guid in seen: continue seen.add(guid) result.append( { "guid": guid, "name": name, "gstin": str(row.get("gstin") or "").strip(), "gstins": list(row.get("gstins") or []), } ) return result def workstation_is_recent(workstation) -> bool: seen = getattr(workstation, "last_seen_at_utc", None) if not seen: return False if seen.tzinfo is None: seen = seen.replace(tzinfo=timezone.utc) return datetime.now(timezone.utc) - seen <= ONLINE_WINDOW def eligible_tally_workstations(db, tenant_id: int, branch_id: int | None = None): stmt = select(ERPWorkstationAgent).where( ERPWorkstationAgent.tenant_id == int(tenant_id), ERPWorkstationAgent.is_active.is_(True), ERPWorkstationAgent.tally_connected.is_(True), ) if branch_id is not None: stmt = stmt.where(ERPWorkstationAgent.branch_id == int(branch_id)) rows = list( db.execute( stmt.order_by( ERPWorkstationAgent.last_seen_at_utc.desc(), ERPWorkstationAgent.machine_name.asc(), ) ).scalars().all() ) return [ row for row in rows if workstation_is_recent(row) and workstation_companies(row) ] def resolve_workstation_company( db, *, workstation_id: int, tenant_id: int, branch_id: int | None, tally_guid: str, ): workstation = db.get(ERPWorkstationAgent, int(workstation_id)) if ( not workstation or int(workstation.tenant_id) != int(tenant_id) or not workstation.is_active or not workstation.tally_connected or not workstation_is_recent(workstation) or ( branch_id is not None and int(workstation.branch_id or 0) != int(branch_id) ) ): raise ValueError( "Selected ERP Local Agent workstation is offline, stale, or not connected to Tally." ) guid = str(tally_guid or "").strip() company = next( ( row for row in workstation_companies(workstation) if str(row.get("guid") or "").strip() == guid ), None, ) if not company: raise ValueError( "Selected Tally company is not currently advertised by this workstation. " "Open the company in TallyPrime and allow the Local Agent to refresh." ) return workstation, company