Accounting: add report mode to buildium-payee-backfill

report mode pulls /v1/generalledger signed balances per account for the
window so imported books can be diffed against Buildium's authoritative GL
to verify completeness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 17:18:34 -04:00
parent 3a7e08fb78
commit e999890ee5
@@ -156,11 +156,46 @@ Deno.serve(async (req) => {
if (gid) glIds.add(gid); if (gid) glIds.add(gid);
} }
const allGlIds = [...glIds]; const allGlIds = [...glIds];
const CHUNK = 50;
// report mode: pull /v1/generalledger (signed amounts, debit +/credit ) and
// sum net movement per account for the window, to diff against our books.
if (mode === "report") {
const glMeta = new Map<string, { number: string; name: string; type: string }>();
const collectMeta = (g: any) => {
if (g?.Id) glMeta.set(String(g.Id), { number: String(g.AccountNumber ?? ""), name: String(g.Name ?? ""), type: String(g.Type ?? "") });
if (Array.isArray(g.SubAccounts)) for (const s of g.SubAccounts) collectMeta(s);
};
for (const g of glAccounts) collectMeta(g);
const netById = new Map<string, number>();
const txnIds = new Set<string>();
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 ?? []) {
netById.set(gid, (netById.get(gid) ?? 0) + (Number(e.Amount) || 0));
if (e.Id) txnIds.add(String(e.Id));
}
}
}
const rows = [...netById.entries()]
.map(([gid, net]) => ({ ...(glMeta.get(gid) ?? { number: "", name: gid, type: "" }), net: Math.round(net * 100) / 100 }))
.filter((r) => Math.abs(r.net) > 0.005)
.sort((a, b) => a.number.localeCompare(b.number));
return json({ mode, company: company.name, window: [dateFrom, dateTo], distinctTxns: txnIds.size, accounts: rows.length, rows });
}
// Pull GL transactions for the window. /v1/generalledger/transactions is the // Pull GL transactions for the window. /v1/generalledger/transactions is the
// Journal view — one row per transaction with its Id, type and party data. // Journal view — one row per transaction with its Id, type and party data.
const txById = new Map<string, any>(); const txById = new Map<string, any>();
const CHUNK = 50;
for (let i = 0; i < allGlIds.length; i += CHUNK) { for (let i = 0; i < allGlIds.length; i += CHUNK) {
const params = new URLSearchParams(); const params = new URLSearchParams();
params.set("startdate", dateFrom); params.set("startdate", dateFrom);