Add bill-wise drilldown for sundry creditors ageing
This commit is contained in:
@@ -49,13 +49,90 @@
|
|||||||
<a href="/tools/accounting/sundry-creditors/export.xlsx?client_id={{ selected_client.id }}&financial_year={{ selected_fy }}{% if follow_up_fy %}&follow_up_fy={{ follow_up_fy }}{% endif %}" class="rounded-xl border border-emerald-300 bg-emerald-50 px-4 py-2 text-sm font-semibold text-emerald-800">Export Excel</a>
|
<a href="/tools/accounting/sundry-creditors/export.xlsx?client_id={{ selected_client.id }}&financial_year={{ selected_fy }}{% if follow_up_fy %}&follow_up_fy={{ follow_up_fy }}{% endif %}" class="rounded-xl border border-emerald-300 bg-emerald-50 px-4 py-2 text-sm font-semibold text-emerald-800">Export Excel</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overflow-x-auto rounded-2xl border border-slate-200 bg-white">
|
<div class="rounded-2xl border border-slate-200 bg-white">
|
||||||
<table class="min-w-full text-sm">
|
<div class="border-b border-slate-100 px-4 py-3 text-xs text-slate-500">
|
||||||
<thead class="bg-slate-50 text-left text-xs font-semibold uppercase tracking-wide text-slate-600"><tr><th class="px-4 py-3">Party</th><th class="px-4 py-3 text-right">≤180 Days</th><th class="px-4 py-3 text-right">>180 Days</th><th class="px-4 py-3 text-right">Closing</th><th class="px-4 py-3 text-right">>180 Paid Later</th><th class="px-4 py-3 text-right">Still Unpaid</th><th class="px-4 py-3">Final Payment Date</th></tr></thead>
|
Click a <strong>party name</strong> to see all surviving FIFO lots, or click the <strong>≤180</strong> / <strong>>180</strong> amount to drill directly into that age bucket. Purchase vouchers, opening/brought-forward balances and subsequent-payment status are shown from the same FIFO calculation used by the summary.
|
||||||
<tbody class="divide-y divide-slate-100">
|
</div>
|
||||||
{% for row in analysis.parties %}<tr><td class="px-4 py-3 font-semibold text-slate-800">{{ row.party_name }}</td><td class="px-4 py-3 text-right">{{ '%.2f'|format(row.within_180) }}</td><td class="px-4 py-3 text-right {% if row.over_180 > 0 %}font-semibold text-rose-700{% endif %}">{{ '%.2f'|format(row.over_180) }}</td><td class="px-4 py-3 text-right font-semibold">{{ '%.2f'|format(row.closing_balance) }}</td><td class="px-4 py-3 text-right">{{ '%.2f'|format(row.over_180_paid_later) }}</td><td class="px-4 py-3 text-right">{{ '%.2f'|format(row.over_180_still_unpaid) }}</td><td class="px-4 py-3">{{ row.final_payment_date or '—' }}</td></tr>{% else %}<tr><td colspan="7" class="px-4 py-8 text-center text-slate-500">No non-zero Sundry Creditors were found for the selected FY.</td></tr>{% endfor %}
|
<div class="overflow-x-auto">
|
||||||
</tbody>
|
<table class="min-w-full text-sm">
|
||||||
</table>
|
<thead class="bg-slate-50 text-left text-xs font-semibold uppercase tracking-wide text-slate-600"><tr><th class="px-4 py-3">Party</th><th class="px-4 py-3 text-right">≤180 Days</th><th class="px-4 py-3 text-right">>180 Days</th><th class="px-4 py-3 text-right">Closing</th><th class="px-4 py-3 text-right">>180 Paid Later</th><th class="px-4 py-3 text-right">Still Unpaid</th><th class="px-4 py-3">Final Payment Date</th></tr></thead>
|
||||||
|
<tbody class="divide-y divide-slate-100">
|
||||||
|
{% for row in analysis.parties %}
|
||||||
|
{% set party_index = loop.index0 %}
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-3 font-semibold text-slate-800">
|
||||||
|
<button type="button" class="inline-flex items-center gap-2 text-left text-brand-700 hover:text-brand-900 hover:underline" data-creditor-drilldown data-target="creditor-detail-{{ party_index }}" data-bucket="all" aria-expanded="false">
|
||||||
|
<span>{{ row.party_name }}</span><span aria-hidden="true" class="text-xs">▾</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-right">
|
||||||
|
{% if row.within_180 > 0 %}<button type="button" class="font-semibold text-brand-700 hover:underline" data-creditor-drilldown data-target="creditor-detail-{{ party_index }}" data-bucket="within" aria-expanded="false">{{ '%.2f'|format(row.within_180) }}</button>{% else %}{{ '%.2f'|format(row.within_180) }}{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-right {% if row.over_180 > 0 %}font-semibold text-rose-700{% endif %}">
|
||||||
|
{% if row.over_180 > 0 %}<button type="button" class="font-semibold text-rose-700 hover:underline" data-creditor-drilldown data-target="creditor-detail-{{ party_index }}" data-bucket="over" aria-expanded="false">{{ '%.2f'|format(row.over_180) }}</button>{% else %}{{ '%.2f'|format(row.over_180) }}{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-right font-semibold">{{ '%.2f'|format(row.closing_balance) }}</td>
|
||||||
|
<td class="px-4 py-3 text-right">{{ '%.2f'|format(row.over_180_paid_later) }}</td>
|
||||||
|
<td class="px-4 py-3 text-right">{{ '%.2f'|format(row.over_180_still_unpaid) }}</td>
|
||||||
|
<td class="px-4 py-3">{{ row.final_payment_date or '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="creditor-detail-{{ party_index }}" class="hidden bg-slate-50/60" data-creditor-detail>
|
||||||
|
<td colspan="7" class="px-4 py-4">
|
||||||
|
<div class="rounded-xl border border-slate-200 bg-white">
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-slate-100 px-4 py-3">
|
||||||
|
<div>
|
||||||
|
<div class="font-semibold text-slate-900">{{ row.party_name }} · FIFO Outstanding Bill Drill-down</div>
|
||||||
|
<div class="mt-1 text-xs text-slate-500">Closing ₹ {{ '%.2f'|format(row.closing_balance) }} · ≤180 ₹ {{ '%.2f'|format(row.within_180) }} · >180 ₹ {{ '%.2f'|format(row.over_180) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<button type="button" class="rounded-lg border border-slate-300 bg-white px-3 py-1.5 text-xs font-semibold text-slate-700 hover:bg-slate-50" data-creditor-filter="all">All outstanding</button>
|
||||||
|
<button type="button" class="rounded-lg border border-brand-200 bg-brand-50 px-3 py-1.5 text-xs font-semibold text-brand-700 hover:bg-brand-100" data-creditor-filter="within">≤180 days</button>
|
||||||
|
<button type="button" class="rounded-lg border border-rose-200 bg-rose-50 px-3 py-1.5 text-xs font-semibold text-rose-700 hover:bg-rose-100" data-creditor-filter="over">>180 days</button>
|
||||||
|
<button type="button" class="rounded-lg border border-slate-300 bg-white px-3 py-1.5 text-xs font-semibold text-slate-600 hover:bg-slate-50" data-creditor-close>Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full text-xs">
|
||||||
|
<thead class="bg-slate-50 text-left font-semibold uppercase tracking-wide text-slate-600">
|
||||||
|
<tr><th class="px-3 py-2">Source</th><th class="px-3 py-2">Purchase / Voucher Date</th><th class="px-3 py-2">Voucher Type</th><th class="px-3 py-2">Voucher No.</th><th class="px-3 py-2">Reference</th><th class="px-3 py-2 text-right">Original Credit</th><th class="px-3 py-2 text-right">Outstanding at FY End</th><th class="px-3 py-2 text-right">Age Days</th><th class="px-3 py-2">Bucket</th><th class="px-3 py-2 text-right">Paid Later</th><th class="px-3 py-2 text-right">Follow-up Balance</th><th class="px-3 py-2">Final Paid</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-slate-100">
|
||||||
|
{% set ns = namespace(found=false) %}
|
||||||
|
{% for detail in analysis.details %}
|
||||||
|
{% if detail.party_name == row.party_name %}
|
||||||
|
{% set ns.found = true %}
|
||||||
|
<tr data-creditor-lot data-bucket="{% if detail.age_bucket == '>180 Days' %}over{% else %}within{% endif %}">
|
||||||
|
<td class="px-3 py-2">{{ detail.source }}</td>
|
||||||
|
<td class="px-3 py-2 whitespace-nowrap">{{ detail.voucher_date }}</td>
|
||||||
|
<td class="px-3 py-2">{{ detail.voucher_type or '—' }}</td>
|
||||||
|
<td class="px-3 py-2 font-medium text-slate-800">{{ detail.voucher_number or '—' }}</td>
|
||||||
|
<td class="px-3 py-2">{{ detail.reference or '—' }}</td>
|
||||||
|
<td class="px-3 py-2 text-right">{{ '%.2f'|format(detail.original_credit) }}</td>
|
||||||
|
<td class="px-3 py-2 text-right font-semibold">{{ '%.2f'|format(detail.outstanding_at_fy_end) }}</td>
|
||||||
|
<td class="px-3 py-2 text-right">{{ detail.age_days }}</td>
|
||||||
|
<td class="px-3 py-2 {% if detail.age_bucket == '>180 Days' %}font-semibold text-rose-700{% else %}font-semibold text-brand-700{% endif %}">{{ detail.age_bucket }}</td>
|
||||||
|
<td class="px-3 py-2 text-right">{{ '%.2f'|format(detail.paid_subsequently) }}</td>
|
||||||
|
<td class="px-3 py-2 text-right">{{ '%.2f'|format(detail.balance_after_follow_up) }}</td>
|
||||||
|
<td class="px-3 py-2 whitespace-nowrap">{{ detail.final_payment_date or '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if not ns.found %}<tr><td colspan="12" class="px-3 py-6 text-center text-slate-500">No surviving FIFO bill lots were found for this party.</td></tr>{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-slate-100 px-4 py-3 text-xs text-slate-500">
|
||||||
|
<strong>Note:</strong> “Opening / brought forward” represents an old balance for which the originating purchase voucher is outside the selected FY/mirror history. Voucher rows are FIFO-derived outstanding lots from the same calculation used in the party summary; they are not Tally bill-reference allocations.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr><td colspan="7" class="px-4 py-8 text-center text-slate-500">No non-zero Sundry Creditors were found for the selected FY.</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details class="rounded-2xl border border-slate-200 bg-white" open><summary class="cursor-pointer px-5 py-4 font-semibold text-slate-800">FIFO closing-balance detail ({{ analysis.details|length }})</summary>
|
<details class="rounded-2xl border border-slate-200 bg-white" open><summary class="cursor-pointer px-5 py-4 font-semibold text-slate-800">FIFO closing-balance detail ({{ analysis.details|length }})</summary>
|
||||||
@@ -63,4 +140,54 @@
|
|||||||
</details>
|
</details>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
function applyBucket(detailRow, bucket) {
|
||||||
|
if (!detailRow) return;
|
||||||
|
detailRow.querySelectorAll('[data-creditor-lot]').forEach(function (lot) {
|
||||||
|
var show = bucket === 'all' || lot.dataset.bucket === bucket;
|
||||||
|
lot.classList.toggle('hidden', !show);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-creditor-drilldown]').forEach(function (button) {
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
var detailRow = document.getElementById(button.dataset.target);
|
||||||
|
if (!detailRow) return;
|
||||||
|
var bucket = button.dataset.bucket || 'all';
|
||||||
|
var currentlyHidden = detailRow.classList.contains('hidden');
|
||||||
|
document.querySelectorAll('[data-creditor-detail]').forEach(function (row) {
|
||||||
|
if (row !== detailRow) row.classList.add('hidden');
|
||||||
|
});
|
||||||
|
if (!currentlyHidden && detailRow.dataset.activeBucket === bucket) {
|
||||||
|
detailRow.classList.add('hidden');
|
||||||
|
detailRow.dataset.activeBucket = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
detailRow.classList.remove('hidden');
|
||||||
|
detailRow.dataset.activeBucket = bucket;
|
||||||
|
applyBucket(detailRow, bucket);
|
||||||
|
detailRow.scrollIntoView({behavior: 'smooth', block: 'nearest'});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-creditor-detail]').forEach(function (detailRow) {
|
||||||
|
detailRow.querySelectorAll('[data-creditor-filter]').forEach(function (button) {
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
var bucket = button.dataset.creditorFilter || 'all';
|
||||||
|
detailRow.dataset.activeBucket = bucket;
|
||||||
|
applyBucket(detailRow, bucket);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var closeButton = detailRow.querySelector('[data-creditor-close]');
|
||||||
|
if (closeButton) {
|
||||||
|
closeButton.addEventListener('click', function () {
|
||||||
|
detailRow.classList.add('hidden');
|
||||||
|
detailRow.dataset.activeBucket = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user