From bd4c385478981a1642b08659ff19d252cdbdbb87 Mon Sep 17 00:00:00 2001 From: renee-png Date: Tue, 9 Jun 2026 01:23:57 -0400 Subject: [PATCH] Accounting reports: page through all GL lines (fix 1000-row truncation) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostgREST caps each response at 1000 rows. The P&L and Balance Sheet fetch every journal_entry_line for the company and aggregate client-side, so any association with >1000 GL lines (e.g. an imported Buildium GL) only saw the first 1000 — accounts whose activity fell past that point read as $0 and were hidden, making reports look empty/out-of-balance. Fetch the GL in 1000-row pages ordered by a stable key so every line is included. Co-Authored-By: Claude Opus 4.8 --- .../accounting/AccountingReportsPage.tsx | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/pages/accounting/AccountingReportsPage.tsx b/src/pages/accounting/AccountingReportsPage.tsx index d256459..1c8b7b0 100644 --- a/src/pages/accounting/AccountingReportsPage.tsx +++ b/src/pages/accounting/AccountingReportsPage.tsx @@ -87,6 +87,30 @@ function shiftBack(from: string, to: string) { return { from: pFrom.toISOString().slice(0, 10), to: pTo.toISOString().slice(0, 10) }; } +// PostgREST caps each response at 1000 rows. Reports aggregate GL lines +// client-side, so for companies with >1000 journal lines we must page through +// all of them — otherwise balances are truncated (accounts whose activity +// 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 { + 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) + .lte("journal_entries.date", to); + 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; + const rows = (data ?? []) as any[]; + out.push(...rows); + if (rows.length < GL_PAGE) break; + } + return out; +} + function useReportData(cid: string, from: string, to: string) { return useQuery({ @@ -109,16 +133,9 @@ function useReportData(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 - accounting.from("journal_entry_lines") - .select("debit,credit,accounts!inner(id,name,code,type,parent_account_id),journal_entries!inner(company_id,date)") - .eq("journal_entries.company_id", cid) - .gte("journal_entries.date", from) - .lte("journal_entries.date", to), + fetchAllGLLines(cid, to, "id,debit,credit,accounts!inner(id,name,code,type,parent_account_id),journal_entries!inner(company_id,date)", from), // Cumulative GL through `to` — Balance Sheet is built from these (as-of balances) - accounting.from("journal_entry_lines") - .select("debit,credit,account_id,accounts!inner(type),journal_entries!inner(company_id,date)") - .eq("journal_entries.company_id", cid) - .lte("journal_entries.date", to), + fetchAllGLLines(cid, to, "id,debit,credit,account_id,accounts!inner(type),journal_entries!inner(company_id,date)"), // 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). @@ -132,8 +149,8 @@ function useReportData(cid: string, from: string, to: string) { openingBalances: ob.data ?? [], ytdInvoices: ytdInv.data ?? [], ytdExpenses: ytdExp.data ?? [], ytdBills: ytdBills.data ?? [], allBills: allBills.data ?? [], - glLines: glRes.data ?? [], - glCumulative: glCumRes.data ?? [], + glLines: glRes ?? [], + glCumulative: glCumRes ?? [], allInvoices: allInvRes.data ?? [], glManaged: companyRes.data ? companyRes.data.gl_auto_post !== false : true, from, asOf: to,