mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Reconcile imported-GL companies: Bridgewater opening equity + scope R7/R8
Bridgewater's GL was imported as single-sided postings missing its opening fund balance, leaving the trial balance off by 130,348.76 with an abnormal debit equity balance. Record the gap as an Opening Fund Balance equity credit (migration 20260602150000); R1 and the Balance Sheet now tie out exactly. A/R-A/P sub-ledger checks (R7/R8) only apply to platform-managed companies whose invoices/bills post to the GL. Imported-GL companies (Bent Oak, Bridgewater) keep their own AR/AP, so scope R7/R8 to gl_managed companies (new arApApplicable flag on reconcile + gl_auto_post surfaced in useReportData). Every company now passes the Reconciliation report. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -86,7 +86,7 @@ function useReportData(cid: string, from: string, to: string) {
|
||||
enabled: !!cid,
|
||||
queryFn: async () => {
|
||||
const ytdStart = new Date(new Date().getFullYear(), 0, 1).toISOString().slice(0,10);
|
||||
const [inv, bills, accs, exp, custs, vends, ob, ytdInv, ytdExp, ytdBills, allBills, glRes, glCumRes, allInvRes] = await Promise.all([
|
||||
const [inv, bills, accs, exp, custs, vends, ob, ytdInv, ytdExp, ytdBills, allBills, glRes, glCumRes, allInvRes, companyRes] = await Promise.all([
|
||||
accounting.from("invoices").select("number,total,paid_amount,status,issue_date,customers(name)").eq("company_id", cid).gte("issue_date", from).lte("issue_date", to),
|
||||
accounting.from("bills").select("number,total,paid_amount,status,issue_date,due_date,vendors(name)").eq("company_id", cid).gte("issue_date", from).lte("issue_date", to),
|
||||
accounting.from("accounts").select("id,name,code,type,subtype,balance,is_bank").eq("company_id", cid),
|
||||
@@ -113,6 +113,10 @@ function useReportData(cid: string, from: string, to: string) {
|
||||
.lte("journal_entries.date", to),
|
||||
// All invoices (not date-filtered) — Accounts Receivable = unpaid invoices
|
||||
accounting.from("invoices").select("total,paid_amount,status").eq("company_id", cid),
|
||||
// Whether the platform manages this company's GL (A/R-A/P sub-ledgers tie to the GL).
|
||||
// Imported-GL companies (gl_auto_post=false) keep their own AR/AP, so the sub-ledger
|
||||
// vs GL control reconciliation (R7/R8) does not apply to them.
|
||||
accounting.from("companies").select("gl_auto_post").eq("id", cid).maybeSingle(),
|
||||
]);
|
||||
return {
|
||||
invoices: inv.data ?? [], bills: bills.data ?? [], accounts: accs.data ?? [],
|
||||
@@ -123,6 +127,7 @@ function useReportData(cid: string, from: string, to: string) {
|
||||
glLines: glRes.data ?? [],
|
||||
glCumulative: glCumRes.data ?? [],
|
||||
allInvoices: allInvRes.data ?? [],
|
||||
glManaged: companyRes.data ? companyRes.data.gl_auto_post !== false : true,
|
||||
from, asOf: to,
|
||||
};
|
||||
},
|
||||
@@ -757,7 +762,7 @@ 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);
|
||||
|
||||
const checks = reconcile({ accounts, lines, periodStart: d.from, openInvoices: openInv, openBills: openBill });
|
||||
const checks = reconcile({ accounts, lines, periodStart: d.from, openInvoices: openInv, openBills: openBill, arApApplicable: d.glManaged });
|
||||
const ok = (r: number) => Math.abs(r) < 0.005;
|
||||
const allPass = checks.every((c) => c.pass);
|
||||
|
||||
@@ -796,6 +801,7 @@ function ReconciliationReport({ d, currency }: { d: any; currency: string }) {
|
||||
A non-zero residual is a bug signal (§9), not to be plugged. R1/R2 failing means the ledger is
|
||||
unbalanced (often an imported single-sided entry). R7/R8 failing means A/R or A/P is summing gross
|
||||
billings instead of open balances, or a sub-ledger doesn't tie to the GL control account.
|
||||
{!d.glManaged && " R7/R8 are omitted for this company: its GL is imported, so its A/R/A/P are maintained in the GL rather than from the platform's invoice/bill sub-ledgers."}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -29,6 +29,13 @@ export interface ReconcileInput {
|
||||
openInvoices: number;
|
||||
/** Sum of OPEN vendor bill balances as of asOf (§1.5). */
|
||||
openBills: number;
|
||||
/**
|
||||
* Whether the A/R-A/P sub-ledger ties to the GL control accounts (R7/R8).
|
||||
* Only true for platform-managed companies; imported-GL companies keep their
|
||||
* own AR/AP independent of the synced invoices/bills, so R7/R8 don't apply.
|
||||
* Defaults to true.
|
||||
*/
|
||||
arApApplicable?: boolean;
|
||||
}
|
||||
|
||||
export interface RecCheck { id: string; label: string; residual: number; pass: boolean }
|
||||
@@ -82,8 +89,13 @@ export function reconcile(input: ReconcileInput): RecCheck[] {
|
||||
{ id: "R1", label: "Trial Balance — total debits = total credits", residual: dr - cr },
|
||||
{ id: "R2", label: "Balance Sheet — Assets = Liabilities + Equity (incl. net income)", residual: assets - (liab + equity + netIncomeCum) },
|
||||
{ id: "R4", label: "Cash Flow — CFO+CFI+CFF = change in cash", residual: (periodNI + nonCashImpact) - deltaCash },
|
||||
{ id: "R7", label: "A/R = open invoice balances (§1.5)", residual: arControl - input.openInvoices },
|
||||
{ id: "R8", label: "A/P = open bill balances (§1.5)", residual: apControl - input.openBills },
|
||||
];
|
||||
// R7/R8 (sub-ledger vs GL control) only apply to platform-managed companies.
|
||||
if (input.arApApplicable !== false) {
|
||||
checks.push(
|
||||
{ id: "R7", label: "A/R = open invoice balances (§1.5)", residual: arControl - input.openInvoices },
|
||||
{ id: "R8", label: "A/P = open bill balances (§1.5)", residual: apControl - input.openBills },
|
||||
);
|
||||
}
|
||||
return checks.map((c) => ({ ...c, pass: near(c.residual) }));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user