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 }, + }; + }); +}