|
|
|
@@ -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:
|
|
|
|
|