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
@@ -18,7 +18,7 @@
<section class="grid gap-4 md:grid-cols-3"> <section class="grid gap-4 md:grid-cols-3">
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">ERP Local Agent</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if agent_online else 'text-red-700' }}">{{ 'Connected' if agent_online else 'Offline' }}</div></div> <div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">ERP Local Agent</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if agent_online else 'text-red-700' }}">{{ 'Connected' if agent_online else 'Offline' }}</div></div>
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">SQLite Mirror</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if mirror.ready_for_financial_year else 'text-amber-700' }}">{{ 'Ready' if mirror.ready_for_financial_year else 'Mirror required' }}</div><div class="mt-1 text-xs text-slate-500">{{ mirror.company.company_name if mirror.ready_for_financial_year and mirror.company else '' }}</div></div> <div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">SQLite Mirror</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if mirror.ready_for_financial_year else 'text-amber-700' }}">{{ 'Ready' if mirror.ready_for_financial_year else 'Mirror required' }}</div>{% if mirror.ready_for_financial_year and mirror.company %}<div class="mt-1 text-xs text-slate-500">{{ mirror.company.company_name }} · {{ fy_start }} to {{ fy_end }}</div>{% elif mirror.financial_year_reason %}<div class="mt-1 text-xs text-amber-700">{{ mirror.financial_year_reason }}</div>{% endif %}</div>
<div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Write-back</div><div class="mt-2 text-lg font-semibold text-slate-700">Controlled</div><div class="mt-1 text-xs text-slate-500">Tally is contacted only after approval/posting.</div></div> <div class="rounded-2xl bg-white p-5 shadow-soft"><div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Write-back</div><div class="mt-2 text-lg font-semibold text-slate-700">Controlled</div><div class="mt-1 text-xs text-slate-500">Tally is contacted only after approval/posting.</div></div>
</section> </section>
@@ -44,7 +44,7 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}"><input type="hidden" name="fy_start" value="{{ fy_start }}"><input type="hidden" name="fy_end" value="{{ fy_end }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}"><input type="hidden" name="fy_start" value="{{ fy_start }}"><input type="hidden" name="fy_end" value="{{ fy_end }}">
<section class="rounded-2xl bg-white p-5 shadow-soft"> <section class="rounded-2xl bg-white p-5 shadow-soft">
<div class="grid gap-4 md:grid-cols-2"> <div class="grid gap-4 md:grid-cols-2">
<div><label class="text-sm font-medium text-slate-700">Depreciation Expense Ledger</label><select name="depreciation_expense_ledger" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Depreciation</option>{% for name in preview.all_ledgers %}<option value="{{ name }}">{{ name }}</option>{% endfor %}</select></div> <div><label class="text-sm font-medium text-slate-700">Depreciation Debit Ledger</label><select id="depreciation-debit-ledger" name="depreciation_expense_ledger" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Depreciation</option>{% for name in preview.all_ledgers %}<option value="{{ name }}">{{ name }}</option>{% endfor %}</select><p class="mt-1 text-xs text-slate-500">Any existing ledger may be selected, including the asset ledger itself.</p></div>
<div><label class="text-sm font-medium text-slate-700">Depreciation Reserve / Credit Ledger</label><select name="depreciation_reserve_ledger" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Depreciation Reserve</option>{% for name in preview.all_ledgers %}<option value="{{ name }}">{{ name }}</option>{% endfor %}</select></div> <div><label class="text-sm font-medium text-slate-700">Depreciation Reserve / Credit Ledger</label><select name="depreciation_reserve_ledger" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="">Depreciation Reserve</option>{% for name in preview.all_ledgers %}<option value="{{ name }}">{{ name }}</option>{% endfor %}</select></div>
</div> </div>
<p class="mt-3 text-xs text-slate-500">These ledger selections are stored with the draft. Phase 6 can post the Journal only after the saved draft is explicitly approved.</p> <p class="mt-3 text-xs text-slate-500">These ledger selections are stored with the draft. Phase 6 can post the Journal only after the saved draft is explicitly approved.</p>
@@ -60,7 +60,7 @@
<button type="button" id="apply-bulk-rate" class="rounded-lg border border-brand-300 bg-white px-3 py-2 text-sm font-semibold text-brand-700 hover:bg-brand-50">Apply rate to selected</button> <button type="button" id="apply-bulk-rate" class="rounded-lg border border-brand-300 bg-white px-3 py-2 text-sm font-semibold text-brand-700 hover:bg-brand-50">Apply rate to selected</button>
</div> </div>
</div> </div>
<div class="overflow-x-auto"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500"><tr><th class="px-4 py-3 w-10">Select</th><th class="px-4 py-3">Ledger</th><th class="px-4 py-3 text-right">Opening WDV</th><th class="px-4 py-3 text-right">Additions ≥180 days</th><th class="px-4 py-3 text-right">Additions &lt;180 days</th><th class="px-4 py-3 text-right">Reductions</th><th class="px-4 py-3">Rate</th></tr></thead><tbody class="divide-y divide-slate-100"> <div class="overflow-x-auto"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500"><tr><th class="px-4 py-3 w-10">Select</th><th class="px-4 py-3">Ledger</th><th class="px-4 py-3 text-right">Opening WDV</th><th class="px-4 py-3 text-right">Additions ≥180 days</th><th class="px-4 py-3 text-right">Additions &lt;180 days</th><th class="px-4 py-3 text-right">Other Reductions</th><th class="px-4 py-3 text-right">Depreciation Already Recorded</th><th class="px-4 py-3">Rate</th><th class="px-4 py-3">Posting Ledger</th></tr></thead><tbody class="divide-y divide-slate-100">
{% for row in preview.fixed_asset_ledgers %} {% for row in preview.fixed_asset_ledgers %}
<tr> <tr>
<td class="px-4 py-3"><input type="checkbox" class="asset-select rounded border-slate-300" data-ledger-key="{{ row.ledger_key }}"></td> <td class="px-4 py-3"><input type="checkbox" class="asset-select rounded border-slate-300" data-ledger-key="{{ row.ledger_key }}"></td>
@@ -69,9 +69,11 @@
<td class="px-4 py-3 text-right">{{ '%.2f'|format(row.additions_full_rate) }}</td> <td class="px-4 py-3 text-right">{{ '%.2f'|format(row.additions_full_rate) }}</td>
<td class="px-4 py-3 text-right">{{ '%.2f'|format(row.additions_half_rate) }}</td> <td class="px-4 py-3 text-right">{{ '%.2f'|format(row.additions_half_rate) }}</td>
<td class="px-4 py-3 text-right">{{ '%.2f'|format(row.reductions) }}</td> <td class="px-4 py-3 text-right">{{ '%.2f'|format(row.reductions) }}</td>
<td class="px-4 py-3 text-right">{% if row.depreciation_already_recorded %}<div class="font-semibold text-emerald-700">{{ '%.2f'|format(row.recorded_depreciation) }}</div><div class="text-[11px] text-slate-500">{{ row.recorded_depreciation_voucher_count }} voucher(s)</div>{% else %}<span class="text-slate-400">—</span>{% endif %}</td>
<td class="px-4 py-3"><select id="rate-{{ loop.index0 }}" data-ledger-key="{{ row.ledger_key }}" name="rate__{{ row.ledger_key }}" class="asset-rate rounded-lg border border-slate-300 px-2 py-1.5"><option value="0" {% if row.rate_percent == 0 %}selected{% endif %}>0%</option>{% for rate in [5,10,15,20,25,30,40,45,60,80,100] %}<option value="{{ rate }}" {% if row.rate_percent == rate %}selected{% endif %}>{{ rate }}%</option>{% endfor %}</select></td> <td class="px-4 py-3"><select id="rate-{{ loop.index0 }}" data-ledger-key="{{ row.ledger_key }}" name="rate__{{ row.ledger_key }}" class="asset-rate rounded-lg border border-slate-300 px-2 py-1.5"><option value="0" {% if row.rate_percent == 0 %}selected{% endif %}>0%</option>{% for rate in [5,10,15,20,25,30,40,45,60,80,100] %}<option value="{{ rate }}" {% if row.rate_percent == rate %}selected{% endif %}>{{ rate }}%</option>{% endfor %}</select></td>
<td class="px-4 py-3"><button type="button" class="use-asset-as-debit text-xs font-semibold text-brand-700 underline underline-offset-2" data-ledger-name="{{ row.ledger_name }}">Use as debit ledger</button></td>
</tr> </tr>
{% else %}<tr><td colspan="7" class="px-4 py-8 text-center text-slate-500">No active Fixed Asset ledgers were found. Zero-opening ledgers without additions are intentionally omitted.</td></tr>{% endfor %} {% else %}<tr><td colspan="9" class="px-4 py-8 text-center text-slate-500">No active Fixed Asset ledgers were found. Zero-opening ledgers without additions are intentionally omitted.</td></tr>{% endfor %}
</tbody></table></div> </tbody></table></div>
<div class="flex justify-end border-t border-slate-200 px-5 py-4"><button type="submit" {% if not preview.fixed_asset_ledgers %}disabled{% endif %} class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-50">Calculate & Save Draft</button></div> <div class="flex justify-end border-t border-slate-200 px-5 py-4"><button type="submit" {% if not preview.fixed_asset_ledgers %}disabled{% endif %} class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white disabled:cursor-not-allowed disabled:opacity-50">Calculate & Save Draft</button></div>
</section> </section>
@@ -94,7 +96,7 @@
</div> </div>
{% if depreciation_run.tally_posting_error %}<div class="mt-3 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">{{ depreciation_run.tally_posting_error }}</div>{% endif %} {% if depreciation_run.tally_posting_error %}<div class="mt-3 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">{{ depreciation_run.tally_posting_error }}</div>{% endif %}
<div class="mt-4 overflow-x-auto"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left text-xs uppercase text-slate-500"><tr><th class="px-3 py-2">Ledger</th><th class="px-3 py-2 text-right">Rate</th><th class="px-3 py-2 text-right">Depreciation</th><th class="px-3 py-2 text-right">Closing WDV</th></tr></thead><tbody class="divide-y divide-slate-100">{% for row in depreciation_run.lines %}<tr><td class="px-3 py-2">{{ row.ledger_name }}</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.rate_percent) }}%</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.depreciation_amount) }}</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.closing_wdv) }}</td></tr>{% endfor %}</tbody></table></div> <div class="mt-4 overflow-x-auto"><table class="min-w-full text-sm"><thead class="bg-slate-50 text-left text-xs uppercase text-slate-500"><tr><th class="px-3 py-2">Ledger</th><th class="px-3 py-2 text-right">Rate</th><th class="px-3 py-2 text-right">Calculated</th><th class="px-3 py-2 text-right">Already Recorded</th><th class="px-3 py-2 text-right">Unrecorded</th><th class="px-3 py-2 text-right">Closing WDV</th></tr></thead><tbody class="divide-y divide-slate-100">{% for row in depreciation_run.lines %}<tr><td class="px-3 py-2">{{ row.ledger_name }}</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.rate_percent) }}%</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.depreciation_amount) }}</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.recorded_depreciation or 0) }}</td><td class="px-3 py-2 text-right font-semibold">{{ '%.2f'|format(row.remaining_unrecorded_depreciation if row.remaining_unrecorded_depreciation is defined else row.depreciation_amount) }}</td><td class="px-3 py-2 text-right">{{ '%.2f'|format(row.closing_wdv) }}</td></tr>{% endfor %}</tbody></table></div>
{% if depreciation_run.status == 'draft' %} {% if depreciation_run.status == 'draft' %}
<form method="post" action="/tools/tally/depreciation/approve" class="mt-5 rounded-xl border border-amber-200 bg-amber-50 p-4"> <form method="post" action="/tools/tally/depreciation/approve" class="mt-5 rounded-xl border border-amber-200 bg-amber-50 p-4">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}"><input type="hidden" name="run_id" value="{{ depreciation_run.id }}"><input type="hidden" name="fy_start" value="{{ depreciation_run.fy_start }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input type="hidden" name="client_id" value="{{ selected_client.id }}"><input type="hidden" name="run_id" value="{{ depreciation_run.id }}"><input type="hidden" name="fy_start" value="{{ depreciation_run.fy_start }}">
@@ -155,4 +157,19 @@
<div class="mt-3 overflow-x-auto"><table class="min-w-full text-sm"><thead><tr class="text-left text-xs uppercase text-slate-500"><th class="p-2">Run</th><th class="p-2">FY</th><th class="p-2">Period</th><th class="p-2">Completed</th><th class="p-2"></th></tr></thead><tbody>{% for row in analysis_history %}<tr class="border-t"><td class="p-2">#{{ row.id }}</td><td class="p-2">{{ row.financial_year }}</td><td class="p-2">{{ row.period_from }} → {{ row.period_to }}</td><td class="p-2">{{ row.completed_at_utc }}</td><td class="p-2 text-right"><a class="font-semibold text-brand-700" href="/tools/tally/analysis/{{ row.id }}/download?client_id={{ selected_client.id }}&financial_year={{ row.financial_year }}&format=xlsx">Excel</a> · <a class="font-semibold text-brand-700" href="/tools/tally/analysis/{{ row.id }}/download?client_id={{ selected_client.id }}&financial_year={{ row.financial_year }}&format=pdf">PDF</a></td></tr>{% endfor %}</tbody></table></div> <div class="mt-3 overflow-x-auto"><table class="min-w-full text-sm"><thead><tr class="text-left text-xs uppercase text-slate-500"><th class="p-2">Run</th><th class="p-2">FY</th><th class="p-2">Period</th><th class="p-2">Completed</th><th class="p-2"></th></tr></thead><tbody>{% for row in analysis_history %}<tr class="border-t"><td class="p-2">#{{ row.id }}</td><td class="p-2">{{ row.financial_year }}</td><td class="p-2">{{ row.period_from }} → {{ row.period_to }}</td><td class="p-2">{{ row.completed_at_utc }}</td><td class="p-2 text-right"><a class="font-semibold text-brand-700" href="/tools/tally/analysis/{{ row.id }}/download?client_id={{ selected_client.id }}&financial_year={{ row.financial_year }}&format=xlsx">Excel</a> · <a class="font-semibold text-brand-700" href="/tools/tally/analysis/{{ row.id }}/download?client_id={{ selected_client.id }}&financial_year={{ row.financial_year }}&format=pdf">PDF</a></td></tr>{% endfor %}</tbody></table></div>
</section> </section>
{% endif %} {% endif %}
<script>
document.addEventListener("DOMContentLoaded",function(){
const debit=document.getElementById("depreciation-debit-ledger");
document.querySelectorAll(".use-asset-as-debit").forEach(function(button){
button.addEventListener("click",function(){
if(!debit)return;
debit.value=button.dataset.ledgerName||"";
debit.scrollIntoView({behavior:"smooth",block:"center"});
debit.focus();
});
});
});
</script>
{% endblock %} {% endblock %}
+24 -10
View File
@@ -810,9 +810,10 @@ def depreciation_it_tool(
node = get_active_storage_node_for_branch(db, scope.tenant_id, scope.branch_id) node = get_active_storage_node_for_branch(db, scope.tenant_id, scope.branch_id)
online = _node_online(node) online = _node_online(node)
today = date.today() today = date.today()
start_year = today.year if today.month >= 4 else today.year - 1 active_start_year = today.year if today.month >= 4 else today.year - 1
start_text = fy_start or date(start_year, 4, 1).isoformat() explicit_period = bool(str(fy_start or "").strip() and str(fy_end or "").strip())
end_text = fy_end or date(start_year + 1, 3, 31).isoformat() start_text = fy_start or date(active_start_year, 4, 1).isoformat()
end_text = fy_end or date(active_start_year + 1, 3, 31).isoformat()
preview = None preview = None
depreciation_run = None depreciation_run = None
mirror_status = None mirror_status = None
@@ -820,17 +821,30 @@ def depreciation_it_tool(
analysis_history = [] analysis_history = []
if selected_client and node and online: if selected_client and node and online:
storage_payload = _accounting_storage_payload( selected_fy=_financial_year_for_date(date.fromisoformat(start_text))
selected_client, _financial_year_for_date(date.fromisoformat(start_text)) storage_payload=_accounting_storage_payload(selected_client,selected_fy)
)
try: try:
status_response = request_agent_command( status_response=request_agent_command(
node.node_code, "accounting_mirror_status", storage_payload, timeout_seconds=20 node.node_code,"accounting_mirror_status",storage_payload,timeout_seconds=20
) )
if status_response.get("ok"): if status_response.get("ok"):
mirror_status = status_response.get("result") or {} mirror_status=status_response.get("result") or {}
else: else:
command_error = str(status_response.get("error") or "Accounting Mirror status failed.") command_error=str(status_response.get("error") or "Accounting Mirror status failed.")
if not explicit_period and not ((mirror_status or {}).get("mirror") or {}).get("ready_for_financial_year"):
prior_start_year=active_start_year-1
prior_start=date(prior_start_year,4,1)
prior_end=date(prior_start_year+1,3,31)
prior_fy=_financial_year_for_date(prior_start)
prior_payload=_accounting_storage_payload(selected_client,prior_fy)
prior_response=request_agent_command(
node.node_code,"accounting_mirror_status",prior_payload,timeout_seconds=20
)
prior_status=(prior_response.get("result") or {}) if prior_response.get("ok") else {}
if ((prior_status.get("mirror") or {}).get("ready_for_financial_year")):
start_text=prior_start.isoformat(); end_text=prior_end.isoformat()
storage_payload=prior_payload; mirror_status=prior_status
if mirror_status and (mirror_status.get("mirror") or {}).get("ready_for_financial_year"): if mirror_status and (mirror_status.get("mirror") or {}).get("ready_for_financial_year"):
preview_response = request_agent_command( preview_response = request_agent_command(
+1 -1
View File
@@ -4,7 +4,7 @@ import io
from pathlib import Path from pathlib import Path
import zipfile import zipfile
ERP_LOCAL_AGENT_VERSION = "1.25.5" ERP_LOCAL_AGENT_VERSION = "1.25.6"
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.25.5" __version__ = "1.25.6"
AGENT_NAME = "ERP Local Agent" AGENT_NAME = "ERP Local Agent"
@@ -968,6 +968,61 @@ class LocalAccountingStore:
"provisional": fy_end > today, "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: def depreciation_preview(self, client_id: int, *, tally_guid: str, fy_start: str, fy_end: str) -> dict:
if not self.exists(client_id): if not self.exists(client_id):
raise ValueError("Accounting storage is not initialized for this client.") 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')""", AND lower(COALESCE(v.is_optional,'')) NOT IN ('yes','true','1')""",
(tally_guid,name,start.isoformat(),movement_end.isoformat()), (tally_guid,name,start.isoformat(),movement_end.isoformat()),
).fetchall() ).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: for e in entries:
amt=abs(float(e["amount"] or 0)) amt=abs(float(e["amount"] or 0))
if self._is_debit_entry(e): if self._is_debit_entry(e):
if self._half_rate_addition(str(e["movement_date"] or ""), end): half += amt if self._half_rate_addition(str(e["movement_date"] or ""), end): half += amt
else: full += 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)) opening_wdv = abs(float(row["opening_balance"] or 0))
tally_closing = abs(float(row["closing_balance"] or 0)) tally_closing = abs(float(row["closing_balance"] or 0))
additions_total = round(full + half, 2) 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 ""), "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, "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), "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), "rate_percent":rules.get(key,0.0),
"movement_entries":len(entries), "movement_entries":len(entries),
}) })
@@ -1050,7 +1116,12 @@ class LocalAccountingStore:
full_base=max(0.0, opening+full-reductions) full_base=max(0.0, opening+full-reductions)
dep=min(closing_before, (full_base*rate/100.0)+(half*rate/200.0)) 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 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) lines.append(line)
mapping=preview["mapping"] mapping=preview["mapping"]
with self.connect(client_id) as db: 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) 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(?,?,?,?,?,?,?,?,?,?,?,?,?,?)""", 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=(",",":")))) (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: def get_it_depreciation_run(self, client_id: int, run_id: int) -> dict:
with self.connect(client_id) as db: with self.connect(client_id) as db:
@@ -3164,22 +3164,27 @@ class AgentCommandProcessor:
if not value: if not value:
return False return False
exact = { exact = {
"capital account", "capital account","capital accounts","partners capital","partner capital",
"capital accounts", "partners capital accounts","partner capital accounts","proprietors capital",
"partners capital", "proprietor capital","owners capital","owner capital","share capital",
"partner capital", "reserves surplus","reserves and surplus","partners current account",
"partners capital accounts", "partner current account","partners current accounts","partner current accounts",
"partner capital accounts", "proprietors current account","proprietor current account",
"proprietors capital", "owners current account","owner current account",
"proprietor capital",
"owners capital",
"owner capital",
"share capital",
"reserves surplus",
"reserves and surplus",
} }
if value in exact: if value in exact:
return True 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 ( return (
value.startswith("capital account ") value.startswith("capital account ")
or value.endswith(" capital account") or value.endswith(" capital account")