From fa0f6811061c86e580e14fd7bc19e0d4aa583f57 Mon Sep 17 00:00:00 2001 From: A R R R Associates Date: Thu, 20 Aug 2026 21:24:35 +0530 Subject: [PATCH] Enrich ERP and Tally GSTIN display --- .../templates/accounting/tally.html | 26 ++++++++- app/modules/accounting/ui.py | 55 +++++++++++++++++++ app/modules/documents/agent_package.py | 2 +- .../README_ERP_LOCAL_AGENT.txt | 26 ++++++++- .../erp_local_agent/__init__.py | 2 +- .../erp_local_agent/tally.py | 14 ++++- 6 files changed, 117 insertions(+), 8 deletions(-) diff --git a/app/modules/accounting/templates/accounting/tally.html b/app/modules/accounting/templates/accounting/tally.html index f3029c8..5e88099 100644 --- a/app/modules/accounting/templates/accounting/tally.html +++ b/app/modules/accounting/templates/accounting/tally.html @@ -261,7 +261,9 @@ ERP Scope Tally Company GUID - GSTIN + ERP GSTIN + Tally GSTIN + GSTIN Match Loaded Action @@ -280,7 +282,25 @@ {{ mapping.company_name }} {{ mapping.tally_guid }} - {{ mapping.gstin or '-' }} + + {% if mapping.erp_gstins %} + {% for gstin in mapping.erp_gstins %}
{{ gstin }}
{% endfor %} + {% else %}-{% endif %} + + {{ mapping.tally_gstin_display or '-' }} + + {% if mapping.gstin_match_status == 'matched' %} + Matched + {% elif mapping.gstin_match_status == 'mismatch' %} + Mismatch + {% elif mapping.gstin_match_status == 'tally_missing' %} + Tally GSTIN unavailable + {% elif mapping.gstin_match_status == 'erp_missing' %} + ERP GSTIN unavailable + {% else %} + Unavailable + {% endif %} + {{ 'Yes' if mapping.currently_loaded else 'No' }} @@ -314,7 +334,7 @@ {% if tally.companies %}
- + {% for company in tally.companies %} diff --git a/app/modules/accounting/ui.py b/app/modules/accounting/ui.py index 6edd734..89fab17 100644 --- a/app/modules/accounting/ui.py +++ b/app/modules/accounting/ui.py @@ -2,6 +2,7 @@ from __future__ import annotations from datetime import date, datetime, timezone from pathlib import Path +import re from urllib.parse import quote from fastapi import APIRouter, Form, Request @@ -104,6 +105,59 @@ def _registration_payload(registration, registration_type) -> dict: } +def _normalise_gstin(value: str) -> str: + return re.sub(r"\s+", "", str(value or "").strip().upper()) + + +def _erp_client_gstins(registrations) -> list[str]: + values: list[str] = [] + for registration, registration_type in registrations or []: + if str(getattr(registration_type, "code", "") or "").strip().upper() != "GSTIN": + continue + number = _normalise_gstin(getattr(registration, "registration_number", "")) + if number and number not in values: + values.append(number) + return values + + +def _enrich_gstin_display(live_result: dict | None, registrations) -> None: + """Attach ERP/Tally GSTIN display metadata without mutating stored mappings.""" + if not isinstance(live_result, dict): + return + accounting = live_result.get("accounting") or {} + tally = live_result.get("tally") or {} + mappings = accounting.get("mappings") or [] + companies = tally.get("companies") or [] + client_gstins = _erp_client_gstins(registrations) + company_by_guid = {str(row.get("guid") or "").strip(): row for row in companies if isinstance(row, dict)} + + for mapping in mappings: + if not isinstance(mapping, dict): + continue + registration_type = str(mapping.get("registration_type_code") or "").strip().upper() + registration_number = _normalise_gstin(mapping.get("registration_number")) + if mapping.get("registration_id") and registration_type == "GSTIN" and registration_number: + erp_gstins = [registration_number] + elif not mapping.get("registration_id"): + erp_gstins = list(client_gstins) + else: + erp_gstins = [] + + live_company = company_by_guid.get(str(mapping.get("tally_guid") or "").strip()) or {} + tally_gstin = _normalise_gstin(live_company.get("gstin") or mapping.get("gstin")) + mapping["erp_gstins"] = erp_gstins + mapping["tally_gstin_display"] = tally_gstin + + if erp_gstins and tally_gstin: + mapping["gstin_match_status"] = "matched" if tally_gstin in erp_gstins else "mismatch" + elif erp_gstins and not tally_gstin: + mapping["gstin_match_status"] = "tally_missing" + elif tally_gstin and not erp_gstins: + mapping["gstin_match_status"] = "erp_missing" + else: + mapping["gstin_match_status"] = "unavailable" + + def _node_online(node) -> bool: if not node or not node.last_seen_at_utc: return False @@ -175,6 +229,7 @@ def tally_tool( ) if response_data.get("ok"): live_result = response_data.get("result") or {} + _enrich_gstin_display(live_result, registrations) else: command_error = str(response_data.get("error") or "Local agent command failed.") except Exception as exc: diff --git a/app/modules/documents/agent_package.py b/app/modules/documents/agent_package.py index 8120df3..10918e4 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.9.0" +ERP_LOCAL_AGENT_VERSION = "1.9.4" 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/README_ERP_LOCAL_AGENT.txt b/app/modules/documents/local_agent_runtime/README_ERP_LOCAL_AGENT.txt index 795dc55..d95819a 100644 --- a/app/modules/documents/local_agent_runtime/README_ERP_LOCAL_AGENT.txt +++ b/app/modules/documents/local_agent_runtime/README_ERP_LOCAL_AGENT.txt @@ -1,4 +1,4 @@ -ERP Local Agent 1.9.0 +ERP Local Agent 1.9.4 Existing storage, WebSocket tunnel, Tally and client .act functionality are preserved. @@ -66,3 +66,27 @@ Desktop Supervisor 1.9.0 - Closing the desktop dashboard does NOT stop the supervisor or worker. - The dashboard automatically reconnects/reloads when the worker goes down and returns during an update. - Existing browser access at http://127.0.0.1:8788 remains available. + +Supervisor detection hotfix 1.9.1: +- Desktop dashboard no longer relies on os.kill(pid, 0) to detect the SYSTEM-owned supervisor. +- Uses Windows OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION) when available. +- Uses localhost dashboard health and recent supervisor_state.json as additional valid signals. +- Prevents false 'ERP Local Agent supervisor did not start' errors caused by cross-user Windows process permissions. + +Single-supervisor hotfix 1.9.2: +- Replaces PID-text singleton logic with an msvcrt held byte-range file lock that works across Windows processes/logon sessions. +- Duplicate supervisors immediately exit without starting/killing workers. +- Desktop dashboard never launches an interactive supervisor when the installed ERP Local Agent Scheduled Task exists. +- If the task exists but is stopped, dashboard requests Task Scheduler to start it and waits for localhost health. +- Prevents supervisor/worker restart storms caused by multiple supervisors killing one another's workers. + +Global mutex hardening 1.9.3: +- Supervisor primary singleton is Windows Global\ARRR_ERP_Local_Agent_Supervisor via CreateMutexW. +- Existing byte-range lock remains only as fallback if Global mutex creation is unavailable. +- Scheduled Task installation is expected to use MultipleInstances=IgnoreNew. + + +Tally GSTIN enrichment 1.9.4: +- Loaded-company discovery requests GSTRegistrationNumber, GSTRegistrationDetails, GSTIN and PartyGSTIN. +- Company parser searches GSTIN candidates recursively, including nested GST registration details. +- Existing company GUID/name discovery remains unchanged. 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 d93f970..cf0d4e4 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.9.0" +__version__ = "1.9.4" AGENT_NAME = "ERP Local Agent" diff --git a/app/modules/documents/local_agent_runtime/erp_local_agent/tally.py b/app/modules/documents/local_agent_runtime/erp_local_agent/tally.py index b266554..709e4e3 100644 --- a/app/modules/documents/local_agent_runtime/erp_local_agent/tally.py +++ b/app/modules/documents/local_agent_runtime/erp_local_agent/tally.py @@ -201,7 +201,7 @@ class TallyLiveConnector: $$SysName:XML - CompanyName,GUID,GSTRegistrationNumber + CompanyName,GUID,GSTRegistrationNumber,GSTRegistrationDetails,GSTIN,PartyGSTIN """ @@ -563,5 +563,15 @@ class TallyLiveConnector: if not name or key in seen: continue seen.add(key) - companies.append(TallyCompany(name=name, guid=_child_text(element, "GUID"), gstin=_child_text(element, "GSTREGISTRATIONNUMBER"))) + gstin = _first_text( + element, + ( + "GSTREGISTRATIONNUMBER", + "GSTIN", + "PARTYGSTIN", + "GSTNO", + "GSTNUMBER", + ), + ) + companies.append(TallyCompany(name=name, guid=_child_text(element, "GUID"), gstin=gstin)) return companies
CompanyGUIDGSTIN
CompanyGUIDTally GSTIN