Fix case insensitive Accounting Mirror master persistence
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
+14
-4
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user