// Priority order for payment allocation: Bank → Interest → Late → Legal → Admin → Violations → Assessments export const BUCKETS = ["bank", "interest", "late", "legal", "admin", "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", viol: "Violations", assess: "Assessments", }; export const BUCKET_SHORT: Record = { bank: "Bank", interest: "Int", late: "Late", legal: "Legal", admin: "Admin", viol: "Viol", assess: "Assess", }; export interface LedgerEntry { id: string; collection_id: string; entry_date: string; description: string | null; account: string | null; assess: number; late: number; admin: number; legal: number; viol: number; interest: number; bank: number; payment: number; debit?: number; credit?: number; transaction_type?: string; } export const num = (v: any) => Number(v) || 0; /** Sum of all charge columns for one row */ export const rowCharges = (e: Pick) => BUCKETS.reduce((s, b) => s + num((e as any)[b]), 0); /** Compute live remaining balance per bucket given chronological entries (FIFO payment allocation by priority). */ export function computeBucketBalances( entries: LedgerEntry[], opening = 0, ): Record & { total: number } { const bal: Record = { bank: 0, interest: 0, late: 0, legal: 0, admin: 0, viol: 0, assess: 0, }; // Treat opening balance as outstanding assessments bal.assess += opening; for (const e of entries) { BUCKETS.forEach((b) => { bal[b] += num((e as any)[b]); }); 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; pay -= take; } // any leftover credit goes against assessments (overpayment) if (pay > 0) bal.assess -= pay; } } const total = BUCKETS.reduce((s, b) => s + bal[b], 0); return { ...bal, total }; } /** Running balance per row */ export function withRunningBalance(entries: LedgerEntry[], opening = 0) { let bal = opening; return entries.map((e) => { bal += rowCharges(e) - num(e.payment); return { ...e, runningBalance: bal }; }); }