diff --git a/app/modules/accounting/cash_payment_ui.py b/app/modules/accounting/cash_payment_ui.py index 571f635..35bcafe 100644 --- a/app/modules/accounting/cash_payment_ui.py +++ b/app/modules/accounting/cash_payment_ui.py @@ -273,7 +273,7 @@ async def cash_payment_ledgers(request: Request): date_from = str(form.get("date_from") or date.today().isoformat()).strip() force_refresh = str(form.get("force_refresh") or "").strip().lower() in {"1","true","yes","on"} if not tally_guid: - return JSONResponse({"ok": False, "error": "Select a mapped Tally company."}, status_code=400) + return JSONResponse({"ok": False, "error": "Select an open Tally company."}, status_code=400) node = get_active_storage_node_for_branch(db, scope.tenant_id, scope.branch_id) if not node or not _node_online(node): return JSONResponse({"ok": False, "error": "ERP Local Agent is offline."}, status_code=409) @@ -351,6 +351,7 @@ async def start_cash_payment_cache(request: Request): if not selected_client: return JSONResponse({"ok": False, "error": "Select a valid client."}, status_code=400) tally_guid = str(form.get("tally_guid") or "").strip() + company_name = str(form.get("company_name") or "").strip() cash_ledger_name = str(form.get("cash_ledger_name") or "").strip() date_from = str(form.get("date_from") or "").strip() date_to = str(form.get("date_to") or "").strip() @@ -362,7 +363,7 @@ async def start_cash_payment_cache(request: Request): except Exception as exc: return JSONResponse({"ok": False, "error": str(exc)}, status_code=400) if not tally_guid: - return JSONResponse({"ok": False, "error": "Select a mapped Tally company."}, status_code=400) + return JSONResponse({"ok": False, "error": "Select an open Tally company."}, status_code=400) if not cash_ledger_name: return JSONResponse({"ok": False, "error": "Load the Tally ledgers and confirm the Cash ledger."}, status_code=400) node = get_active_storage_node_for_branch(db, scope.tenant_id, scope.branch_id) @@ -376,6 +377,7 @@ async def start_cash_payment_cache(request: Request): { **_accounting_storage_payload(selected_client, _financial_year_for_date(parsed_from)), "tally_guid": tally_guid, + "company_name": company_name, "date_from": date_from, "date_to": date_to, "cash_ledger_name": cash_ledger_name, diff --git a/app/modules/accounting/templates/accounting/cash_payment_review.html b/app/modules/accounting/templates/accounting/cash_payment_review.html index 76cffaa..ae562c9 100644 --- a/app/modules/accounting/templates/accounting/cash_payment_review.html +++ b/app/modules/accounting/templates/accounting/cash_payment_review.html @@ -32,11 +32,25 @@
- + + {% set mapped_guids = [] %} {% if live_result and live_result.accounting %} {% for mapping in live_result.accounting.mappings or [] %} - + {% if mapping.tally_guid %}{% set _ = mapped_guids.append(mapping.tally_guid) %}{% endif %} + {% endfor %} + {% endif %} + {% if live_result and live_result.tally %} + {% for company in live_result.tally.companies or [] %} + {% set is_mapped = company.guid in mapped_guids %} + + {% endfor %} + {% endif %} + {% if live_result and live_result.accounting %} + {% for mapping in live_result.accounting.mappings or [] %} + {% if mapping.tally_guid and mapping.tally_guid not in ((live_result.tally.companies or [])|map(attribute='guid')|list if live_result and live_result.tally else []) %} + + {% endif %} {% endfor %} {% endif %} @@ -284,6 +298,12 @@ } if (ledgerButton) ledgerButton.addEventListener('click', function(){loadCashLedgers(false);}); if (ledgerRefreshButton) ledgerRefreshButton.addEventListener('click', function(){loadCashLedgers(true);}); + const companySelect = document.getElementById('cash-tally-company'); + if (companySelect) companySelect.addEventListener('change', function(){ + ledgerSelect.innerHTML = ''; + ledgerHelp.textContent = this.value ? 'Company changed. Click Load to use that company's cached ledgers, or Refresh from Tally to re-sync.' : 'Select an open Tally company first.'; + ledgerProgress.classList.add('hidden'); + }); function show() { overlay.classList.remove('hidden'); @@ -366,6 +386,10 @@ if (!form.reportValidity()) return; const fd = new FormData(form); fd.set('csrf_token', document.getElementById('cash-csrf-token').value); + const selectedCompany = document.getElementById('cash-tally-company'); + const selectedCompanyOption = selectedCompany && selectedCompany.options[selectedCompany.selectedIndex]; + const selectedCompanyName = selectedCompanyOption ? String(selectedCompanyOption.dataset.companyName || selectedCompanyOption.textContent || '').replace(' • Mapped','').replace(' • Mapped (not open)','').trim() : ''; + if (selectedCompanyName) fd.set('company_name', selectedCompanyName); button.disabled = true; show(); try { diff --git a/app/modules/documents/agent_package.py b/app/modules/documents/agent_package.py index 5b9777e..683e27b 100644 --- a/app/modules/documents/agent_package.py +++ b/app/modules/documents/agent_package.py @@ -4,7 +4,7 @@ import io from pathlib import Path import zipfile -ERP_LOCAL_AGENT_VERSION = "1.22.16" +ERP_LOCAL_AGENT_VERSION = "1.22.17" 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) diff --git a/app/modules/documents/local_agent_runtime/erp_local_agent/__init__.py b/app/modules/documents/local_agent_runtime/erp_local_agent/__init__.py index c13fa20..6686d32 100644 --- a/app/modules/documents/local_agent_runtime/erp_local_agent/__init__.py +++ b/app/modules/documents/local_agent_runtime/erp_local_agent/__init__.py @@ -1,2 +1,2 @@ -__version__ = "1.22.16" +__version__ = "1.22.17" AGENT_NAME = "ERP Local Agent" diff --git a/app/modules/documents/local_agent_runtime/erp_local_agent/commands.py b/app/modules/documents/local_agent_runtime/erp_local_agent/commands.py index 11f1f24..88dfb71 100644 --- a/app/modules/documents/local_agent_runtime/erp_local_agent/commands.py +++ b/app/modules/documents/local_agent_runtime/erp_local_agent/commands.py @@ -174,6 +174,7 @@ class AgentCommandProcessor: "cash_payment_dual_progress_capability": True, "cash_payment_async_ledger_master_capability": True, "cash_payment_ledger_cache_capability": True, + "cash_payment_open_company_selection_capability": True, "opening_balance_balance_sheet_only_capability": True, "tally_writeback_capability": True, } @@ -518,7 +519,7 @@ class AgentCommandProcessor: def _resolve_open_company(self, payload: dict[str, Any]) -> tuple[dict[str, Any], str]: requested_guid = str(payload.get("tally_guid") or "").strip() if not requested_guid: - raise ValueError("Mapped Tally company GUID is required.") + raise ValueError("Open Tally company GUID is required.") tally_status = self.tally.status() if not tally_status.get("connected"): raise ValueError(str(tally_status.get("error") or "TallyPrime is not connected.")) @@ -801,7 +802,7 @@ class AgentCommandProcessor: client_id = int(payload.get("client_id")) tally_guid = str(payload.get("tally_guid") or "").strip() if not tally_guid: - raise ValueError("Mapped Tally company GUID is required.") + raise ValueError("Open Tally company GUID is required.") company_name = str(payload.get("company_name") or "").strip() cached = self._cash_payment_cached_ledgers(client_id, tally_guid) if cached: @@ -827,7 +828,7 @@ class AgentCommandProcessor: client_id = int(payload.get("client_id")) tally_guid = str(payload.get("tally_guid") or "").strip() if not tally_guid: - raise ValueError("Mapped Tally company GUID is required.") + raise ValueError("Open Tally company GUID is required.") expected_company_name = str(payload.get("company_name") or "").strip() self._ensure_cash_ledger_schema(client_id) @@ -891,12 +892,12 @@ class AgentCommandProcessor: with self.store.connect(client_id) as db: db.execute( "UPDATE cash_payment_ledger_jobs SET status='running',stage=?,percent=? WHERE job_id=?", - ("Validating mapped company with Tally", 8, job_id), + ("Validating selected open company with Tally", 8, job_id), ) db.commit() _cash_progress_snapshot({ "job_id": job_id, "job_kind": "ledger_master", "status": "running", - "stage": "Validating mapped company with Tally", "percent": 8, + "stage": "Validating selected open company with Tally", "percent": 8, "company_name": company_name, "ledger_count": 0, }) @@ -1127,7 +1128,7 @@ class AgentCommandProcessor: date_from = str(payload.get("date_from") or "").strip() date_to = str(payload.get("date_to") or "").strip() if not tally_guid: - raise ValueError("Select a mapped Tally company before starting extraction.") + raise ValueError("Select an open Tally company before starting extraction.") selected_cash_ledgers = [str(x or "").strip() for x in (payload.get("cash_ledger_names") or []) if str(x or "").strip()] single_cash_ledger = str(payload.get("cash_ledger_name") or "").strip() if single_cash_ledger and single_cash_ledger not in selected_cash_ledgers: @@ -1224,10 +1225,19 @@ 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) - company_name = str(mapping.get("company_name") or "").strip() + 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: - raise ValueError("The mapped Tally company name is not available in local accounting storage. Refresh the Tally mapping first.") + # 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, + } # Reuse a paused/interrupted identical cache only inside the worker. with self.store.connect(client_id) as db: