Added allocation breakdown
X-Lovable-Edit-ID: edt-d08ce090-c646-4c3c-9438-ea1e777b3942 Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
@@ -56,6 +56,7 @@ import { toast } from "sonner";
|
||||
import {
|
||||
BUCKETS,
|
||||
BUCKET_LABEL,
|
||||
BUCKET_SHORT,
|
||||
computeBucketBalances,
|
||||
num,
|
||||
withAllocations,
|
||||
@@ -694,68 +695,10 @@ export function CollectionDetail({
|
||||
theme: "grid",
|
||||
});
|
||||
|
||||
// ── Amounts Due Breakdown (left), with sub-account subtotals ──
|
||||
// Group entries by bucket -> account label -> subtotal
|
||||
const subAccountTotals: Record<string, Record<string, number>> = {};
|
||||
BUCKETS.forEach((b) => (subAccountTotals[b] = {}));
|
||||
entries.forEach((e: any) => {
|
||||
const acctLabel = (e.account && String(e.account).trim()) || "—";
|
||||
BUCKETS.forEach((b) => {
|
||||
const v = num(e[b]);
|
||||
if (v) {
|
||||
subAccountTotals[b][acctLabel] = (subAccountTotals[b][acctLabel] || 0) + v;
|
||||
}
|
||||
});
|
||||
});
|
||||
const cardEndY = (doc as any).lastAutoTable?.finalY ?? 150;
|
||||
|
||||
const breakdownBody: any[] = [];
|
||||
// Order matches screenshot: Assessments, Late, Admin, Legal, Violations, Interest, Bank
|
||||
const DISPLAY_ORDER: Array<typeof BUCKETS[number]> = [
|
||||
"assess", "late", "admin", "legal", "viol", "interest", "bank",
|
||||
];
|
||||
DISPLAY_ORDER.forEach((b) => {
|
||||
const bucketTotal = Math.max(0, computed[b] ?? 0);
|
||||
const label =
|
||||
b === "interest"
|
||||
? `Interest @ ${(effectiveRate || 0).toFixed(2)}%`
|
||||
: BUCKET_LABEL[b];
|
||||
breakdownBody.push([
|
||||
{ content: label, styles: { fontStyle: "bold", fillColor: [245, 245, 245] } },
|
||||
{ content: formatCurrency(bucketTotal), styles: { halign: "right", fontStyle: "bold", fillColor: [245, 245, 245] } },
|
||||
]);
|
||||
// Sub-account rows (only if there's more than one OR the single one differs from the bucket label)
|
||||
const subs = subAccountTotals[b];
|
||||
const subKeys = Object.keys(subs);
|
||||
if (subKeys.length > 0) {
|
||||
subKeys
|
||||
.sort((a, z) => subs[z] - subs[a])
|
||||
.forEach((sk) => {
|
||||
breakdownBody.push([
|
||||
{ content: ` ${sk}`, styles: { textColor: 90 } },
|
||||
{ content: formatCurrency(subs[sk]), styles: { halign: "right", textColor: 90 } },
|
||||
]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
autoTable(doc, {
|
||||
startY: 150,
|
||||
margin: { left: margin, right: pageWidth / 2 + 20 },
|
||||
head: [["Amounts Due Breakdown", "Amount"]],
|
||||
body: breakdownBody,
|
||||
styles: { fontSize: 9, cellPadding: 4 },
|
||||
headStyles: { fillColor: [20, 20, 20], textColor: 255, fontStyle: "bold" },
|
||||
columnStyles: {
|
||||
0: { cellWidth: "auto" },
|
||||
1: { halign: "right", cellWidth: 90 },
|
||||
},
|
||||
theme: "grid",
|
||||
});
|
||||
|
||||
const breakdownEndY = (doc as any).lastAutoTable?.finalY ?? 150;
|
||||
|
||||
// ── Transaction ledger table (full width below) ──────────────
|
||||
const txStartY = breakdownEndY + 24;
|
||||
// ── Transaction ledger table (full width below header) ──────────────
|
||||
const txStartY = Math.max(cardEndY + 24, 150);
|
||||
const body: any[] = [];
|
||||
if (opening !== 0) {
|
||||
body.push([
|
||||
@@ -769,9 +712,18 @@ export function CollectionDetail({
|
||||
}
|
||||
withRunning.forEach((e: any) => {
|
||||
const isPayment = num(e.payment) > 0;
|
||||
let desc = e.description || (e.account || "—");
|
||||
if (isPayment && e.allocations) {
|
||||
const allocParts = BUCKETS
|
||||
.filter((b) => num(e.allocations[b]) > 0)
|
||||
.map((b) => `${BUCKET_SHORT[b]} ${formatCurrency(num(e.allocations[b]))}`);
|
||||
if (allocParts.length > 0) {
|
||||
desc = `${desc}\nApplied → ${allocParts.join(", ")}`;
|
||||
}
|
||||
}
|
||||
body.push([
|
||||
e.entry_date ? formatDate(e.entry_date) : "",
|
||||
e.description || (e.account || "—"),
|
||||
desc,
|
||||
num(e.assess) ? num(e.assess).toFixed(2) : "",
|
||||
num(e.late) ? num(e.late).toFixed(2) : "",
|
||||
num(e.admin) ? num(e.admin).toFixed(2) : "",
|
||||
@@ -834,6 +786,67 @@ export function CollectionDetail({
|
||||
},
|
||||
});
|
||||
|
||||
// ── Amounts Due Breakdown (below the ledger) ──
|
||||
// Group entries by bucket → account label → subtotal
|
||||
const subAccountTotals: Record<string, Record<string, number>> = {};
|
||||
BUCKETS.forEach((b) => (subAccountTotals[b] = {}));
|
||||
entries.forEach((e: any) => {
|
||||
const acctLabel = (e.account && String(e.account).trim()) || "—";
|
||||
BUCKETS.forEach((b) => {
|
||||
const v = num(e[b]);
|
||||
if (v) {
|
||||
subAccountTotals[b][acctLabel] = (subAccountTotals[b][acctLabel] || 0) + v;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const breakdownBody: any[] = [];
|
||||
const DISPLAY_ORDER: Array<typeof BUCKETS[number]> = [
|
||||
"assess", "late", "admin", "legal", "viol", "interest", "bank",
|
||||
];
|
||||
DISPLAY_ORDER.forEach((b) => {
|
||||
const bucketTotal = Math.max(0, computed[b] ?? 0);
|
||||
const label =
|
||||
b === "interest"
|
||||
? `Interest @ ${(effectiveRate || 0).toFixed(2)}%`
|
||||
: BUCKET_LABEL[b];
|
||||
breakdownBody.push([
|
||||
{ content: label, styles: { fontStyle: "bold", fillColor: [245, 245, 245] } },
|
||||
{ content: formatCurrency(bucketTotal), styles: { halign: "right", fontStyle: "bold", fillColor: [245, 245, 245] } },
|
||||
]);
|
||||
const subs = subAccountTotals[b];
|
||||
const subKeys = Object.keys(subs);
|
||||
if (subKeys.length > 0) {
|
||||
subKeys
|
||||
.sort((a, z) => subs[z] - subs[a])
|
||||
.forEach((sk) => {
|
||||
breakdownBody.push([
|
||||
{ content: ` ${sk}`, styles: { textColor: 90 } },
|
||||
{ content: formatCurrency(subs[sk]), styles: { halign: "right", textColor: 90 } },
|
||||
]);
|
||||
});
|
||||
}
|
||||
});
|
||||
breakdownBody.push([
|
||||
{ content: "TOTAL DUE", styles: { fontStyle: "bold", fillColor: [20, 20, 20], textColor: 255 } },
|
||||
{ content: formatCurrency(Math.max(0, computed.total)), styles: { halign: "right", fontStyle: "bold", fillColor: [20, 20, 20], textColor: 255 } },
|
||||
]);
|
||||
|
||||
const ledgerEndY = (doc as any).lastAutoTable?.finalY ?? txStartY;
|
||||
autoTable(doc, {
|
||||
startY: ledgerEndY + 20,
|
||||
margin: { left: margin, right: pageWidth / 2 + 20 },
|
||||
head: [["Amounts Due Breakdown", "Amount"]],
|
||||
body: breakdownBody,
|
||||
styles: { fontSize: 9, cellPadding: 4 },
|
||||
headStyles: { fillColor: [20, 20, 20], textColor: 255, fontStyle: "bold" },
|
||||
columnStyles: {
|
||||
0: { cellWidth: "auto" },
|
||||
1: { halign: "right", cellWidth: 90 },
|
||||
},
|
||||
theme: "grid",
|
||||
});
|
||||
|
||||
doc.save(`${name}.pdf`);
|
||||
toast.success("PDF exported");
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user