mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
Reconciliation Checks: enable PDF/CSV download
Extracted the check computation into buildReconChecks() and exposed it to the report exporter (exportFlat), so the Reconciliation Checks report now downloads as a branded PDF/CSV (Check · Residual · Status) like the other reports. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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<string, AR>();
|
||||
@@ -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 <div className="text-sm text-muted-foreground">Loading…</div>;
|
||||
// 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 <div className="text-sm text-muted-foreground">Loading…</div>;
|
||||
const checks = buildReconChecks(d);
|
||||
const ok = (r: number) => Math.abs(r) < 0.005;
|
||||
const allPass = checks.every((c) => c.pass);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user