Fix mirror FY period detection and add TDS exception links

This commit is contained in:
A R R R Associates
2026-09-06 21:05:07 +05:30
parent eb83e5418d
commit cbe594e73b
4 changed files with 97 additions and 5 deletions
@@ -1,7 +1,19 @@
{% 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-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="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>
{% if k == 'tds_not_deducted' and v %}
<button type="button" data-tds-filter-status="TDS NOT DEDUCTED" class="mt-2 text-xs font-semibold text-brand-700 underline underline-offset-2">View non-deduction</button>
{% elif k == 'short_deduction' and v %}
<button type="button" data-tds-filter-status="SHORT DEDUCTION" class="mt-2 text-xs font-semibold text-brand-700 underline underline-offset-2">View short deduction</button>
{% endif %}
</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">
@@ -14,6 +26,7 @@
<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>
<th class="p-3 text-left">Review</th>
</tr>
</thead>
<tbody>
@@ -27,9 +40,82 @@
<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>
<td class="p-3">
<div class="flex flex-wrap gap-x-3 gap-y-1">
{% if row.tds_not_deducted %}
<button type="button"
data-tds-filter-rule="{{ row.rule_code }}"
data-tds-filter-status="TDS NOT DEDUCTED"
class="text-xs font-semibold text-brand-700 underline underline-offset-2">
Non-deduction ({{ row.tds_not_deducted }})
</button>
{% endif %}
{% if row.short_deduction %}
<button type="button"
data-tds-filter-rule="{{ row.rule_code }}"
data-tds-filter-status="SHORT DEDUCTION"
class="text-xs font-semibold text-brand-700 underline underline-offset-2">
Short ({{ row.short_deduction }})
</button>
{% endif %}
</div>
</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>
</div>
<div class="overflow-x-auto rounded-2xl bg-white shadow-soft">
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-slate-100 px-4 py-3">
<div>
<h2 class="font-semibold text-slate-900">Transaction Review</h2>
<p id="tdsFilterLabel" class="mt-1 text-xs text-slate-500">Showing all candidate transactions.</p>
</div>
<button type="button" id="tdsClearFilter" class="hidden rounded-lg border border-slate-300 bg-white px-3 py-1.5 text-xs font-semibold text-slate-700">Show all</button>
</div>
<table id="tdsTransactionTable" 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" data-tds-status="{{ x.status }}" data-tds-rule="{{ x.rule_code }}"><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>
{% if review %}
<script>
document.addEventListener("DOMContentLoaded", function () {
const table = document.getElementById("tdsTransactionTable");
if (!table) return;
const rows = Array.from(table.querySelectorAll("tbody tr[data-tds-status]"));
const label = document.getElementById("tdsFilterLabel");
const clear = document.getElementById("tdsClearFilter");
function applyFilter(status, rule) {
let visible = 0;
rows.forEach(function (row) {
const statusMatch = !status || row.dataset.tdsStatus === status;
const ruleMatch = !rule || row.dataset.tdsRule === rule;
const show = statusMatch && ruleMatch;
row.classList.toggle("hidden", !show);
if (show) visible += 1;
});
const pieces = [];
if (status) pieces.push(status === "TDS NOT DEDUCTED" ? "non-deduction" : "short deduction");
if (rule) pieces.push("selected category");
label.textContent = "Showing " + visible + " transaction(s)" + (pieces.length ? " for " + pieces.join(" · ") + "." : ".");
clear.classList.toggle("hidden", !status && !rule);
if (visible > 0) {
table.scrollIntoView({behavior: "smooth", block: "start"});
}
}
document.querySelectorAll("[data-tds-filter-status]").forEach(function (button) {
button.addEventListener("click", function () {
applyFilter(button.dataset.tdsFilterStatus || "", button.dataset.tdsFilterRule || "");
});
});
clear.addEventListener("click", function () {
applyFilter("", "");
});
});
</script>
{% endif %}
{% endblock %}