Fix cash payment Tally company resolution
This commit is contained in:
@@ -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,19 +1361,24 @@ 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),
|
||||
"company_name": company_name,
|
||||
"gstin": str(company.get("gstin") or ""),
|
||||
"transient_open_company": True,
|
||||
}
|
||||
|
||||
# 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(getattr(company, "gstin", "") or ""),
|
||||
"transient_open_company": True,
|
||||
}
|
||||
|
||||
# Reuse a paused/interrupted identical cache only inside the worker.
|
||||
with self.store.connect(client_id) as db:
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user