Prepaid Homeowners + AR Aging (Property): roll ledger up to the unit, show current owner

Owner-ledger entries tied only to an old owner_id grouped separately,
so former owners showed big prepaid balances and current owners were
missing. Map each entry to its unit (via the owner's unit when the entry
has no unit_id) so balances net per unit, and label each unit with its
current (active, primary) owner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 00:55:13 -04:00
parent e11a48e8bf
commit ee10f74753
2 changed files with 16 additions and 4 deletions
@@ -99,10 +99,13 @@ export function ARAgingPropertyReport({ companyId, companyName, logoUrl, to: pro
if (c.owner_id && !collByOwner.has(c.owner_id)) collByOwner.set(c.owner_id, c.status); if (c.owner_id && !collByOwner.has(c.owner_id)) collByOwner.set(c.owner_id, c.status);
} }
// Group ledger entries per unit (fall back to owner when the entry has no unit) // Group ledger entries per unit. Entries tied only to an owner_id (often a
// former owner) are mapped to that owner's unit so the balance rolls into
// the unit and shows under its current owner — not a separate old-owner row.
const byUnit = new Map<string, OwnerLedgerEntry[]>(); const byUnit = new Map<string, OwnerLedgerEntry[]>();
for (const e of entries) { for (const e of entries) {
const key = e.unit_id ? `u:${e.unit_id}` : e.owner_id ? `o:${e.owner_id}` : null; const unitId = e.unit_id ?? (e.owner_id ? ownerById.get(e.owner_id)?.unit_id ?? null : null);
const key = unitId ? `u:${unitId}` : e.owner_id ? `o:${e.owner_id}` : null;
if (!key) continue; if (!key) continue;
const list = byUnit.get(key) ?? []; const list = byUnit.get(key) ?? [];
list.push(e); list.push(e);
@@ -46,12 +46,21 @@ export function PrepaidHomeownersReport({ companyId, companyName, logoUrl, to: p
for (const u of data.units) unitById.set(u.id, u); for (const u of data.units) unitById.set(u.id, u);
const ownerById = new Map<string, OwnerInfo>(); const ownerById = new Map<string, OwnerInfo>();
for (const o of data.owners) ownerById.set(o.id, o); for (const o of data.owners) ownerById.set(o.id, o);
// Current owner per unit (active + primary first) so balances aren't tagged
// to a moved-out owner.
const ownerRank = (o: OwnerInfo) =>
((o.status == null || o.status === "active") ? 0 : 2) + (o.is_primary ? 0 : 1);
const ownerByUnit = new Map<string, OwnerInfo>(); const ownerByUnit = new Map<string, OwnerInfo>();
for (const o of data.owners) if (o.unit_id && !ownerByUnit.has(o.unit_id)) ownerByUnit.set(o.unit_id, o); for (const o of [...data.owners].sort((a, b) => ownerRank(a) - ownerRank(b)))
if (o.unit_id && !ownerByUnit.has(o.unit_id)) ownerByUnit.set(o.unit_id, o);
// Roll every entry up to its UNIT (funds attach to the unit). Entries tied
// only to an owner_id (often a former owner) are mapped to that owner's unit
// so old-owner credits net against the unit instead of showing separately.
const bal = new Map<string, number>(); const bal = new Map<string, number>();
for (const e of data.entries) { for (const e of data.entries) {
const key = e.unit_id ? `u:${e.unit_id}` : e.owner_id ? `o:${e.owner_id}` : null; const unitId = e.unit_id ?? (e.owner_id ? ownerById.get(e.owner_id)?.unit_id ?? null : null);
const key = unitId ? `u:${unitId}` : e.owner_id ? `o:${e.owner_id}` : null;
if (!key) continue; if (!key) continue;
bal.set(key, (bal.get(key) ?? 0) + e.debit - e.credit); bal.set(key, (bal.get(key) ?? 0) + e.debit - e.credit);
} }