diff --git a/src/pages/accounting/AccountingReportsPage.tsx b/src/pages/accounting/AccountingReportsPage.tsx index 56b423b..de33505 100644 --- a/src/pages/accounting/AccountingReportsPage.tsx +++ b/src/pages/accounting/AccountingReportsPage.tsx @@ -24,7 +24,7 @@ import { renderReportPdf, fmtAmount, type StructuredReport, type StructuredRow, } from "./lib/reportPdf"; -import { reconcile, type RecAccount, type RecLine } from "./lib/reconcile"; +import { reconcile, type RecAccount, type RecLine, type RecCheck } from "./lib/reconcile"; import { computePnL, computeMargins, toMinor, fromMinor, PnlValidationError, type PnlAccount, type PnlClassification, type Posting as PnlPosting, type PnlResult, @@ -397,6 +397,17 @@ export default function AccountingReportsPage({ association }: { association?: { const m = (n: number) => money(n, cur); const now = new Date(); + if (active === "reconciliation") { + if (!data) return null; + const checks = buildReconChecks(data); + return { + title: "Reconciliation Checks", + columns: ["Check", "Residual", "Status"], + rows: checks.map((c) => [`${c.id} ${c.label}`, m(c.residual), c.pass ? "Pass" : "FAIL"]), + boldRows: [], + }; + } + if (active === "ar-aging" || active === "customer-balances") { type AR = { name: string; current: number; d30: number; d60: number; d90: number; d90p: number; total: number }; const byC = new Map(); @@ -1251,18 +1262,12 @@ function PreviewSheet({ report, companyName, rangeLabel, showCodes, showCompare, // ---------- Financial report builders (structured) ---------- // Reconciliation matrix (§9) surfaced as visible residuals — never plug a residual. -function ReconciliationReport({ d, currency }: { d: any; currency: string }) { - if (!d) return
Loading…
; +// Shared so both the on-screen report and the PDF/CSV export run identical checks. +function buildReconChecks(d: any): RecCheck[] { const accounts: RecAccount[] = ((d.accounts ?? []) as any[]).map((a) => ({ 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; @@ -1276,21 +1281,22 @@ function ReconciliationReport({ d, currency }: { d: any; currency: string }) { })); const openInv = ((d.allInvoices ?? []) as any[]).filter((i) => i.status !== "void").reduce((s, i) => s + (Number(i.total || 0) - Number(i.paid_amount || 0)), 0); const openBill = ((d.allBills ?? []) as any[]).filter((b) => b.status !== "void").reduce((s, b) => s + (Number(b.total || 0) - Number(b.paid_amount || 0)), 0); - - // Cross-path figures from the report builders so R3/R5 verify the builders agree - // with the raw GL. (P&L net income vs GL; Movement-of-Equity ending vs Balance Sheet.) const pl = buildPnL(d, undefined, false); const plNI = pl.rows.find((r) => r.kind === "grand" && /net income/i.test(r.label))?.amount; const bs = buildBalanceSheet(d); const bsEquity = bs.rows.find((r) => r.kind === "total" && /total equity/i.test(r.label))?.amount; const sce = buildMovementOfEquity(d, undefined, false); const sceEnding = sce.rows.find((r) => r.kind === "grand" && /closing equity/i.test(r.label))?.amount; - - const checks = reconcile({ + return reconcile({ accounts, lines, periodStart: d.from, openInvoices: openInv, openBills: openBill, arApApplicable: d.glManaged, reportPLNetIncome: plNI, sceEndingEquity: sceEnding, bsTotalEquity: bsEquity, }); +} + +function ReconciliationReport({ d, currency }: { d: any; currency: string }) { + if (!d) return
Loading…
; + const checks = buildReconChecks(d); const ok = (r: number) => Math.abs(r) < 0.005; const allPass = checks.every((c) => c.pass);