Fix cash payment Tally company resolution

This commit is contained in:
A R R R Associates
2026-09-04 19:42:58 +05:30
parent 1c9be6bd4a
commit deeebc01be
4 changed files with 62 additions and 31 deletions
+1 -1
View File
@@ -252,7 +252,7 @@ def cash_payment_review(
@router.post("/ledgers")
async def cash_payment_ledgers(request: Request):
"""Load Cash ledgers using the same live Tally master snapshot path as Opening Balance Review."""
"""Load Cash ledgers using the same name-resolved live Tally master snapshot path as Opening Balance Review."""
form = await request.form()
validate_csrf(request, str(form.get("csrf_token") or ""))
db = CommonSessionLocal()
+1 -1
View File
@@ -4,7 +4,7 @@ import io
from pathlib import Path
import zipfile
ERP_LOCAL_AGENT_VERSION = "1.22.19"
ERP_LOCAL_AGENT_VERSION = "1.22.20"
ERP_LOCAL_AGENT_NAME = "ERP Local Agent"
RUNTIME_ROOT = Path(__file__).resolve().parent / "local_agent_runtime"
_DETERMINISTIC_ZIP_TIMESTAMP = (2026, 1, 1, 0, 0, 0)
@@ -1,2 +1,2 @@
__version__ = "1.22.19"
__version__ = "1.22.20"
AGENT_NAME = "ERP Local Agent"
@@ -429,6 +429,28 @@ class AgentCommandProcessor:
if len(matches)!=1: raise ValueError(f"Tally company '{wanted}' is not uniquely loaded. Open both previous/current companies in TallyPrime first.")
return matches[0]
def _cash_payment_selected_company(self, payload: dict[str, Any]):
"""Resolve Cash Payment company exactly the same way as Opening Balance Review.
The visible company name selected by the user is authoritative for this
read-only review. Tally can report a different CURRENTCOMPANY while
multiple companies are open, so Cash Payment must not veto the selection
merely because that global/current-company value differs. The selected
company's real GUID is returned for cache identity once the name has been
resolved from Tally's loaded-company list.
"""
company_name = str(payload.get("company_name") or "").strip()
company = self._opening_balance_company(company_name)
selected_guid = str(payload.get("tally_guid") or "").strip()
actual_guid = str(company.guid or "").strip()
guid_changed = bool(selected_guid and actual_guid and selected_guid != actual_guid)
if guid_changed:
self.logger.warning(
"Cash Payment selected-company GUID refreshed by name: selected=%s actual=%s company=%s",
selected_guid, actual_guid, company.name,
)
return company, (actual_guid or selected_guid), guid_changed
def _opening_balance_snapshot(self, payload: dict[str, Any]) -> dict[str, Any]:
previous=self._opening_balance_company(str(payload.get("previous_company_name") or ""))
current=self._opening_balance_company(str(payload.get("current_company_name") or ""))
@@ -746,15 +768,11 @@ class AgentCommandProcessor:
"ledger_count": 0,
})
try:
# This is intentionally the same company-resolution helper used by
# _opening_balance_snapshot(). Company name is the human selection;
# GUID is verified when supplied so another open company cannot leak in.
company = self._opening_balance_company(expected_company_name)
if expected_guid and company.guid and str(company.guid).strip() != expected_guid:
raise ValueError(
"Selected Tally company GUID does not match the currently loaded company. "
"Reload Tally companies and select the company again."
)
# Resolve by the exact company name selected by the user, exactly like
# Opening Balance Review. Do not use Tally's global CURRENTCOMPANY as
# a veto when multiple companies are open; Tally may report whichever
# company currently has UI focus.
company, resolved_guid, guid_refreshed = self._cash_payment_selected_company(payload)
masters = self.tally.fetch_accounting_masters(company.name)
group_rows = list(masters.get("groups") or [])
@@ -808,6 +826,8 @@ class AgentCommandProcessor:
"status": "completed",
"company": company.as_dict(),
"company_name": company.name,
"company_guid": resolved_guid,
"selected_guid_refreshed": guid_refreshed,
"ledgers": returned,
"cash_candidates": cash_ledgers,
"ledger_count": len(returned),
@@ -1341,17 +1361,22 @@ class AgentCommandProcessor:
if not self.store.exists(client_id):
raise ValueError("Accounting local storage is not initialized for this client.")
self._ensure_cash_cache_schema(client_id)
mapping = self.store.get_active_mapping_by_guid(client_id, tally_guid) or {}
company_name = str(payload.get("company_name") or mapping.get("company_name") or "").strip()
if not company_name:
# Read-only cash compliance may work against any company that is currently
# open in TallyPrime. Do not create or alter the client's permanent mapping.
company, live_company_name = self._resolve_open_company(payload)
company_name = live_company_name
mapping = {
"tally_guid": str(company.get("guid") or tally_guid),
# Match Opening Balance Review: resolve the exact company selected by
# name from Tally's loaded-company list. This deliberately avoids the
# generic CURRENTCOMPANY name guard, which is unreliable when two Tally
# companies are open at the same time.
company, resolved_guid, _guid_refreshed = self._cash_payment_selected_company(payload)
company_name = str(company.name or "").strip()
if resolved_guid:
tally_guid = resolved_guid
payload["tally_guid"] = resolved_guid
payload["company_name"] = company_name
mapping = self.store.get_active_mapping_by_guid(client_id, tally_guid) or {
"tally_guid": tally_guid,
"company_name": company_name,
"gstin": str(company.get("gstin") or ""),
"gstin": str(getattr(company, "gstin", "") or ""),
"transient_open_company": True,
}
@@ -1451,12 +1476,18 @@ class AgentCommandProcessor:
self._cash_cache_update(client_id, job_id, status="cancelled", stage="Cancelled before Tally connection", completed_at_utc=datetime.now(timezone.utc).isoformat())
return
# This is deliberately inside the worker. It may be slow, but it can
# no longer hold open the ERP start-job command or block its timeout.
company, company_name = self._resolve_open_company(payload)
expected_guid = str(payload.get("tally_guid") or "").strip()
if str(company.get("guid") or "").strip() != expected_guid:
raise ValueError("The selected Tally company is not the currently resolved company.")
# Resolve the company with the same name-based mechanism used by
# Opening Balance Review. Do not compare against Tally CURRENTCOMPANY;
# that value can point at another open company simply because it has UI
# focus. Persist the actual GUID returned for the selected company.
company, resolved_guid, _guid_refreshed = self._cash_payment_selected_company(payload)
company_name = str(company.name or "").strip()
if resolved_guid:
payload["tally_guid"] = resolved_guid
self._cash_cache_update(
client_id, job_id, tally_guid=resolved_guid, company_name=company_name,
status="running", stage="Selected Tally company resolved by name", current_date=""
)
self._cash_cache_update(client_id, job_id, status="running", stage="Reading cash ledger masters", current_date="")
# One master request only, then a generous cooling gap before vouchers.