mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
Accounting: add account/txn detail modes to buildium-payee-backfill
account mode lists a single account's ledger entries; txn mode reconstructs one transaction's full double-entry across accounts. Used to trace the VW 5098 discrepancy to a missing 2025-12-31 reserve-funded reclass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -193,6 +193,68 @@ Deno.serve(async (req) => {
|
|||||||
return json({ mode, company: company.name, window: [dateFrom, dateTo], distinctTxns: txnIds.size, accounts: rows.length, rows });
|
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<string, { number: string; name: string }>();
|
||||||
|
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
|
// 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>();
|
||||||
|
|||||||
Reference in New Issue
Block a user