diff --git a/src/pages/accounting/AccountingChartOfAccountsPage.tsx b/src/pages/accounting/AccountingChartOfAccountsPage.tsx index b5f188c..af40e29 100644 --- a/src/pages/accounting/AccountingChartOfAccountsPage.tsx +++ b/src/pages/accounting/AccountingChartOfAccountsPage.tsx @@ -99,6 +99,28 @@ export default function AccountingChartOfAccountsPage() { } }, [obSetup]); + // Ensure "Retained Earnings" and "Current Year Earnings" equity accounts exist + // so they can be entered directly in Opening Balances. The Balance Sheet folds + // their balances into the "Retained Earnings (prior years)" and "Net Income" + // lines, so a mid-year import can seed both without entering income/expense detail. + useEffect(() => { + if (!cid) return; + const list = accounts as any[]; + if (list.length === 0) return; // wait for accounts to load before deciding + const want = [ + { name: "Retained Earnings", re: /retained\s*earnings/i }, + { name: "Current Year Earnings", re: /current\s*year\s*earnings/i }, + ]; + const missing = want.filter((w) => !list.some((a) => a.type === "equity" && w.re.test(String(a.name || "")))); + if (missing.length === 0) return; + (async () => { + const { error } = await accounting.from("accounts").insert( + missing.map((w) => ({ company_id: cid, name: w.name, type: "equity" })) + ); + if (!error) qc.invalidateQueries({ queryKey: ["accounts", cid] }); + })(); + }, [accounts, cid, qc]); + useEffect(() => { const map: Record = {}; for (const b of balances as any[]) { diff --git a/src/pages/accounting/AccountingReportsPage.tsx b/src/pages/accounting/AccountingReportsPage.tsx index abc8635..ccb6444 100644 --- a/src/pages/accounting/AccountingReportsPage.tsx +++ b/src/pages/accounting/AccountingReportsPage.tsx @@ -1174,14 +1174,21 @@ function buildBalanceSheet(d: any, p?: any, useCompare?: boolean): StructuredRep // A real "Retained Earnings" equity account is postable via journal entries. // Fold its posted balance into the calculated "Retained Earnings (prior years)" // line so manual JE adjustments show there instead of as a separate line. + // Posted "Retained Earnings" and "Current Year Earnings" equity accounts are + // postable via journal entries / opening balances. Fold their balances into the + // calculated prior-years and Net Income lines (instead of separate lines) so the + // figures the user enters in Opening Balances show up where expected. const reAccts = equityAccs.filter((a) => /retained\s+earnings/i.test(String(a.name || ""))); - const reIds = new Set(reAccts.map((a) => a.id)); - const otherEquity = equityAccs.filter((a) => !reIds.has(a.id)); + const cyeAccts = equityAccs.filter((a) => /current\s*year\s*earnings/i.test(String(a.name || ""))); + const foldedIds = new Set([...reAccts, ...cyeAccts].map((a) => a.id)); + const otherEquity = equityAccs.filter((a) => !foldedIds.has(a.id)); for (const a of otherEquity) rows.push({ kind: "sub", label: a.name, code: a.code ?? undefined, amount: balOf(a), compare: cmp(balOfP(a)), accountId: a.id }); const rePosted = reAccts.reduce((s, a) => s + balOf(a), 0); const rePostedP = prev ? reAccts.reduce((s, a) => s + (prev.glByAcct.get(a.id) ?? 0), 0) : undefined; + const cyePosted = cyeAccts.reduce((s, a) => s + balOf(a), 0); + const cyePostedP = prev ? cyeAccts.reduce((s, a) => s + (prev.glByAcct.get(a.id) ?? 0), 0) : undefined; rows.push({ kind: "sub", label: "Retained Earnings (prior years)", amount: cur.rePrior + rePosted, compare: cmp(prev ? prev.rePrior + (rePostedP ?? 0) : undefined) }); - rows.push({ kind: "sub", label: "Net Income", amount: cur.cye, compare: cmp(prev?.cye) }); + rows.push({ kind: "sub", label: "Net Income", amount: cur.cye + cyePosted, compare: cmp(prev ? prev.cye + (cyePostedP ?? 0) : undefined) }); const totalE = sumBal(equityAccs) + cur.rePrior + cur.cye; const totalEP = prev ? (sumBalP(equityAccs)! + prev.rePrior + prev.cye) : undefined; rows.push({ kind: "total", label: "Total Equity", amount: totalE, compare: cmp(totalEP) });