Validate FY mirrors and repair depreciation and TDS review

This commit is contained in:
A R R R Associates
2026-09-06 20:49:42 +05:30
parent 739973e370
commit eb83e5418d
12 changed files with 166 additions and 22 deletions
+2 -2
View File
@@ -203,13 +203,13 @@ def page(
# the ERP header is now FY2026-27.
if not requested_fy:
active_status, active_response = mirror_status_for(active_fy)
active_ready = bool((active_status.get("mirror") or {}).get("ready"))
active_ready = bool((active_status.get("mirror") or {}).get("ready_for_financial_year"))
if active_ready:
current_fy = active_fy
else:
prior_active_fy = _previous_financial_year(active_fy)
prior_status, prior_response = mirror_status_for(prior_active_fy)
prior_ready = bool((prior_status.get("mirror") or {}).get("ready"))
prior_ready = bool((prior_status.get("mirror") or {}).get("ready_for_financial_year"))
if prior_ready:
current_fy = prior_active_fy
else:
@@ -18,7 +18,7 @@
<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">SQLite Mirror</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if mirror.ready else 'text-amber-700' }}">{{ 'Ready' if mirror.ready else 'Mirror required' }}</div><div class="mt-1 text-xs text-slate-500">{{ mirror.company.company_name if mirror.ready 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><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">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>
@@ -122,8 +122,6 @@
{% endif %}
</section>
{% endif %}
<div class="rounded-xl border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-800">Phase 6 enables only controlled depreciation Journal write-back. All master and transaction synchronization remain read-only; no other Tally create/alter/delete operation is enabled.</div>
</div>
<script>
@@ -46,22 +46,28 @@
{% if selected_client %}
{% set prev = previous_mirror_status.mirror if previous_mirror_status and previous_mirror_status.mirror else {} %}
{% set curr = current_mirror_status.mirror if current_mirror_status and current_mirror_status.mirror else {} %}
{% set prev_ready = prev.ready_for_financial_year %}
{% set curr_ready = curr.ready_for_financial_year %}
<div class="mt-5 grid gap-3 md:grid-cols-2">
<div class="rounded-xl border p-4 {{ 'border-emerald-200 bg-emerald-50' if prev.ready else 'border-amber-200 bg-amber-50' }}">
<div class="rounded-xl border p-4 {{ 'border-emerald-200 bg-emerald-50' if prev_ready else 'border-amber-200 bg-amber-50' }}">
<div class="text-xs font-semibold uppercase text-slate-500">Previous FY Mirror · FY {{ previous_fy }}</div>
<div class="mt-1 font-semibold">{{ 'Ready' if prev.ready else 'Mirror required' }}</div>
{% if prev.ready %}
<div class="mt-1 font-semibold">{{ 'Ready' if prev_ready else 'Mirror required' }}</div>
{% if prev_ready %}
<div class="mt-1 text-xs text-slate-600">{{ prev.company.company_name }} · {{ prev.counts.voucher }} vouchers</div>
<div class="mt-1 break-all text-[11px] text-slate-500">{{ previous_mirror_status.mirror_db_path or prev.path or '' }}</div>
{% elif prev.ready and prev.financial_year_reason %}
<div class="mt-1 text-xs text-amber-800">{{ prev.financial_year_reason }}</div>
{% endif %}
</div>
<div class="rounded-xl border p-4 {{ 'border-emerald-200 bg-emerald-50' if curr.ready else 'border-amber-200 bg-amber-50' }}">
<div class="rounded-xl border p-4 {{ 'border-emerald-200 bg-emerald-50' if curr_ready else 'border-amber-200 bg-amber-50' }}">
<div class="text-xs font-semibold uppercase text-slate-500">Current FY Mirror · FY {{ current_fy }}</div>
<div class="mt-1 font-semibold">{{ 'Ready' if curr.ready else 'Mirror required' }}</div>
{% if curr.ready %}
<div class="mt-1 font-semibold">{{ 'Ready' if curr_ready else 'Mirror required' }}</div>
{% if curr_ready %}
<div class="mt-1 text-xs text-slate-600">{{ curr.company.company_name }} · {{ curr.counts.voucher }} vouchers</div>
<div class="mt-1 break-all text-[11px] text-slate-500">{{ current_mirror_status.mirror_db_path or curr.path or '' }}</div>
{% elif curr.ready and curr.financial_year_reason %}
<div class="mt-1 text-xs text-amber-800">{{ curr.financial_year_reason }}</div>
{% endif %}
</div>
</div>
@@ -70,7 +76,7 @@
<input type="hidden" name="client_id" value="{{ selected_client.id }}">
<input type="hidden" name="previous_fy" value="{{ previous_fy }}">
<input type="hidden" name="current_fy" value="{{ current_fy }}">
<button class="rounded-xl bg-slate-900 px-5 py-2 text-sm font-semibold text-white" {% if not node_online or not prev.ready or not curr.ready %}disabled{% endif %}>Compare Opening Balances</button>
<button class="rounded-xl bg-slate-900 px-5 py-2 text-sm font-semibold text-white" {% if not node_online or not prev_ready or not curr_ready %}disabled{% endif %}>Compare Opening Balances</button>
</form>
{% endif %}
</section>
@@ -1,5 +1,35 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="space-y-6">
<div class="flex flex-wrap items-start justify-between gap-3"><div><p class="text-xs font-semibold uppercase tracking-[0.16em] text-violet-600">TDS &amp; Withholding Tax</p><h1 class="mt-1 text-2xl font-semibold text-slate-900">TDS Compliance Review</h1><p class="mt-1 text-sm text-slate-500">Review expenses, purchases, journals and creditor postings from the client SQLite Accounting Mirror. Rules and thresholds come from the editable TDS Rule Master.</p></div><div class="flex gap-2"><a href="/tools/accounting/tds/rules" class="rounded-xl border px-3 py-2 text-sm font-semibold">Rule Master</a><a href="/tools/tally" class="rounded-xl border px-3 py-2 text-sm font-semibold">Tally</a></div></div>{% if error %}<div class="rounded-xl bg-red-50 p-3 text-sm text-red-800">{{ error }}</div>{% endif %}<form method="get" class="rounded-2xl bg-white p-5 shadow-soft"><div class="grid gap-3 lg:grid-cols-3"><label class="text-sm">Client<select name="client_id" required class="mt-1 w-full rounded-xl border px-3 py-2"><option value="">Select client</option>{% for c in clients %}<option value="{{ c.id }}" {% if selected_client and c.id==selected_client.id %}selected{% endif %}>{{ c.client_name }}</option>{% endfor %}</select></label><label class="text-sm">From<input type="date" name="date_from" value="{{ date_from }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">To<input type="date" name="date_to" value="{{ date_to }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label></div><input type="hidden" name="run" value="1"><button class="mt-4 rounded-xl bg-violet-700 px-4 py-2 text-sm font-semibold text-white">Run TDS Review</button></form>{% if review %}<div class="flex justify-end gap-2"><a href="/tools/tally/analysis/{{ review.analysis_run_id }}/download?client_id={{ selected_client.id }}&financial_year={{ date_from[:4] }}-{{ date_to[2:4] }}&format=xlsx" class="rounded-xl border border-emerald-300 bg-emerald-50 px-3 py-2 text-sm font-semibold text-emerald-800">Download Excel</a><a href="/tools/tally/analysis/{{ review.analysis_run_id }}/download?client_id={{ selected_client.id }}&financial_year={{ date_from[:4] }}-{{ date_to[2:4] }}&format=pdf" class="rounded-xl border px-3 py-2 text-sm font-semibold">Download PDF</a></div><div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-5">{% for k,v in review.summary.items() %}<div class="rounded-xl bg-white p-4 shadow-soft"><div class="text-xs uppercase text-slate-500">{{ k|replace('_',' ') }}</div><div class="mt-1 text-xl font-semibold">{{ v }}</div></div>{% endfor %}</div><div class="overflow-x-auto rounded-2xl bg-white shadow-soft"><table class="min-w-full text-sm"><thead class="bg-slate-50"><tr><th class="p-3 text-left">Date</th><th class="p-3 text-left">Party</th><th class="p-3 text-left">Ledger / Rule</th><th class="p-3 text-right">Base</th><th class="p-3 text-right">Expected</th><th class="p-3 text-right">Actual</th><th class="p-3 text-right">Liability</th><th class="p-3 text-left">Status</th></tr></thead><tbody>{% for x in review.transactions %}<tr class="border-t"><td class="p-3">{{ x.date }}</td><td class="p-3">{{ x.party }}</td><td class="p-3">{{ x.nature_ledger }}<div class="text-xs text-slate-500">{{ x.rule_name }} · {{ x.legacy_section }}</div></td><td class="p-3 text-right">{{ '%.2f'|format(x.taxable_base) }}</td><td class="p-3 text-right">{{ '%.2f'|format(x.expected_tds) }}</td><td class="p-3 text-right">{{ '%.2f'|format(x.actual_tds) }}</td><td class="p-3 text-right font-semibold">{{ '%.2f'|format(x.liability) }}</td><td class="p-3">{{ x.status }}</td></tr>{% endfor %}</tbody></table></div>{% endif %}{% if history %}<div class="rounded-2xl bg-white p-5 shadow-soft"><h2 class="font-semibold">TDS Analysis History</h2><div class="mt-3 overflow-x-auto"><table class="min-w-full text-sm"><tbody>{% for row in 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 text-right"><a class="font-semibold text-violet-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-violet-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>{% endif %}</div>
<div class="flex flex-wrap items-start justify-between gap-3"><div><p class="text-xs font-semibold uppercase tracking-[0.16em] text-brand-600">TDS &amp; Withholding Tax</p><h1 class="mt-1 text-2xl font-semibold text-slate-900">TDS Compliance Review</h1><p class="mt-1 text-sm text-slate-500">Review expenses, purchases, journals and creditor postings from the client SQLite Accounting Mirror. Rules and thresholds come from the editable TDS Rule Master.</p></div><div class="flex gap-2"><a href="/tools/accounting/tds/rules" class="rounded-xl border px-3 py-2 text-sm font-semibold">Rule Master</a><a href="/tools/tally" class="rounded-xl border px-3 py-2 text-sm font-semibold">Tally</a></div></div>{% if error %}<div class="rounded-xl bg-red-50 p-3 text-sm text-red-800">{{ error }}</div>{% endif %}<form method="get" class="rounded-2xl bg-white p-5 shadow-soft"><div class="grid gap-3 lg:grid-cols-3"><label class="text-sm">Client<select name="client_id" required class="mt-1 w-full rounded-xl border px-3 py-2"><option value="">Select client</option>{% for c in clients %}<option value="{{ c.id }}" {% if selected_client and c.id==selected_client.id %}selected{% endif %}>{{ c.client_name }}</option>{% endfor %}</select></label><label class="text-sm">From<input type="date" name="date_from" value="{{ date_from }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">To<input type="date" name="date_to" value="{{ date_to }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label></div><input type="hidden" name="run" value="1"><button class="mt-4 rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Run TDS Review</button></form>{% if review %}<div class="flex justify-end gap-2"><a href="/tools/tally/analysis/{{ review.analysis_run_id }}/download?client_id={{ selected_client.id }}&financial_year={{ date_from[:4] }}-{{ date_to[2:4] }}&format=xlsx" class="rounded-xl border border-emerald-300 bg-emerald-50 px-3 py-2 text-sm font-semibold text-emerald-800">Download Excel</a><a href="/tools/tally/analysis/{{ review.analysis_run_id }}/download?client_id={{ selected_client.id }}&financial_year={{ date_from[:4] }}-{{ date_to[2:4] }}&format=pdf" class="rounded-xl border px-3 py-2 text-sm font-semibold">Download PDF</a></div><div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-5">{% for k,v in review.summary.items() %}<div class="rounded-xl bg-white p-4 shadow-soft"><div class="text-xs uppercase text-slate-500">{{ k|replace('_',' ') }}</div><div class="mt-1 text-xl font-semibold">{{ v }}</div></div>{% endfor %}</div><div class="overflow-x-auto rounded-2xl bg-white shadow-soft">
<div class="border-b border-slate-100 px-4 py-3"><h2 class="font-semibold text-slate-900">TDS Category Summary</h2><p class="mt-1 text-xs text-slate-500">Rule/category-wise totals for quick review before transaction-level exceptions.</p></div>
<table class="min-w-full text-sm">
<thead class="bg-slate-50">
<tr>
<th class="p-3 text-left">Category / Section</th>
<th class="p-3 text-right">Transactions</th>
<th class="p-3 text-right">Taxable Base</th>
<th class="p-3 text-right">Expected TDS</th>
<th class="p-3 text-right">Actual TDS</th>
<th class="p-3 text-right">Liability</th>
<th class="p-3 text-right">Not Deducted</th>
<th class="p-3 text-right">Short</th>
</tr>
</thead>
<tbody>
{% for row in review.category_summary %}
<tr class="border-t">
<td class="p-3"><div class="font-medium">{{ row.category }}</div><div class="text-xs text-slate-500">{{ row.legacy_section }}{% if row.statutory_reference %} · {{ row.statutory_reference }}{% endif %}</div></td>
<td class="p-3 text-right">{{ row.transactions }}</td>
<td class="p-3 text-right">{{ '%.2f'|format(row.taxable_base) }}</td>
<td class="p-3 text-right">{{ '%.2f'|format(row.expected_tds) }}</td>
<td class="p-3 text-right">{{ '%.2f'|format(row.actual_tds) }}</td>
<td class="p-3 text-right font-semibold">{{ '%.2f'|format(row.liability) }}</td>
<td class="p-3 text-right">{{ row.tds_not_deducted }}</td>
<td class="p-3 text-right">{{ row.short_deduction }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div><div class="overflow-x-auto rounded-2xl bg-white shadow-soft"><table class="min-w-full text-sm"><thead class="bg-slate-50"><tr><th class="p-3 text-left">Date</th><th class="p-3 text-left">Party</th><th class="p-3 text-left">Ledger / Rule</th><th class="p-3 text-right">Base</th><th class="p-3 text-right">Expected</th><th class="p-3 text-right">Actual</th><th class="p-3 text-right">Liability</th><th class="p-3 text-left">Status</th></tr></thead><tbody>{% for x in review.transactions %}<tr class="border-t"><td class="p-3">{{ x.date }}</td><td class="p-3">{{ x.party }}</td><td class="p-3">{{ x.nature_ledger }}<div class="text-xs text-slate-500">{{ x.rule_name }} · {{ x.legacy_section }}</div></td><td class="p-3 text-right">{{ '%.2f'|format(x.taxable_base) }}</td><td class="p-3 text-right">{{ '%.2f'|format(x.expected_tds) }}</td><td class="p-3 text-right">{{ '%.2f'|format(x.actual_tds) }}</td><td class="p-3 text-right font-semibold">{{ '%.2f'|format(x.liability) }}</td><td class="p-3">{{ x.status }}</td></tr>{% endfor %}</tbody></table></div>{% endif %}{% if history %}<div class="rounded-2xl bg-white p-5 shadow-soft"><h2 class="font-semibold">TDS Analysis History</h2><div class="mt-3 overflow-x-auto"><table class="min-w-full text-sm"><tbody>{% for row in 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 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>{% endif %}</div>
{% endblock %}
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="space-y-6">
<div class="flex flex-wrap items-start justify-between gap-3"><div><p class="text-xs font-semibold uppercase tracking-[0.16em] text-violet-600">TDS &amp; Withholding Tax</p><h1 class="mt-1 text-2xl font-semibold text-slate-900">TDS Reconciliation</h1><p class="mt-1 text-sm text-slate-500">Compare live Tally TDS with return/form, challan and TRACES totals. Paste exports as one row per line: PAN, section/code, amount.</p></div><a href="/tools/tally" class="rounded-xl border px-3 py-2 text-sm font-semibold">Tally</a></div>{% if error %}<div class="rounded-xl bg-red-50 p-3 text-sm text-red-800">{{ error }}</div>{% endif %}<form method="post" class="rounded-2xl bg-white p-5 shadow-soft"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><div class="grid gap-3 lg:grid-cols-4"><label class="text-sm">Client<select name="client_id" required class="mt-1 w-full rounded-xl border px-3 py-2"><option value="">Select client</option>{% for c in clients %}<option value="{{ c.id }}" {% if selected_client and c.id==selected_client.id %}selected{% endif %}>{{ c.client_name }}</option>{% endfor %}</select></label><label class="text-sm">Tally GUID<input name="tally_guid" value="{{ tally_guid or '' }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">From<input type="date" name="date_from" value="{{ date_from or '' }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">To<input type="date" name="date_to" value="{{ date_to or '' }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label></div><div class="mt-4 grid gap-3 lg:grid-cols-3"><label class="text-sm">TDS return / form export<textarea name="return_data" rows="8" class="mt-1 w-full rounded-xl border p-2 font-mono text-xs">{{ return_data or '' }}</textarea></label><label class="text-sm">Challan export<textarea name="challan_data" rows="8" class="mt-1 w-full rounded-xl border p-2 font-mono text-xs">{{ challan_data or '' }}</textarea></label><label class="text-sm">TRACES export<textarea name="traces_data" rows="8" class="mt-1 w-full rounded-xl border p-2 font-mono text-xs">{{ traces_data or '' }}</textarea></label></div><button class="mt-4 rounded-xl bg-violet-700 px-4 py-2 text-sm font-semibold text-white">Run Reconciliation</button></form>{% if result %}<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-5">{% for label,value in [('Tally booked TDS',result.tally_actual),('Expected TDS',result.tally_expected),('Return / Forms',result.return_total),('Challans',result.challan_total),('TRACES',result.traces_total)] %}<div class="rounded-xl bg-white p-4 shadow-soft"><div class="text-xs uppercase text-slate-500">{{ label }}</div><div class="mt-1 text-xl font-semibold">₹{{ '%.2f'|format(value) }}</div></div>{% endfor %}</div><div class="grid gap-3 md:grid-cols-3"><div class="rounded-xl bg-white p-4 shadow-soft">Tally − Return: <b>₹{{ '%.2f'|format(result.tally_vs_return) }}</b></div><div class="rounded-xl bg-white p-4 shadow-soft">Return − Challan: <b>₹{{ '%.2f'|format(result.return_vs_challan) }}</b></div><div class="rounded-xl bg-white p-4 shadow-soft">Return − TRACES: <b>₹{{ '%.2f'|format(result.return_vs_traces) }}</b></div></div>{% endif %}</div>
<div class="flex flex-wrap items-start justify-between gap-3"><div><p class="text-xs font-semibold uppercase tracking-[0.16em] text-brand-600">TDS &amp; Withholding Tax</p><h1 class="mt-1 text-2xl font-semibold text-slate-900">TDS Reconciliation</h1><p class="mt-1 text-sm text-slate-500">Compare live Tally TDS with return/form, challan and TRACES totals. Paste exports as one row per line: PAN, section/code, amount.</p></div><a href="/tools/tally" class="rounded-xl border px-3 py-2 text-sm font-semibold">Tally</a></div>{% if error %}<div class="rounded-xl bg-red-50 p-3 text-sm text-red-800">{{ error }}</div>{% endif %}<form method="post" class="rounded-2xl bg-white p-5 shadow-soft"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><div class="grid gap-3 lg:grid-cols-4"><label class="text-sm">Client<select name="client_id" required class="mt-1 w-full rounded-xl border px-3 py-2"><option value="">Select client</option>{% for c in clients %}<option value="{{ c.id }}" {% if selected_client and c.id==selected_client.id %}selected{% endif %}>{{ c.client_name }}</option>{% endfor %}</select></label><label class="text-sm">Tally GUID<input name="tally_guid" value="{{ tally_guid or '' }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">From<input type="date" name="date_from" value="{{ date_from or '' }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">To<input type="date" name="date_to" value="{{ date_to or '' }}" required class="mt-1 w-full rounded-xl border px-3 py-2"></label></div><div class="mt-4 grid gap-3 lg:grid-cols-3"><label class="text-sm">TDS return / form export<textarea name="return_data" rows="8" class="mt-1 w-full rounded-xl border p-2 font-mono text-xs">{{ return_data or '' }}</textarea></label><label class="text-sm">Challan export<textarea name="challan_data" rows="8" class="mt-1 w-full rounded-xl border p-2 font-mono text-xs">{{ challan_data or '' }}</textarea></label><label class="text-sm">TRACES export<textarea name="traces_data" rows="8" class="mt-1 w-full rounded-xl border p-2 font-mono text-xs">{{ traces_data or '' }}</textarea></label></div><button class="mt-4 rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Run Reconciliation</button></form>{% if result %}<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-5">{% for label,value in [('Tally booked TDS',result.tally_actual),('Expected TDS',result.tally_expected),('Return / Forms',result.return_total),('Challans',result.challan_total),('TRACES',result.traces_total)] %}<div class="rounded-xl bg-white p-4 shadow-soft"><div class="text-xs uppercase text-slate-500">{{ label }}</div><div class="mt-1 text-xl font-semibold">₹{{ '%.2f'|format(value) }}</div></div>{% endfor %}</div><div class="grid gap-3 md:grid-cols-3"><div class="rounded-xl bg-white p-4 shadow-soft">Tally − Return: <b>₹{{ '%.2f'|format(result.tally_vs_return) }}</b></div><div class="rounded-xl bg-white p-4 shadow-soft">Return − Challan: <b>₹{{ '%.2f'|format(result.return_vs_challan) }}</b></div><div class="rounded-xl bg-white p-4 shadow-soft">Return − TRACES: <b>₹{{ '%.2f'|format(result.return_vs_traces) }}</b></div></div>{% endif %}</div>
{% endblock %}
@@ -1,7 +1,7 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
<div class="space-y-6">
<div class="flex flex-wrap items-start justify-between gap-3"><div><p class="text-xs font-semibold uppercase tracking-[0.16em] text-violet-600">TDS &amp; Withholding Tax</p><h1 class="mt-1 text-2xl font-semibold text-slate-900">TDS Rule Master</h1><p class="mt-1 text-sm text-slate-500">Default rules are seeded for each firm and remain editable. Historical effective-date versions can be retained instead of hard-coding thresholds in Python.</p></div><a href="/tools/tally" class="rounded-xl border px-3 py-2 text-sm font-semibold">Back to Tally</a></div>
<div class="flex flex-wrap items-start justify-between gap-3"><div><p class="text-xs font-semibold uppercase tracking-[0.16em] text-brand-600">TDS &amp; Withholding Tax</p><h1 class="mt-1 text-2xl font-semibold text-slate-900">TDS Rule Master</h1><p class="mt-1 text-sm text-slate-500">Default rules are seeded for each firm and remain editable. Historical effective-date versions can be retained instead of hard-coding thresholds in Python.</p></div><a href="/tools/tally" class="rounded-xl border px-3 py-2 text-sm font-semibold">Back to Tally</a></div>
{% if saved %}<div class="rounded-xl bg-emerald-50 p-3 text-sm text-emerald-800">Rule saved.</div>{% endif %}{% if error %}<div class="rounded-xl bg-red-50 p-3 text-sm text-red-800">{{ error }}</div>{% endif %}
<div class="space-y-4">{% for r in rules %}<form method="post" action="/tools/accounting/tds/rules/{{ r.id }}" class="rounded-2xl bg-white p-5 shadow-soft"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><div class="grid gap-3 lg:grid-cols-4"><label class="text-sm">Rule name<input name="name" value="{{ r.name }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">New Act reference<input name="statutory_reference" value="{{ r.statutory_reference }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">Legacy section<input name="legacy_section" value="{{ r.legacy_section or '' }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">Threshold mode<select name="threshold_mode" class="mt-1 w-full rounded-xl border px-3 py-2">{% for m in ['ANNUAL','SINGLE','BOTH','MONTHLY'] %}<option {% if r.threshold_mode==m %}selected{% endif %}>{{ m }}</option>{% endfor %}</select></label><label class="text-sm">Single / monthly threshold<input name="single_threshold" value="{{ r.single_threshold }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">Annual aggregate threshold<input name="aggregate_threshold" value="{{ r.aggregate_threshold }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">Rate - Individual/HUF %<input name="rate_individual_huf" value="{{ r.rate_individual_huf }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">Rate - Other %<input name="rate_other" value="{{ r.rate_other }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">No-PAN rate %<input name="no_pan_rate" value="{{ r.no_pan_rate }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm lg:col-span-3">Ledger/nature keywords<input name="nature_keywords" value="{{ r.nature_keywords }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label></div><div class="mt-3 flex flex-wrap gap-4 text-sm"><label><input type="checkbox" name="deduct_on_excess_only" {% if r.deduct_on_excess_only %}checked{% endif %}> Deduct only on amount exceeding threshold</label><label><input type="checkbox" name="applies_to_purchases" {% if r.applies_to_purchases %}checked{% endif %}> Purchases</label><label><input type="checkbox" name="applies_to_expenses" {% if r.applies_to_expenses %}checked{% endif %}> Expenses/services</label><label><input type="checkbox" name="is_active" {% if r.is_active %}checked{% endif %}> Active</label></div><button class="mt-4 rounded-xl bg-violet-700 px-4 py-2 text-sm font-semibold text-white">Save Rule</button></form>{% endfor %}</div></div>
<div class="space-y-4">{% for r in rules %}<form method="post" action="/tools/accounting/tds/rules/{{ r.id }}" class="rounded-2xl bg-white p-5 shadow-soft"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><div class="grid gap-3 lg:grid-cols-4"><label class="text-sm">Rule name<input name="name" value="{{ r.name }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">New Act reference<input name="statutory_reference" value="{{ r.statutory_reference }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">Legacy section<input name="legacy_section" value="{{ r.legacy_section or '' }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">Threshold mode<select name="threshold_mode" class="mt-1 w-full rounded-xl border px-3 py-2">{% for m in ['ANNUAL','SINGLE','BOTH','MONTHLY'] %}<option {% if r.threshold_mode==m %}selected{% endif %}>{{ m }}</option>{% endfor %}</select></label><label class="text-sm">Single / monthly threshold<input name="single_threshold" value="{{ r.single_threshold }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">Annual aggregate threshold<input name="aggregate_threshold" value="{{ r.aggregate_threshold }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">Rate - Individual/HUF %<input name="rate_individual_huf" value="{{ r.rate_individual_huf }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">Rate - Other %<input name="rate_other" value="{{ r.rate_other }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm">No-PAN rate %<input name="no_pan_rate" value="{{ r.no_pan_rate }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label><label class="text-sm lg:col-span-3">Ledger/nature keywords<input name="nature_keywords" value="{{ r.nature_keywords }}" class="mt-1 w-full rounded-xl border px-3 py-2"></label></div><div class="mt-3 flex flex-wrap gap-4 text-sm"><label><input type="checkbox" name="deduct_on_excess_only" {% if r.deduct_on_excess_only %}checked{% endif %}> Deduct only on amount exceeding threshold</label><label><input type="checkbox" name="applies_to_purchases" {% if r.applies_to_purchases %}checked{% endif %}> Purchases</label><label><input type="checkbox" name="applies_to_expenses" {% if r.applies_to_expenses %}checked{% endif %}> Expenses/services</label><label><input type="checkbox" name="is_active" {% if r.is_active %}checked{% endif %}> Active</label></div><button class="mt-4 rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Save Rule</button></form>{% endfor %}</div></div>
{% endblock %}
+1 -1
View File
@@ -832,7 +832,7 @@ def depreciation_it_tool(
else:
command_error = str(status_response.get("error") or "Accounting Mirror status failed.")
if mirror_status and (mirror_status.get("mirror") or {}).get("ready"):
if mirror_status and (mirror_status.get("mirror") or {}).get("ready_for_financial_year"):
preview_response = request_agent_command(
node.node_code,
"accounting_depreciation_preview",
+1 -1
View File
@@ -4,7 +4,7 @@ import io
from pathlib import Path
import zipfile
ERP_LOCAL_AGENT_VERSION = "1.25.2"
ERP_LOCAL_AGENT_VERSION = "1.25.3"
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.25.2"
__version__ = "1.25.3"
AGENT_NAME = "ERP Local Agent"
@@ -3,6 +3,7 @@ from __future__ import annotations
from datetime import date, datetime, timedelta, timezone
import json
import os
import re
from pathlib import Path
import shutil
import sqlite3
@@ -3194,7 +3194,61 @@ class AgentCommandProcessor:
liability=round(max(0.0,expected-actual),2)
status="OK" if expected>0 and liability<=0.009 else ("TDS NOT DEDUCTED" if expected>0 and actual<=0.009 else ("SHORT DEDUCTION" if liability>0 else "BELOW / OUTSIDE THRESHOLD"))
transactions.append({**{k:v for k,v in x.items() if k!='rule'},"rule_id":r.get("id"),"rule_code":r.get("rule_code"),"rule_name":r.get("name"),"legacy_section":r.get("legacy_section"),"statutory_reference":r.get("statutory_reference"),"cumulative_amount":cumulative,"expected_tds":expected,"actual_tds":actual,"liability":liability,"status":status})
review = {"company_name":company_name,"company_guid":company_guid,"date_from":date_from,"date_to":date_to,"summary":{"vouchers_reviewed":len(vouchers),"candidate_transactions":len(transactions),"tds_not_deducted":sum(1 for x in transactions if x["status"]=="TDS NOT DEDUCTED"),"short_deduction":sum(1 for x in transactions if x["status"]=="SHORT DEDUCTION"),"purchase_transactions":sum(1 for x in transactions if x["rule_code"]=="PURCHASE_GOODS")},"transactions":transactions,"tds_ledgers":sorted(tds_ledger_names),"read_only":True,"sqlite_mirror":True}
category_map = {}
for item in transactions:
key = str(item.get("rule_code") or item.get("rule_name") or "UNCLASSIFIED")
row = category_map.setdefault(key, {
"rule_code": str(item.get("rule_code") or ""),
"category": str(item.get("rule_name") or "Unclassified"),
"legacy_section": str(item.get("legacy_section") or ""),
"statutory_reference": str(item.get("statutory_reference") or ""),
"transactions": 0,
"taxable_base": 0.0,
"expected_tds": 0.0,
"actual_tds": 0.0,
"liability": 0.0,
"tds_not_deducted": 0,
"short_deduction": 0,
"ok": 0,
"below_threshold": 0,
})
row["transactions"] += 1
row["taxable_base"] = round(row["taxable_base"] + float(item.get("taxable_base") or 0), 2)
row["expected_tds"] = round(row["expected_tds"] + float(item.get("expected_tds") or 0), 2)
row["actual_tds"] = round(row["actual_tds"] + float(item.get("actual_tds") or 0), 2)
row["liability"] = round(row["liability"] + float(item.get("liability") or 0), 2)
status = str(item.get("status") or "")
if status == "TDS NOT DEDUCTED":
row["tds_not_deducted"] += 1
elif status == "SHORT DEDUCTION":
row["short_deduction"] += 1
elif status == "OK":
row["ok"] += 1
else:
row["below_threshold"] += 1
category_summary = sorted(
category_map.values(),
key=lambda row: (-float(row.get("liability") or 0), str(row.get("category") or "").casefold()),
)
review = {
"company_name": company_name,
"company_guid": company_guid,
"date_from": date_from,
"date_to": date_to,
"summary": {
"vouchers_reviewed": len(vouchers),
"candidate_transactions": len(transactions),
"tds_not_deducted": sum(1 for x in transactions if x["status"]=="TDS NOT DEDUCTED"),
"short_deduction": sum(1 for x in transactions if x["status"]=="SHORT DEDUCTION"),
"purchase_transactions": sum(1 for x in transactions if x["rule_code"]=="PURCHASE_GOODS"),
},
"category_summary": category_summary,
"transactions": transactions,
"tds_ledgers": sorted(tds_ledger_names),
"read_only": True,
"sqlite_mirror": True,
}
analysis_run_id = self._save_analysis_run(
client_id,
analysis_type="TDS_COMPLIANCE",
@@ -3531,11 +3585,66 @@ class AgentCommandProcessor:
@staticmethod
def _mirror_financial_year_match(mirror: dict[str, Any], financial_year: str) -> dict[str, Any]:
"""Validate mirror contents against the FY requested by ERP.
A mirror file can exist in an FY folder even when its vouchers were
extracted while Tally was on another active period. Such a file must
not be shown as Ready for Opening Balance / depreciation analysis.
"""
fy = str(financial_year or "").strip()
match = re.fullmatch(r"(\d{4})-(\d{2})", fy)
if not match:
return {"requested_financial_year": fy, "ready_for_financial_year": bool(mirror.get("ready"))}
start_year = int(match.group(1))
fy_from = _dt_date(start_year, 4, 1)
fy_to = _dt_date(start_year + 1, 3, 31)
period = mirror.get("voucher_period") or {}
from_text = str(period.get("from_date") or "").strip()
to_text = str(period.get("to_date") or "").strip()
try:
mirror_from = _dt_date.fromisoformat(from_text)
mirror_to = _dt_date.fromisoformat(to_text)
except Exception:
return {
"requested_financial_year": fy,
"ready_for_financial_year": False,
"financial_year_reason": "Mirror voucher period is unavailable.",
"financial_year_from": fy_from.isoformat(),
"financial_year_to": fy_to.isoformat(),
}
# Require the mirror's actual voucher period to fall inside the requested
# Indian FY. This deliberately rejects a copied/misfiled FY2025-26
# extraction sitting in the FY2026-27 directory.
ready = bool(mirror.get("ready")) and mirror_from >= fy_from and mirror_to <= fy_to
return {
"requested_financial_year": fy,
"ready_for_financial_year": ready,
"financial_year_reason": "" if ready else (
f"Mirror contains {mirror_from.isoformat()} to {mirror_to.isoformat()}, "
f"not FY {fy}."
),
"financial_year_from": fy_from.isoformat(),
"financial_year_to": fy_to.isoformat(),
}
def _accounting_mirror_status(self, payload: dict[str, Any]) -> dict[str, Any]:
client_id = int(payload.get("client_id") or 0)
if client_id <= 0:
raise ValueError("client_id is required.")
return self.tally.mirror.status(client_id)
result = self.tally.mirror.status(client_id)
mirror = result.get("mirror") or {}
fy_check = self._mirror_financial_year_match(mirror, str(payload.get("financial_year") or ""))
mirror.update(fy_check)
# Keep file-level readiness for diagnostics, but expose the effective
# readiness used by ERP analysis pages separately.
result["mirror"] = mirror
result["ready_for_financial_year"] = bool(fy_check.get("ready_for_financial_year"))
return result
def _accounting_mirror_sync(self, payload: dict[str, Any]) -> dict[str, Any]:
client_id = int(payload.get("client_id") or 0)