mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
Opening Balances: fix duplicate earnings accounts; recognize name variants
The auto-provision effect could fire twice and create duplicate "Current Year Earnings" accounts (seen on Village Grove). Use the idempotent coa_get_or_create RPC with a once-per-company ref guard, and match existing name variants (Current Year Income / Net Income / Retained Earnings) so it won't create redundant accounts. Balance sheet now also folds "Current Year Income"/"Net Income" equity accounts into the Net Income line. Removed the existing Village Grove duplicate (zero activity). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
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 { format } from "date-fns";
|
||||||
import { accounting } from "@/lib/accountingClient";
|
import { accounting } from "@/lib/accountingClient";
|
||||||
import { useCompanyId } from "./lib/useCompanyId";
|
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
|
// so they can be entered directly in Opening Balances. The Balance Sheet folds
|
||||||
// their balances into the "Retained Earnings (prior years)" and "Net Income"
|
// 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.
|
// 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<string | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!cid) return;
|
if (!cid || provisionedRef.current === cid) return;
|
||||||
const list = accounts as any[];
|
if ((accounts as any[]).length === 0) return; // wait for accounts to load
|
||||||
if (list.length === 0) return; // wait for accounts to load before deciding
|
provisionedRef.current = cid;
|
||||||
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 () => {
|
(async () => {
|
||||||
const { error } = await accounting.from("accounts").insert(
|
await (accounting as any).rpc("coa_get_or_create", {
|
||||||
missing.map((w) => ({ company_id: cid, name: w.name, type: "equity" }))
|
_company_id: cid, _match: ["Retained Earnings"], _code: null, _name: "Retained Earnings", _type: "equity",
|
||||||
);
|
});
|
||||||
if (!error) qc.invalidateQueries({ queryKey: ["accounts", cid] });
|
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]);
|
}, [accounts, cid, qc]);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// calculated prior-years and Net Income lines (instead of separate lines) so the
|
||||||
// figures the user enters in Opening Balances show up where expected.
|
// 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 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 foldedIds = new Set([...reAccts, ...cyeAccts].map((a) => a.id));
|
||||||
const otherEquity = equityAccs.filter((a) => !foldedIds.has(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 });
|
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 });
|
||||||
|
|||||||
Reference in New Issue
Block a user