Fix case insensitive Accounting Mirror master persistence

This commit is contained in:
A R R R Associates
2026-09-09 12:22:26 +05:30
parent 88bcd4d0a4
commit 4ef6b1fc5d
4 changed files with 40 additions and 8 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ import io
from pathlib import Path from pathlib import Path
import zipfile import zipfile
ERP_LOCAL_AGENT_VERSION = "1.26.9" ERP_LOCAL_AGENT_VERSION = "1.26.10"
ERP_LOCAL_AGENT_NAME = "ERP Local Agent" ERP_LOCAL_AGENT_NAME = "ERP Local Agent"
RUNTIME_ROOT = Path(__file__).resolve().parent / "local_agent_runtime" RUNTIME_ROOT = Path(__file__).resolve().parent / "local_agent_runtime"
_DETERMINISTIC_ZIP_TIMESTAMP = (2026, 1, 1, 0, 0, 0) _DETERMINISTIC_ZIP_TIMESTAMP = (2026, 1, 1, 0, 0, 0)
@@ -1,2 +1,2 @@
__version__ = "1.26.9" __version__ = "1.26.10"
AGENT_NAME = "ERP Local Agent" AGENT_NAME = "ERP Local Agent"
@@ -140,7 +140,21 @@ class AccountingMirrorEngine:
reader = csv.DictReader(f) reader = csv.DictReader(f)
if not reader.fieldnames: if not reader.fieldnames:
return [] 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 @staticmethod
def _summary(db_path: Path) -> dict[str, Any]: def _summary(db_path: Path) -> dict[str, Any]:
@@ -228,6 +242,14 @@ class AccountingMirrorEngine:
if not rows: if not rows:
diagnostics[table] = {"source_rows": 0, "stored_rows": 0} diagnostics[table] = {"source_rows": 0, "stored_rows": 0}
continue 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] names = [name for name,_ in cols]
placeholders = ",".join("?" for _ in names) placeholders = ",".join("?" for _ in names)
for row in rows: for row in rows:
@@ -240,7 +262,7 @@ class AccountingMirrorEngine:
"gst_type_of_supply":"GSTTypeofSupply","original_name":"OriginalName", "gst_type_of_supply":"GSTTypeofSupply","original_name":"OriginalName",
"is_simple_unit":"IsSimpleUnit","additional_units":"AdditionalUnits","conversion":"Conversion", "is_simple_unit":"IsSimpleUnit","additional_units":"AdditionalUnits","conversion":"Conversion",
}.get(name, name) }.get(name, name)
value = row.get(source, "") value = row.get(source, row.get(str(source).casefold(), ""))
if name == "conversion": if name == "conversion":
value = _number(value) value = _number(value)
else: else:
@@ -66,16 +66,21 @@ def csv_rows_expected(src, name, expected_fields):
return return
clean=[ck(x) for x in headers] clean=[ck(x) for x in headers]
print(f"{name} headers:",clean) 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} 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 use_headers=bool(expected_keys.intersection(header_keys)) and "name" in header_keys
for values in raw: for values in raw:
if use_headers: if use_headers:
row={} row={}
for idx,key in enumerate(clean): for idx,key in enumerate(clean):
if key: if not key:
row[key]=values[idx] if idx < len(values) else "" continue
yield norm(row) 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: else:
row={} row={}
for idx,key in enumerate(expected_fields): for idx,key in enumerate(expected_fields):
@@ -396,6 +401,11 @@ def main():
lname=t(r.get("Name")) lname=t(r.get("Name"))
if not lg or not lname: if not lg or not lname:
ledger_skipped_rows+=1 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 continue
aid=integer(r.get("AlterID")) or 0 aid=integer(r.get("AlterID")) or 0
max_ledger_alter=max(max_ledger_alter,aid) max_ledger_alter=max(max_ledger_alter,aid)