Allocated payments by bucket
X-Lovable-Edit-ID: edt-2a369356-4628-484a-9cd5-2eadaec6c5d8 Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
@@ -832,18 +832,52 @@ 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] = {}));
|
||||
// Group entries by bucket → account label → charged subtotal,
|
||||
// then subtract payments proportionally so each sub-account shows
|
||||
// its remaining (net of payments) balance.
|
||||
const subAccountCharges: Record<string, Record<string, number>> = {};
|
||||
const subAccountPayments: Record<string, Record<string, number>> = {};
|
||||
BUCKETS.forEach((b) => {
|
||||
subAccountCharges[b] = {};
|
||||
subAccountPayments[b] = {};
|
||||
});
|
||||
// 1) Sum charges per bucket / account
|
||||
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;
|
||||
if (v > 0) {
|
||||
subAccountCharges[b][acctLabel] = (subAccountCharges[b][acctLabel] || 0) + v;
|
||||
}
|
||||
});
|
||||
});
|
||||
// 2) Distribute each payment's bucket allocation proportionally
|
||||
// across sub-accounts by their share of charges in that bucket.
|
||||
withRunning.forEach((e: any) => {
|
||||
if (!(num(e.payment) > 0) || !e.allocations) return;
|
||||
BUCKETS.forEach((b) => {
|
||||
const allocAmt = num(e.allocations[b]);
|
||||
if (allocAmt <= 0) return;
|
||||
const charges = subAccountCharges[b];
|
||||
const totalCharged = Object.values(charges).reduce((s, v) => s + v, 0);
|
||||
if (totalCharged <= 0) return;
|
||||
Object.keys(charges).forEach((acctLabel) => {
|
||||
const share = charges[acctLabel] / totalCharged;
|
||||
subAccountPayments[b][acctLabel] =
|
||||
(subAccountPayments[b][acctLabel] || 0) + allocAmt * share;
|
||||
});
|
||||
});
|
||||
});
|
||||
// 3) Net remaining = charges − payments per sub-account
|
||||
const subAccountNet: Record<string, Record<string, number>> = {};
|
||||
BUCKETS.forEach((b) => {
|
||||
subAccountNet[b] = {};
|
||||
Object.keys(subAccountCharges[b]).forEach((acctLabel) => {
|
||||
const charged = subAccountCharges[b][acctLabel] || 0;
|
||||
const paid = subAccountPayments[b][acctLabel] || 0;
|
||||
subAccountNet[b][acctLabel] = charged - paid;
|
||||
});
|
||||
});
|
||||
|
||||
const breakdownBody: any[] = [];
|
||||
const DISPLAY_ORDER: Array<typeof BUCKETS[number]> = [
|
||||
@@ -859,8 +893,9 @@ export function CollectionDetail({
|
||||
{ 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);
|
||||
const subs = subAccountNet[b];
|
||||
// Hide sub-accounts that are fully paid off (≤ 0.005 to avoid float noise)
|
||||
const subKeys = Object.keys(subs).filter((k) => Math.abs(subs[k]) > 0.005);
|
||||
if (subKeys.length > 0) {
|
||||
subKeys
|
||||
.sort((a, z) => subs[z] - subs[a])
|
||||
|
||||
Reference in New Issue
Block a user