Fix depreciation mirror selection and TDS capital account exclusion

This commit is contained in:
A R R R Associates
2026-09-06 21:28:23 +05:30
parent f211d9e6a8
commit 5c19f23c2d
6 changed files with 153 additions and 34 deletions
@@ -1,2 +1,2 @@
__version__ = "1.25.5"
__version__ = "1.25.6"
AGENT_NAME = "ERP Local Agent"
@@ -968,6 +968,61 @@ class LocalAccountingStore:
"provisional": fy_end > today,
}
@staticmethod
def _looks_like_depreciation_text(value: str) -> bool:
text = re.sub(r"[^a-z0-9]+", " ", str(value or "").casefold()).strip()
if not text:
return False
return any(token in text for token in (
"depreciation","depn","depreciation expense","depreciation reserve",
"accumulated depreciation","provision for depreciation",
))
def _recorded_depreciation_for_asset(self, db: sqlite3.Connection, *, tally_guid: str,
asset_ledger_name: str, start_date: str, end_date: str) -> dict:
rows = db.execute(
"""SELECT v.id AS voucher_id,
COALESCE(NULLIF(v.effective_date,''),v.voucher_date) AS movement_date,
v.voucher_type_name,v.voucher_number,v.reference,v.narration,
e.amount,e.is_deemed_positive
FROM tally_voucher_ledger_entries e
JOIN tally_vouchers v ON v.id=e.voucher_id
WHERE v.tally_guid=?
AND lower(trim(COALESCE(e.ledger_name,'')))=lower(trim(?))
AND COALESCE(NULLIF(v.effective_date,''),v.voucher_date)>=?
AND COALESCE(NULLIF(v.effective_date,''),v.voucher_date)<=?
AND lower(COALESCE(v.is_cancelled,'')) NOT IN ('yes','true','1')
AND lower(COALESCE(v.is_optional,'')) NOT IN ('yes','true','1')
ORDER BY movement_date,v.id""",
(tally_guid,asset_ledger_name,start_date,end_date),
).fetchall()
total=0.0; vouchers=[]; seen=set()
for row in rows:
if self._is_debit_entry(row):
continue
counter_rows=db.execute(
"""SELECT ledger_name FROM tally_voucher_ledger_entries
WHERE voucher_id=? AND lower(trim(COALESCE(ledger_name,'')))<>lower(trim(?))""",
(int(row["voucher_id"]),asset_ledger_name),
).fetchall()
counters=[str(x["ledger_name"] or "").strip() for x in counter_rows]
text=" ".join([str(row["voucher_type_name"] or ""),str(row["reference"] or ""),
str(row["narration"] or ""),*counters])
if not self._looks_like_depreciation_text(text):
continue
total += abs(float(row["amount"] or 0))
vid=int(row["voucher_id"])
if vid not in seen:
seen.add(vid)
vouchers.append({
"voucher_id":vid,"date":str(row["movement_date"] or ""),
"voucher_type":str(row["voucher_type_name"] or ""),
"voucher_number":str(row["voucher_number"] or ""),
"reference":str(row["reference"] or ""),
"counter_ledgers":counters,
})
return {"amount":round(total,2),"voucher_count":len(vouchers),"vouchers":vouchers,"already_recorded":bool(total>0)}
def depreciation_preview(self, client_id: int, *, tally_guid: str, fy_start: str, fy_end: str) -> dict:
if not self.exists(client_id):
raise ValueError("Accounting storage is not initialized for this client.")
@@ -1003,13 +1058,20 @@ class LocalAccountingStore:
AND lower(COALESCE(v.is_optional,'')) NOT IN ('yes','true','1')""",
(tally_guid,name,start.isoformat(),movement_end.isoformat()),
).fetchall()
full=half=reductions=0.0
recorded_dep=self._recorded_depreciation_for_asset(
db,tally_guid=tally_guid,asset_ledger_name=name,
start_date=start.isoformat(),end_date=movement_end.isoformat()
)
recorded_dep_amount=float(recorded_dep.get("amount") or 0)
full=half=0.0; raw_credit_reductions=0.0
for e in entries:
amt=abs(float(e["amount"] or 0))
if self._is_debit_entry(e):
if self._half_rate_addition(str(e["movement_date"] or ""), end): half += amt
else: full += amt
else: reductions += amt
else:
raw_credit_reductions += amt
reductions=max(0.0,raw_credit_reductions-recorded_dep_amount)
opening_wdv = abs(float(row["opening_balance"] or 0))
tally_closing = abs(float(row["closing_balance"] or 0))
additions_total = round(full + half, 2)
@@ -1024,6 +1086,10 @@ class LocalAccountingStore:
"ledger_key":key,"ledger_guid":str(row["master_guid"] or ""),"ledger_name":name,"parent_group":str(row["parent"] or ""),
"opening_wdv":opening_wdv,"tally_closing_balance":tally_closing,
"additions_full_rate":round(full,2),"additions_half_rate":round(half,2),"reductions":round(reductions,2),
"recorded_depreciation":round(recorded_dep_amount,2),
"depreciation_already_recorded":bool(recorded_dep.get("already_recorded")),
"recorded_depreciation_voucher_count":int(recorded_dep.get("voucher_count") or 0),
"recorded_depreciation_vouchers":list(recorded_dep.get("vouchers") or []),
"rate_percent":rules.get(key,0.0),
"movement_entries":len(entries),
})
@@ -1050,7 +1116,12 @@ class LocalAccountingStore:
full_base=max(0.0, opening+full-reductions)
dep=min(closing_before, (full_base*rate/100.0)+(half*rate/200.0))
dep=round(dep,2); closing=round(max(0.0,closing_before-dep),2); total+=dep
line={**row,"rate_percent":rate,"depreciation_amount":dep,"closing_wdv":closing,"closing_before_depreciation":round(closing_before,2)}
recorded_dep=float(row.get("recorded_depreciation") or 0)
line={**row,"rate_percent":rate,"depreciation_amount":dep,
"recorded_depreciation":round(recorded_dep,2),
"depreciation_already_recorded":bool(recorded_dep>0),
"remaining_unrecorded_depreciation":round(max(0.0,dep-recorded_dep),2),
"closing_wdv":closing,"closing_before_depreciation":round(closing_before,2)}
lines.append(line)
mapping=preview["mapping"]
with self.connect(client_id) as db:
@@ -1067,7 +1138,19 @@ class LocalAccountingStore:
db.execute("""INSERT INTO it_depreciation_run_lines(run_id,line_no,ledger_key,ledger_guid,ledger_name,parent_group,opening_wdv,additions_full_rate,additions_half_rate,reductions,rate_percent,depreciation_amount,closing_wdv,payload_json)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(run_id,i,row["ledger_key"],row["ledger_guid"],row["ledger_name"],row["parent_group"],row["opening_wdv"],row["additions_full_rate"],row["additions_half_rate"],row["reductions"],row["rate_percent"],row["depreciation_amount"],row["closing_wdv"],json.dumps(row,ensure_ascii=False,separators=(",",":"))))
return {"run_id":run_id,"status":"draft","company_name":str(mapping.get("company_name") or ""),"fy_start":str(fy_start),"fy_end":str(fy_end),"total_depreciation":round(total,2),"depreciation_expense_ledger":str(depreciation_expense_ledger or ""),"depreciation_reserve_ledger":str(depreciation_reserve_ledger or ""),"lines":lines,"proposed_entry":{"debit_ledger":str(depreciation_expense_ledger or "Depreciation"),"credit_ledger":str(depreciation_reserve_ledger or "Depreciation Reserve"),"amount":round(total,2)},"no_tally_writeback":True}
total_recorded=round(sum(float(x.get("recorded_depreciation") or 0) for x in lines),2)
total_remaining=round(sum(float(x.get("remaining_unrecorded_depreciation") or 0) for x in lines),2)
return {"run_id":run_id,"status":"draft","company_name":str(mapping.get("company_name") or ""),
"fy_start":str(fy_start),"fy_end":str(fy_end),"total_depreciation":round(total,2),
"total_recorded_depreciation":total_recorded,
"total_remaining_unrecorded_depreciation":total_remaining,
"depreciation_expense_ledger":str(depreciation_expense_ledger or ""),
"depreciation_reserve_ledger":str(depreciation_reserve_ledger or ""),
"lines":lines,
"proposed_entry":{"debit_ledger":str(depreciation_expense_ledger or "Depreciation"),
"credit_ledger":str(depreciation_reserve_ledger or "Depreciation Reserve"),
"amount":round(total,2)},
"no_tally_writeback":True}
def get_it_depreciation_run(self, client_id: int, run_id: int) -> dict:
with self.connect(client_id) as db:
@@ -3164,22 +3164,27 @@ class AgentCommandProcessor:
if not value:
return False
exact = {
"capital account",
"capital accounts",
"partners capital",
"partner capital",
"partners capital accounts",
"partner capital accounts",
"proprietors capital",
"proprietor capital",
"owners capital",
"owner capital",
"share capital",
"reserves surplus",
"reserves and surplus",
"capital account","capital accounts","partners capital","partner capital",
"partners capital accounts","partner capital accounts","proprietors capital",
"proprietor capital","owners capital","owner capital","share capital",
"reserves surplus","reserves and surplus","partners current account",
"partner current account","partners current accounts","partner current accounts",
"proprietors current account","proprietor current account",
"owners current account","owner current account",
}
if value in exact:
return True
owner_prefix = (
value.startswith("partner ") or value.startswith("partners ")
or value.startswith("proprietor ") or value.startswith("proprietors ")
or value.startswith("owner ") or value.startswith("owners ")
)
current_account_like = (
" current account" in value or " current accounts" in value
or " current a c" in value or value.endswith(" current ac")
)
if owner_prefix and current_account_like:
return True
return (
value.startswith("capital account ")
or value.endswith(" capital account")