From 4ef6b1fc5ded6dac7f516bdaf21538bfc9f57acd Mon Sep 17 00:00:00 2001 From: A R R R Associates Date: Wed, 9 Sep 2026 12:22:26 +0530 Subject: [PATCH] Fix case insensitive Accounting Mirror master persistence --- app/modules/documents/agent_package.py | 2 +- .../erp_local_agent/__init__.py | 2 +- .../erp_local_agent/accounting_mirror.py | 26 +++++++++++++++++-- .../mirror_runtime/build_act_v3_0_1.py | 18 ++++++++++--- 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/app/modules/documents/agent_package.py b/app/modules/documents/agent_package.py index 0e695ce..2b2e164 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.26.9" +ERP_LOCAL_AGENT_VERSION = "1.26.10" 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 624b92a..c16f55a 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.26.9" +__version__ = "1.26.10" AGENT_NAME = "ERP Local Agent" diff --git a/app/modules/documents/local_agent_runtime/erp_local_agent/accounting_mirror.py b/app/modules/documents/local_agent_runtime/erp_local_agent/accounting_mirror.py index 9411ff4..d8883af 100644 --- a/app/modules/documents/local_agent_runtime/erp_local_agent/accounting_mirror.py +++ b/app/modules/documents/local_agent_runtime/erp_local_agent/accounting_mirror.py @@ -140,7 +140,21 @@ class AccountingMirrorEngine: reader = csv.DictReader(f) if not reader.fieldnames: return [] - return [{str(k or "").lstrip("$").strip(): (v or "") for k, v in row.items()} for row in reader] + result: list[dict[str, str]] = [] + for raw in reader: + row: dict[str, str] = {} + for key, value in raw.items(): + clean = str(key or "").lstrip("$").strip() + if not clean: + continue + text = value or "" + row[clean] = text + # Alias every field by case-folded name. This fixes Tally + # variants such as Guid/GUID and GstRegistrationType/ + # GSTRegistrationType without changing the extracted CSV. + row[clean.casefold()] = text + result.append(row) + return result @staticmethod def _summary(db_path: Path) -> dict[str, Any]: @@ -228,6 +242,14 @@ class AccountingMirrorEngine: if not rows: diagnostics[table] = {"source_rows": 0, "stored_rows": 0} continue + if table == "group_master": + sample = rows[0] if rows else {} + diagnostics["group_csv"] = { + "source_rows": len(rows), + "sample_name": _text(sample.get("Name", sample.get("name", ""))), + "sample_parent": _text(sample.get("Parent", sample.get("parent", ""))), + "sample_guid": _text(sample.get("GUID", sample.get("guid", ""))), + } names = [name for name,_ in cols] placeholders = ",".join("?" for _ in names) for row in rows: @@ -240,7 +262,7 @@ class AccountingMirrorEngine: "gst_type_of_supply":"GSTTypeofSupply","original_name":"OriginalName", "is_simple_unit":"IsSimpleUnit","additional_units":"AdditionalUnits","conversion":"Conversion", }.get(name, name) - value = row.get(source, "") + value = row.get(source, row.get(str(source).casefold(), "")) if name == "conversion": value = _number(value) else: diff --git a/app/modules/documents/local_agent_runtime/erp_local_agent/mirror_runtime/build_act_v3_0_1.py b/app/modules/documents/local_agent_runtime/erp_local_agent/mirror_runtime/build_act_v3_0_1.py index bfb18e6..d099ea8 100644 --- a/app/modules/documents/local_agent_runtime/erp_local_agent/mirror_runtime/build_act_v3_0_1.py +++ b/app/modules/documents/local_agent_runtime/erp_local_agent/mirror_runtime/build_act_v3_0_1.py @@ -66,16 +66,21 @@ def csv_rows_expected(src, name, expected_fields): return clean=[ck(x) for x in headers] print(f"{name} headers:",clean) + expected_map={str(x).casefold(): str(x) for x in expected_fields} header_keys={x.casefold() for x in clean if x} - expected_keys={str(x).casefold() for x in expected_fields} + expected_keys=set(expected_map) use_headers=bool(expected_keys.intersection(header_keys)) and "name" in header_keys for values in raw: if use_headers: row={} for idx,key in enumerate(clean): - if key: - row[key]=values[idx] if idx < len(values) else "" - yield norm(row) + if not key: + continue + canonical=expected_map.get(key.casefold(), key) + row[canonical]=values[idx] if idx < len(values) else "" + # Keep exact expected key names (GUID, AlterID, Name...) so the + # existing proven loader never loses a row due only to header case. + yield row else: row={} for idx,key in enumerate(expected_fields): @@ -396,6 +401,11 @@ def main(): lname=t(r.get("Name")) if not lg or not lname: ledger_skipped_rows+=1 + if ledger_skipped_rows <= 3: + print( + "LEDGER IMPORT - SKIPPED SAMPLE:", + {"GUID": lg, "Name": lname, "keys": sorted(str(k) for k in r.keys())} + ) continue aid=integer(r.get("AlterID")) or 0 max_ledger_alter=max(max_ledger_alter,aid)