|
|
|
@@ -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.
|
|
|
|
|