diff --git a/src/pages/accounting/AccountingReportsPage.tsx b/src/pages/accounting/AccountingReportsPage.tsx index 095e535..36d777c 100644 --- a/src/pages/accounting/AccountingReportsPage.tsx +++ b/src/pages/accounting/AccountingReportsPage.tsx @@ -101,17 +101,20 @@ function shiftBack(from: string, to: string) { // falls past row 1000 silently read as $0). Order by a stable key (id) so the // pages don't overlap or skip rows. const GL_PAGE = 1000; -async function fetchAllGLLines(cid: string, to: string, select: string, from?: string): Promise { +async function fetchAllGLLines(cid: string, to: string, select: string, from?: string, includeArchived = false): Promise { const out: any[] = []; for (let offset = 0; ; offset += GL_PAGE) { let q = accounting .from("journal_entry_lines") .select(select) .eq("journal_entries.company_id", cid) - // Archived accounts stay off the financial statements (their history - // remains visible in the General Ledger report, which queries directly). - .eq("accounts.is_archived", false) .lte("journal_entries.date", to); + // Archived accounts are normally kept off the financial statements, BUT an + // archived account that still holds a balance MUST stay on them — otherwise + // its balance is silently dropped and the Balance Sheet goes out of balance + // by exactly that amount. Including archived accounts here is safe: this + // queries journal lines, so archived accounts with no activity never appear. + if (!includeArchived) q = q.eq("accounts.is_archived", false); if (from) q = q.gte("journal_entries.date", from); const { data, error } = await q.order("id", { ascending: true }).range(offset, offset + GL_PAGE - 1); if (error) throw error; @@ -140,9 +143,9 @@ export async function fetchReportData(cid: string, from: string, to: string) { // All bills (not date-filtered) for AP aging accounting.from("bills").select("id,vendor_id,total,paid_amount,status,due_date,issue_date,vendors(id,name)").eq("company_id", cid), // General-ledger lines in period — P&L is built from these, grouped by account - fetchAllGLLines(cid, to, "id,debit,credit,accounts!inner(id,name,code,type,parent_account_id),journal_entries!inner(company_id,date)", from), + fetchAllGLLines(cid, to, "id,debit,credit,accounts!inner(id,name,code,type,parent_account_id),journal_entries!inner(company_id,date)", from, true), // Cumulative GL through `to` — Balance Sheet is built from these (as-of balances) - fetchAllGLLines(cid, to, "id,debit,credit,account_id,accounts!inner(type),journal_entries!inner(company_id,date)"), + fetchAllGLLines(cid, to, "id,debit,credit,account_id,accounts!inner(id,name,code,type),journal_entries!inner(company_id,date)", undefined, true), // 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). @@ -1629,10 +1632,25 @@ function buildBalanceSheet(d: any, p?: any, useCompare?: boolean): StructuredRep const accounts = (d.accounts ?? []) as any[]; const cur = bsBalances(d); const prev = useCompare && p ? bsBalances(p) : undefined; + // Surface accounts missing from the active COA list (i.e. archived) that still + // carry a balance, so their balance lands on the statement instead of being + // dropped (which would unbalance the Balance Sheet). Archived income/expense + // accounts already flow into Net Income via bsBalances; asset/liability/equity + // ones must appear as line items here. Zero-balance accounts are not added. + const knownIds = new Set(accounts.map((a) => a.id)); + const extraAccounts: any[] = []; + const seenExtra = new Set(); + for (const l of (d.glCumulative ?? []) as any[]) { + const id = l.account_id; const meta = l.accounts; + if (!meta || !id || knownIds.has(id) || seenExtra.has(id)) continue; + seenExtra.add(id); + if (Math.abs(cur.glByAcct.get(id) ?? 0) > 0.005) extraAccounts.push({ id, name: meta.name, code: meta.code, type: meta.type }); + } + const allAccounts = extraAccounts.length ? [...accounts, ...extraAccounts] : accounts; const balOf = (a: any) => (cur.glByAcct.get(a.id) ?? 0); const balOfP = (a: any) => (prev ? (prev.glByAcct.get(a.id) ?? 0) : undefined); const cmp = (v: number | undefined) => (prev ? v : undefined); - const byType = (t: string) => accounts.filter((a) => a.type === t); + const byType = (t: string) => allAccounts.filter((a) => a.type === t); const sumBal = (rows: any[]) => rows.reduce((s, a) => s + balOf(a), 0); const sumBalP = (rows: any[]) => (prev ? rows.reduce((s, a) => s + (prev.glByAcct.get(a.id) ?? 0), 0) : undefined);