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,