diff --git a/supabase/functions/buildium-payee-backfill/index.ts b/supabase/functions/buildium-payee-backfill/index.ts index 543043a..a1d987d 100644 --- a/supabase/functions/buildium-payee-backfill/index.ts +++ b/supabase/functions/buildium-payee-backfill/index.ts @@ -193,6 +193,68 @@ Deno.serve(async (req) => { return json({ mode, company: company.name, window: [dateFrom, dateTo], distinctTxns: txnIds.size, accounts: rows.length, rows }); } + // account mode: list every /v1/generalledger entry for a single account + // (by AccountNumber) in the window — used to chase per-account discrepancies. + if (mode === "account") { + const wantNum = String(body?.accountNumber ?? ""); + let gid = ""; + const findGl = (g: any) => { + if (String(g?.AccountNumber ?? "") === wantNum) gid = String(g.Id); + if (Array.isArray(g.SubAccounts)) for (const s of g.SubAccounts) findGl(s); + }; + for (const g of glAccounts) findGl(g); + if (!gid) return json({ error: `account number ${wantNum} not found in Buildium chart` }, 404); + const params = new URLSearchParams(); + params.set("accountingbasis", "Accrual"); + params.set("startdate", dateFrom); + params.set("enddate", dateTo); + params.set("entitytype", "Association"); + params.set("entityid", String(bAssocId)); + params.append("glaccountids", gid); + const ledgers = await buildiumFetchAll("/v1/generalledger", clientId, clientSecret, params); + const entries: any[] = []; + let net = 0; + for (const ledger of ledgers) for (const e of ledger.Entries ?? []) { + net += Number(e.Amount) || 0; + entries.push({ txnId: String(e.Id ?? ""), date: String(e.Date ?? "").split("T")[0], type: e.TransactionType, amount: Number(e.Amount) || 0, memo: e.Memo ?? e.Description ?? "" }); + } + entries.sort((a, b) => a.date.localeCompare(b.date)); + return json({ mode, accountNumber: wantNum, gid, window: [dateFrom, dateTo], net: Math.round(net * 100) / 100, count: entries.length, entries }); + } + + // txn mode: reconstruct one transaction's full double-entry by scanning the + // ledger across all accounts for entries carrying its id. + if (mode === "txn") { + const wantId = String(body?.txnId ?? ""); + const numByGid = new Map(); + const collect = (g: any) => { + if (g?.Id) numByGid.set(String(g.Id), { number: String(g.AccountNumber ?? ""), name: String(g.Name ?? "") }); + if (Array.isArray(g.SubAccounts)) for (const s of g.SubAccounts) collect(s); + }; + for (const g of glAccounts) collect(g); + const lines: any[] = []; + for (let i = 0; i < allGlIds.length; i += CHUNK) { + const params = new URLSearchParams(); + params.set("accountingbasis", "Accrual"); + params.set("startdate", dateFrom); + params.set("enddate", dateTo); + params.set("entitytype", "Association"); + params.set("entityid", String(bAssocId)); + for (const id of allGlIds.slice(i, i + CHUNK)) params.append("glaccountids", id); + const ledgers = await buildiumFetchAll("/v1/generalledger", clientId, clientSecret, params); + for (const ledger of ledgers) { + const gid = String(ledger.GLAccountId ?? ledger.GLAccount?.Id ?? ""); + for (const e of ledger.Entries ?? []) { + if (String(e.Id ?? "") !== wantId) continue; + const m = numByGid.get(gid) ?? { number: "", name: gid }; + lines.push({ account: `${m.number} ${m.name}`.trim(), amount: Number(e.Amount) || 0, date: String(e.Date ?? "").split("T")[0], type: e.TransactionType, memo: e.Memo ?? e.Description ?? "" }); + } + } + } + const sum = Math.round(lines.reduce((s, l) => s + l.amount, 0) * 100) / 100; + return json({ mode, txnId: wantId, balanceCheck: sum, lineCount: lines.length, lines }); + } + // Pull GL transactions for the window. /v1/generalledger/transactions is the // Journal view — one row per transaction with its Id, type and party data. const txById = new Map();