Accounting: partymap mode + register payee backfill

Adds "partymap" mode to buildium-payee-backfill: resolves each Buildium GL
transaction to a local vendor (by name) / customer (by unit_number) and stages
it in accounting.buildium_party_map. The materialized bank register
(accounting.transactions) is then backfilled in SQL by bridging each register
row to its journal-entry bank line (bijective row-number pairing within
date/amount/side groups, so same-amount/same-day payments each get a distinct
owner) and pulling the resolved vendor/customer + name from staging.

Applied to all five Buildium-imported associations (Village Woods, Bent Oak,
Village Grove, Bridgewater, Casuarina).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 19:44:42 -04:00
parent 12e551f578
commit 0faee9994d
2 changed files with 64 additions and 0 deletions
@@ -298,6 +298,51 @@ Deno.serve(async (req) => {
if (name) { payeeById.set(id, name); byType[type].named++; } if (name) { payeeById.set(id, name); byType[type].named++; }
} }
// partymap mode: resolve each Buildium tx to a LOCAL vendor/customer id and
// stage it (accounting.buildium_party_map) so the materialized bank register
// (accounting.transactions) can be backfilled in SQL by bridging tx→JE.
// vendor : Payee.Type=Vendor → match Payee.Name to accounting.vendors.name
// customer : tx.UnitNumber → match to accounting.customers.unit_number
if (mode === "partymap") {
const vendorByName = new Map<string, string>();
for (const v of (await supabase.schema("accounting").from("vendors").select("id,name").eq("company_id", companyId)).data ?? [])
if (v.name) vendorByName.set(norm(v.name), v.id);
const customerByUnitNo = new Map<string, string>();
for (const c of (await supabase.schema("accounting").from("customers").select("id,unit_number").eq("company_id", companyId)).data ?? [])
if (c.unit_number) customerByUnitNo.set(norm(c.unit_number), c.id);
const rows: any[] = [];
let vendorHits = 0, customerHits = 0;
for (const tx of txns) {
const id = String(tx?.Id ?? "");
if (!id) continue;
const name = payeeById.get(id) ?? null;
const payeeType = String(tx?.PaymentDetail?.Payee?.Type ?? "");
let vendor_id: string | null = null;
let customer_id: string | null = null;
if (payeeType === "Vendor" && name) vendor_id = vendorByName.get(norm(name)) ?? null;
const unitNo = tx?.UnitNumber;
if (!vendor_id && typeof unitNo === "string" && unitNo.trim()) customer_id = customerByUnitNo.get(norm(unitNo)) ?? null;
// owner payment with no UnitNumber: try the payee name against customers
if (!vendor_id && !customer_id && name && payeeType && payeeType !== "Vendor") {
// fall through — name match handled in SQL if needed; leave null here
}
if (vendor_id) vendorHits++;
if (customer_id) customerHits++;
if (name || vendor_id || customer_id)
rows.push({ company_id: companyId, external_id: id, party_name: name, vendor_id, customer_id, party_type: payeeType || null });
}
// Upsert in batches.
let staged = 0;
for (let i = 0; i < rows.length; i += 500) {
const batch = rows.slice(i, i + 500);
const { error } = await supabase.schema("accounting").from("buildium_party_map").upsert(batch, { onConflict: "company_id,external_id" });
if (error) throw error;
staged += batch.length;
}
return json({ mode, company: company.name, window: [dateFrom, dateTo], txns: txns.length, staged, vendorHits, customerHits });
}
// Load this company's imported entries and compute the new description. // Load this company's imported entries and compute the new description.
const entries: { id: string; external_id: string; description: string }[] = []; const entries: { id: string; external_id: string; description: string }[] = [];
for (let offset = 0; ; offset += 1000) { for (let offset = 0; ; offset += 1000) {
@@ -0,0 +1,19 @@
-- Staging table for the Buildium payee/payor register backfill.
-- Maps a Buildium GL transaction id to the resolved local vendor/customer and
-- the party name, so the materialized bank register (accounting.transactions)
-- of imported-GL companies can be backfilled by bridging transaction → journal
-- entry (by bank line) → this map. Populated by the buildium-payee-backfill
-- edge function ("partymap" mode).
create table if not exists accounting.buildium_party_map (
company_id uuid not null,
external_id text not null,
party_name text,
vendor_id uuid,
customer_id uuid,
party_type text,
updated_at timestamptz not null default now(),
primary key (company_id, external_id)
);
comment on table accounting.buildium_party_map is
'Staging: Buildium GL transaction id -> resolved local vendor/customer + name, used to backfill the materialized bank register (accounting.transactions) for imported companies.';