From c0035b2dc8c7066d8a71c14f7f396bd7df22db19 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 08:29:25 +0000 Subject: [PATCH 1/4] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/lib/ledger.ts | 58 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/src/lib/ledger.ts b/src/lib/ledger.ts index a937f60..3ff132c 100644 --- a/src/lib/ledger.ts +++ b/src/lib/ledger.ts @@ -1,13 +1,13 @@ -// Priority order for payment allocation: Bank → Interest → Late → Legal → Admin → Violations → Assessments -export const BUCKETS = ["bank", "interest", "late", "legal", "admin", "viol", "assess"] as const; +// Priority order for payment allocation: Bank → Interest → Late → Admin → Legal → Violations → Assessments +export const BUCKETS = ["bank", "interest", "late", "admin", "legal", "viol", "assess"] as const; export type Bucket = (typeof BUCKETS)[number]; export const BUCKET_LABEL: Record = { bank: "Bank Fees", interest: "Interest", late: "Late Fees", - legal: "Legal Fees", admin: "Admin Fees", + legal: "Legal Fees", viol: "Violations", assess: "Assessments", }; @@ -16,8 +16,8 @@ export const BUCKET_SHORT: Record = { bank: "Bank", interest: "Int", late: "Late", - legal: "Legal", admin: "Admin", + legal: "Legal", viol: "Viol", assess: "Assess", }; @@ -93,3 +93,53 @@ export function withRunningBalance(entries: LedgerEntry[], opening = 0) { return { ...e, runningBalance: bal }; }); } + +/** + * For each row, compute: + * - runningBalance (overall) + * - allocations: Record — how much of THIS row's payment was + * applied to each bucket (in priority order). Empty / zero for non-payment rows. + * - bucketBalancesAfter: per-bucket remaining balance after this row. + */ +export function withAllocations(entries: LedgerEntry[], opening = 0) { + const bal: Record = { + bank: 0, interest: 0, late: 0, admin: 0, legal: 0, viol: 0, assess: 0, + }; + bal.assess += opening; + let running = opening; + + return entries.map((e) => { + // First add this row's charges + BUCKETS.forEach((b) => { bal[b] += num((e as any)[b]); }); + running += rowCharges(e); + + // Then allocate this row's payment in priority order + const allocations: Record = { + bank: 0, interest: 0, late: 0, admin: 0, legal: 0, viol: 0, assess: 0, + }; + let pay = num(e.payment); + if (pay > 0) { + for (const b of BUCKETS) { + if (pay <= 0) break; + if (bal[b] <= 0) continue; + const take = Math.min(bal[b], pay); + bal[b] -= take; + allocations[b] += take; + pay -= take; + } + // leftover overpayment → reduce assess (creates credit) + if (pay > 0) { + bal.assess -= pay; + allocations.assess += pay; + } + running -= num(e.payment); + } + + return { + ...e, + runningBalance: running, + allocations, + bucketBalancesAfter: { ...bal }, + }; + }); +} From 8c39b539fab7a880f293e5c972199dafb46deaf6 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 08:29:44 +0000 Subject: [PATCH 2/4] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/cases/ledger-row.tsx | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/components/cases/ledger-row.tsx b/src/components/cases/ledger-row.tsx index e104fe3..94ac184 100644 --- a/src/components/cases/ledger-row.tsx +++ b/src/components/cases/ledger-row.tsx @@ -3,7 +3,7 @@ import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { GripVertical, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { BUCKETS, num } from "@/lib/ledger"; +import { BUCKETS, num, type Bucket } from "@/lib/ledger"; import { formatCurrency } from "@/lib/format"; export interface EditableEntry { @@ -20,6 +20,7 @@ export interface EditableEntry { bank: number; payment: number; runningBalance?: number; + allocations?: Record; } interface Props { @@ -29,13 +30,13 @@ interface Props { } const NUM_COLS: { key: keyof EditableEntry; danger?: boolean; positive?: boolean }[] = [ - { key: "assess" }, + { key: "bank", danger: true }, + { key: "interest" }, { key: "late" }, { key: "admin" }, { key: "legal" }, { key: "viol" }, - { key: "interest" }, - { key: "bank", danger: true }, + { key: "assess" }, { key: "payment", positive: true }, ]; @@ -111,6 +112,24 @@ export function SortableLedgerRow({ entry, onPatch, onRemove }: Props) { {NUM_COLS.map(({ key, danger, positive }) => { const v = num((draft as any)[key]); + // For non-payment columns: if this row is a payment with an allocation + // to this bucket, show the allocation as a green negative deduction. + const isPaymentCol = key === "payment"; + const allocAmt = !isPaymentCol && entry.allocations + ? num((entry.allocations as any)[key]) + : 0; + if (!isPaymentCol && allocAmt > 0 && v === 0) { + return ( + +
+ −{allocAmt.toFixed(2)} +
+ + ); + } return ( Date: Sun, 26 Apr 2026 08:30:14 +0000 Subject: [PATCH 3/4] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/cases/collections-tab.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/cases/collections-tab.tsx b/src/components/cases/collections-tab.tsx index 06cac6d..5062be2 100644 --- a/src/components/cases/collections-tab.tsx +++ b/src/components/cases/collections-tab.tsx @@ -57,7 +57,7 @@ import { BUCKET_LABEL, computeBucketBalances, num, - withRunningBalance, + withAllocations, } from "@/lib/ledger"; import { DndContext, @@ -533,7 +533,7 @@ export function CollectionDetail({ [entries, opening], ); const withRunning = useMemo( - () => withRunningBalance(entries as any, opening), + () => withAllocations(entries as any, opening), [entries, opening], ); @@ -972,13 +972,13 @@ export function CollectionDetail({ Date Description Account - Assess ($) + Bank ($) + Int ($) Late ($) Admin ($) Legal ($) Viol ($) - Int ($) - Bank ($) + Assess ($) Pay (AR) Balance @@ -1000,8 +1000,9 @@ export function CollectionDetail({ {formatDate(collection.opened_at)} Opening balance opening + 0.00 {opening > 0 ? opening.toFixed(2) : "0.00"} - 0.00 + 0.00 {formatCurrency(opening)} @@ -1019,13 +1020,13 @@ export function CollectionDetail({ ))} Totals: - {formatCurrency(totals.assess)} + {formatCurrency(totals.bank)} + {formatCurrency(totals.interest)} {formatCurrency(totals.late)} {formatCurrency(totals.admin)} {formatCurrency(totals.legal)} {formatCurrency(totals.viol)} - {formatCurrency(totals.interest)} - {formatCurrency(totals.bank)} + {formatCurrency(totals.assess)} {formatCurrency(totals.payment)} {formatCurrency(computed.total)} From fc4e215d1bcfdb8daf3ff2f1b18b376645d897df Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 08:30:34 +0000 Subject: [PATCH 4/4] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/components/cases/collections-tab.tsx | 25 ++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/components/cases/collections-tab.tsx b/src/components/cases/collections-tab.tsx index 5062be2..b83d51e 100644 --- a/src/components/cases/collections-tab.tsx +++ b/src/components/cases/collections-tab.tsx @@ -602,18 +602,18 @@ export function CollectionDetail({ const exportStatement = () => { const rows = [ - ["Date", "Description", "Account", "Assess", "Late", "Admin", "Legal", "Viol", "Int", "Bank", "Payment", "Balance"].join(","), + ["Date", "Description", "Account", "Bank", "Int", "Late", "Admin", "Legal", "Viol", "Assess", "Payment", "Balance"].join(","), ...withRunning.map((e: any) => [ e.entry_date, `"${(e.description || "").replace(/"/g, '""')}"`, e.account || e.transaction_type || "", - num(e.assess).toFixed(2), + num(e.bank).toFixed(2), + num(e.interest).toFixed(2), num(e.late).toFixed(2), num(e.admin).toFixed(2), num(e.legal).toFixed(2), num(e.viol).toFixed(2), - num(e.interest).toFixed(2), - num(e.bank).toFixed(2), + num(e.assess).toFixed(2), num(e.payment).toFixed(2), e.runningBalance.toFixed(2), ].join(",")), @@ -676,8 +676,9 @@ export function CollectionDetail({ formatDate(collection.opened_at), "Opening balance", "opening", + "0.00", "0.00", "0.00", "0.00", "0.00", "0.00", opening > 0 ? opening.toFixed(2) : "0.00", - "0.00", "0.00", "0.00", "0.00", "0.00", "0.00", "0.00", + "0.00", formatCurrency(opening), ]); } @@ -686,33 +687,33 @@ export function CollectionDetail({ formatDate(e.entry_date), e.description || "—", e.account || e.transaction_type || "", - num(e.assess) ? num(e.assess).toFixed(2) : "", + num(e.bank) ? num(e.bank).toFixed(2) : "", + num(e.interest) ? num(e.interest).toFixed(2) : "", num(e.late) ? num(e.late).toFixed(2) : "", num(e.admin) ? num(e.admin).toFixed(2) : "", num(e.legal) ? num(e.legal).toFixed(2) : "", num(e.viol) ? num(e.viol).toFixed(2) : "", - num(e.interest) ? num(e.interest).toFixed(2) : "", - num(e.bank) ? num(e.bank).toFixed(2) : "", + num(e.assess) ? num(e.assess).toFixed(2) : "", num(e.payment) ? num(e.payment).toFixed(2) : "", formatCurrency(e.runningBalance ?? 0), ]); }); body.push([ { content: "Totals", colSpan: 3, styles: { halign: "right", fontStyle: "bold" } }, - totals.assess.toFixed(2), + totals.bank.toFixed(2), + totals.interest.toFixed(2), totals.late.toFixed(2), totals.admin.toFixed(2), totals.legal.toFixed(2), totals.viol.toFixed(2), - totals.interest.toFixed(2), - totals.bank.toFixed(2), + totals.assess.toFixed(2), totals.payment.toFixed(2), formatCurrency(computed.total), ]); autoTable(doc, { startY: startY + 36, - head: [["Date", "Description", "Account", "Assess", "Late", "Admin", "Legal", "Viol", "Int", "Bank", "Payment", "Balance"]], + head: [["Date", "Description", "Account", "Bank", "Int", "Late", "Admin", "Legal", "Viol", "Assess", "Payment", "Balance"]], body, styles: { fontSize: 8, cellPadding: 3 }, headStyles: { fillColor: [40, 40, 40], textColor: 255 },