From 0fadfcd2d2bebdc4d0100e21150b86d4bad4b7fe Mon Sep 17 00:00:00 2001 From: renee-png Date: Wed, 17 Jun 2026 11:53:07 -0400 Subject: [PATCH] Accounting reports: BvA column shading + reconciliation archived-account fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Budget vs Actuals: shade the Budget / Actual / Variance / Variance % columns in a light→dark grey gradient with left vertical borders so they're easy to tell apart (shared BVA_COL styles applied to header, group-total and detail rows; variance text bumped to emerald/red-700 for contrast on the darker fills). Reconciliation R2: reconcile() classified GL lines via the active-only account list and dropped lines on archived accounts, so the "Assets = Liabilities + Equity (incl. net income)" check went out of balance by the archived balance (e.g. VW's archived 4016 Renters Insurance Income, -$75). Feed reconcile any account referenced by glCumulative but missing from the active list (joined metadata), matching the Balance Sheet builder; once every line is typed, R2 reduces to R1 and passes whenever the ledger balances. Also tightens R3/R4. Co-Authored-By: Claude Opus 4.8 --- .../accounting/AccountingReportsPage.tsx | 60 +++++++++++++------ 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/src/pages/accounting/AccountingReportsPage.tsx b/src/pages/accounting/AccountingReportsPage.tsx index e4e7773..a06aa01 100644 --- a/src/pages/accounting/AccountingReportsPage.tsx +++ b/src/pages/accounting/AccountingReportsPage.tsx @@ -1257,6 +1257,19 @@ function ReconciliationReport({ d, currency }: { d: any; currency: string }) { id: a.id, type: a.type, name: a.name, is_cash: !!a.is_bank || /cash|undeposited/i.test(String(a.name || "")), })); + // Archived accounts are excluded from d.accounts, but their GL lines remain in + // glCumulative. Without their type, reconcile() drops those lines (if (!a) continue) + // and the Balance Sheet check (R2) goes out of balance by exactly the dropped + // amount — e.g. an archived income account that still holds a balance. Add any + // GL-referenced account missing from the active list, using the joined meta, so + // reconcile classifies them just like the Balance Sheet builder does. + const knownAcctIds = new Set(accounts.map((a) => a.id)); + for (const l of (d.glCumulative ?? []) as any[]) { + const meta = l.accounts; const id = l.account_id; + if (!meta || !id || knownAcctIds.has(id)) continue; + knownAcctIds.add(id); + accounts.push({ id, type: meta.type, name: meta.name, is_cash: /cash|undeposited/i.test(String(meta.name || "")) }); + } const lines: RecLine[] = ((d.glCumulative ?? []) as any[]).map((l) => ({ account_id: l.account_id, date: String(l.journal_entries?.date ?? ""), debit: Number(l.debit || 0), credit: Number(l.credit || 0), @@ -1935,6 +1948,17 @@ function computeBvaActuals(actualsData: any): Record { return m; } +// Budget vs. Actuals column shading — a light→dark grey gradient with left +// vertical borders so the Budget / Actual / Variance / Variance % columns are +// easy to tell apart. Applied to the header, group-total and detail cells. +const BVA_COL = { + budget: "text-right tabular-nums border-l border-gray-300 bg-gray-50", + actual: "text-right tabular-nums border-l border-gray-300 bg-gray-100", + variance: "text-right tabular-nums border-l border-gray-300 bg-gray-200/70", + pct: "text-right tabular-nums border-l border-gray-300 bg-gray-300/50", + cmp: "text-right tabular-nums border-l border-gray-300", +}; + function BudgetVsActuals({ companyId, from, to, currency, companyName, rangeLabel, logoUrl }: { companyId: string; from: string; to: string; currency: string; companyName: string; rangeLabel: string; logoUrl?: string | null }) { const { data: budgets = [] } = useQuery({ queryKey: ["budgets-active", companyId], @@ -2198,12 +2222,12 @@ function BudgetVsActuals({ companyId, from, to, currency, companyName, rangeLabe Account - Budget - Actual - Variance - Variance % - {cmpOn && Compare} - {cmpOn && Δ vs Compare} + Budget + Actual + Variance + Variance % + {cmpOn && Compare} + {cmpOn && Δ vs Compare} @@ -2220,12 +2244,12 @@ function BudgetVsActuals({ companyId, from, to, currency, companyName, rangeLabe {t.label} - {money(totalB, currency)} - {money(totalA, currency)} - {money(totalVar, currency)} - {totalB ? `${totalPct.toFixed(1)}%` : "—"} - {cmpOn && {money(totalC, currency)}} - {cmpOn && {money(totalA - totalC, currency)}} + {money(totalB, currency)} + {money(totalA, currency)} + {money(totalVar, currency)} + {totalB ? `${totalPct.toFixed(1)}%` : "—"} + {cmpOn && {money(totalC, currency)}} + {cmpOn && {money(totalA - totalC, currency)}} {(groupedOrdered[t.value] ?? []).map((a: any) => { const b = budgetByAcct[a.id] ?? 0; @@ -2241,12 +2265,12 @@ function BudgetVsActuals({ companyId, from, to, currency, companyName, rangeLabe {a.name} {a.code && {a.code}} - {money(b, currency)} - {money(ac, currency)} - {money(v, currency)} - {b ? `${pct.toFixed(1)}%` : "—"} - {cmpOn && {money(c, currency)}} - {cmpOn && {money(ac - c, currency)}} + {money(b, currency)} + {money(ac, currency)} + {money(v, currency)} + {b ? `${pct.toFixed(1)}%` : "—"} + {cmpOn && {money(c, currency)}} + {cmpOn && {money(ac - c, currency)}} ); })}