diff --git a/src/pages/accounting/AccountingChartOfAccountsPage.tsx b/src/pages/accounting/AccountingChartOfAccountsPage.tsx index af40e29..7b8bcef 100644 --- a/src/pages/accounting/AccountingChartOfAccountsPage.tsx +++ b/src/pages/accounting/AccountingChartOfAccountsPage.tsx @@ -1,5 +1,5 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { Fragment, useEffect, useMemo, useState } from "react"; +import { Fragment, useEffect, useMemo, useRef, useState } from "react"; import { format } from "date-fns"; import { accounting } from "@/lib/accountingClient"; import { useCompanyId } from "./lib/useCompanyId"; @@ -103,21 +103,21 @@ export default function AccountingChartOfAccountsPage() { // 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. + // Uses the idempotent coa_get_or_create RPC (recognizes name variants) and a + // once-per-company ref guard so the effect can't create duplicates if it re-fires. + const provisionedRef = useRef(null); 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; + if (!cid || provisionedRef.current === cid) return; + if ((accounts as any[]).length === 0) return; // wait for accounts to load + provisionedRef.current = cid; (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] }); + await (accounting as any).rpc("coa_get_or_create", { + _company_id: cid, _match: ["Retained Earnings"], _code: null, _name: "Retained Earnings", _type: "equity", + }); + await (accounting as any).rpc("coa_get_or_create", { + _company_id: cid, _match: ["Current Year Earnings", "Current Year Income", "Net Income"], _code: null, _name: "Current Year Earnings", _type: "equity", + }); + qc.invalidateQueries({ queryKey: ["accounts", cid] }); })(); }, [accounts, cid, qc]); diff --git a/src/pages/accounting/AccountingReportsPage.tsx b/src/pages/accounting/AccountingReportsPage.tsx index ccb6444..1be505e 100644 --- a/src/pages/accounting/AccountingReportsPage.tsx +++ b/src/pages/accounting/AccountingReportsPage.tsx @@ -1179,7 +1179,7 @@ function buildBalanceSheet(d: any, p?: any, useCompare?: boolean): StructuredRep // 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 cyeAccts = equityAccs.filter((a) => /current\s*year\s*earnings/i.test(String(a.name || ""))); + const cyeAccts = equityAccs.filter((a) => /current\s*year\s*(earnings|income)/i.test(String(a.name || "")) || /^\s*net\s*income\s*$/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 });