Add right-side accounting menu and sundry creditors ageing analysis
This commit is contained in:
@@ -4,7 +4,7 @@ import io
|
||||
from pathlib import Path
|
||||
import zipfile
|
||||
|
||||
ERP_LOCAL_AGENT_VERSION = "1.26.10"
|
||||
ERP_LOCAL_AGENT_VERSION = "1.26.11"
|
||||
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.10"
|
||||
__version__ = "1.26.11"
|
||||
AGENT_NAME = "ERP Local Agent"
|
||||
|
||||
@@ -4180,6 +4180,92 @@ class AgentCommandProcessor:
|
||||
f"SELECT * FROM {table} WHERE stock_item_name=? COLLATE NOCASE ORDER BY applicable_from,history_position",
|
||||
(item,)).fetchall()]
|
||||
return {"rows":rows,"count":len(rows)}
|
||||
if query == "sundry_creditor_movements":
|
||||
creditor_limit = max(1, min(100000, int(filters.get("limit") or 50000)))
|
||||
# Resolve Sundry Creditors and all descendant groups from the mirror
|
||||
# master hierarchy. Older mirrors may not have group_master; in that
|
||||
# case direct-parent ledgers still remain analyzable.
|
||||
group_parent = {}
|
||||
try:
|
||||
for gr in db.execute("SELECT name,parent FROM group_master").fetchall():
|
||||
name = str(gr["name"] or "").strip()
|
||||
if name:
|
||||
group_parent[name.casefold()] = str(gr["parent"] or "").strip()
|
||||
except Exception:
|
||||
group_parent = {}
|
||||
|
||||
def is_creditor_group(name: str) -> bool:
|
||||
current = str(name or "").strip()
|
||||
seen = set()
|
||||
while current and current.casefold() not in seen:
|
||||
key = current.casefold()
|
||||
seen.add(key)
|
||||
if key in {"sundry creditors", "sundry creditor"}:
|
||||
return True
|
||||
current = group_parent.get(key, "")
|
||||
return False
|
||||
|
||||
ledgers = []
|
||||
for lr in db.execute(
|
||||
"SELECT ledger_name,parent_group,opening_balance,closing_balance FROM ledger_master ORDER BY ledger_name"
|
||||
).fetchall():
|
||||
parent = str(lr["parent_group"] or "").strip()
|
||||
if is_creditor_group(parent):
|
||||
ledgers.append(dict(lr))
|
||||
# Direct parent fallback for mirrors where group_master was not
|
||||
# available during an older extraction.
|
||||
if not ledgers:
|
||||
ledgers = [dict(r) for r in db.execute(
|
||||
"""SELECT ledger_name,parent_group,opening_balance,closing_balance
|
||||
FROM ledger_master
|
||||
WHERE LOWER(TRIM(parent_group)) IN ('sundry creditors','sundry creditor')
|
||||
ORDER BY ledger_name"""
|
||||
).fetchall()]
|
||||
|
||||
names = [str(x.get("ledger_name") or "").strip() for x in ledgers if str(x.get("ledger_name") or "").strip()]
|
||||
rows = []
|
||||
if names:
|
||||
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)
|
||||
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 ?"
|
||||
)
|
||||
params.append(creditor_limit)
|
||||
rows = [dict(r) for r in db.execute(sql, tuple(params)).fetchall()]
|
||||
# Voucher reference/narration are intentionally joined from the
|
||||
# voucher header so the exported audit trail remains readable.
|
||||
if rows:
|
||||
vmap = {}
|
||||
guids = list(dict.fromkeys(str(r.get("voucher_guid") or "") for r in rows if r.get("voucher_guid")))
|
||||
for pos in range(0, len(guids), 800):
|
||||
chunk = guids[pos:pos+800]
|
||||
ph = ",".join("?" for _ in chunk)
|
||||
for vr in db.execute(
|
||||
f"SELECT voucher_guid,reference,narration FROM voucher WHERE voucher_guid IN ({ph})",
|
||||
tuple(chunk),
|
||||
).fetchall():
|
||||
vmap[str(vr["voucher_guid"] or "")] = dict(vr)
|
||||
for row in rows:
|
||||
head = vmap.get(str(row.get("voucher_guid") or ""), {})
|
||||
row["reference"] = str(head.get("reference") or "")
|
||||
row["narration"] = str(head.get("narration") or "")
|
||||
return {
|
||||
"ledgers": ledgers,
|
||||
"rows": rows,
|
||||
"count": len(rows),
|
||||
"truncated": len(rows) >= creditor_limit,
|
||||
"mirror": self.tally.mirror.status(client_id).get("mirror") or {},
|
||||
}
|
||||
if query == "voucher":
|
||||
guid=str(filters.get("voucher_guid") or "").strip()
|
||||
v=db.execute("SELECT * FROM voucher WHERE voucher_guid=?",(guid,)).fetchone()
|
||||
|
||||
Reference in New Issue
Block a user