Fix sundry creditors FIFO ageing from mirror voucher dates

This commit is contained in:
A R R R Associates
2026-09-09 16:55:15 +05:30
parent 078116f14f
commit a1828d645b
4 changed files with 227 additions and 87 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ import io
from pathlib import Path
import zipfile
ERP_LOCAL_AGENT_VERSION = "1.26.11"
ERP_LOCAL_AGENT_VERSION = "1.26.12"
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)
@@ -1,2 +1,2 @@
__version__ = "1.26.11"
__version__ = "1.26.12"
AGENT_NAME = "ERP Local Agent"
@@ -4224,24 +4224,76 @@ class AgentCommandProcessor:
names = [str(x.get("ledger_name") or "").strip() for x in ledgers if str(x.get("ledger_name") or "").strip()]
rows = []
date_parse_errors = []
truncated = False
if names:
from datetime import datetime as _dt
def _iso_voucher_date(value):
text = str(value or "").strip()
if not text:
return ""
try:
return _dt.fromisoformat(text[:10]).date().isoformat()
except ValueError:
pass
for fmt in (
"%Y%m%d", "%d-%m-%Y", "%d/%m/%Y", "%d-%b-%Y",
"%d-%b-%y", "%m/%d/%Y", "%m/%d/%Y %H:%M:%S",
"%Y-%m-%d %H:%M:%S",
):
try:
return _dt.strptime(text, fmt).date().isoformat()
except ValueError:
continue
return ""
placeholders = ",".join("?" for _ in names)
clauses = [f"LOWER(ledger_name) IN ({placeholders})"]
params = [x.casefold() for x in names]
if from_date:
clauses.append("voucher_date>=?")
params.append(from_date)
if to_date:
clauses.append("voucher_date<=?")
params.append(to_date)
# Do not filter dates in SQL here. Older .act files can contain
# Tally/ODBC date representations that are valid dates but are not
# lexically comparable with YYYY-MM-DD. Normalize in Python first,
# then apply the requested date window.
scan_limit = max(100000, min(250000, creditor_limit * 2))
sql = (
"SELECT voucher_guid,line_no,voucher_date,voucher_type,voucher_number,party_ledger,"
"ledger_name,dr_cr,amount,raw_amount FROM voucher_ledger_entry WHERE "
+ " AND ".join(clauses)
+ " ORDER BY ledger_name,voucher_date,line_no LIMIT ?"
f"LOWER(ledger_name) IN ({placeholders}) "
"ORDER BY ledger_name,line_no LIMIT ?"
)
params.append(creditor_limit)
rows = [dict(r) for r in db.execute(sql, tuple(params)).fetchall()]
raw_rows = [dict(r) for r in db.execute(
sql,
tuple([x.casefold() for x in names] + [scan_limit + 1]),
).fetchall()]
if len(raw_rows) > scan_limit:
truncated = True
raw_rows = raw_rows[:scan_limit]
for row in raw_rows:
original_date = str(row.get("voucher_date") or "").strip()
normalized_date = _iso_voucher_date(original_date)
if not normalized_date:
date_parse_errors.append({
"ledger_name": str(row.get("ledger_name") or ""),
"voucher_number": str(row.get("voucher_number") or ""),
"voucher_date": original_date,
})
continue
if from_date and normalized_date < from_date:
continue
if to_date and normalized_date > to_date:
continue
row["voucher_date"] = normalized_date
rows.append(row)
rows.sort(key=lambda r: (
str(r.get("ledger_name") or "").casefold(),
str(r.get("voucher_date") or ""),
int(r.get("line_no") or 0),
))
if len(rows) > creditor_limit:
truncated = True
rows = rows[:creditor_limit]
# Voucher reference/narration are intentionally joined from the
# voucher header so the exported audit trail remains readable.
if rows:
@@ -4263,7 +4315,9 @@ class AgentCommandProcessor:
"ledgers": ledgers,
"rows": rows,
"count": len(rows),
"truncated": len(rows) >= creditor_limit,
"truncated": truncated,
"date_parse_errors": date_parse_errors[:25],
"date_parse_error_count": len(date_parse_errors),
"mirror": self.tally.mirror.status(client_id).get("mirror") or {},
}
if query == "voucher":