diff --git a/supabase/functions/buildium-payee-backfill/index.ts b/supabase/functions/buildium-payee-backfill/index.ts index 65fafab..74ea7a7 100644 --- a/supabase/functions/buildium-payee-backfill/index.ts +++ b/supabase/functions/buildium-payee-backfill/index.ts @@ -310,9 +310,13 @@ Deno.serve(async (req) => { const customerByUnitNo = new Map(); 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); + // Local income/expense accounts by code → {id,name}, for the category. + const acctByCode = new Map(); + for (const a of (await supabase.schema("accounting").from("accounts").select("id,code,name,type").eq("company_id", companyId)).data ?? []) + if (a.code && (a.type === "income" || a.type === "expense")) acctByCode.set(String(a.code).trim(), { id: a.id, name: a.name }); const rows: any[] = []; - let vendorHits = 0, customerHits = 0; + let vendorHits = 0, customerHits = 0, categoryHits = 0; for (const tx of txns) { const id = String(tx?.Id ?? ""); if (!id) continue; @@ -323,14 +327,30 @@ Deno.serve(async (req) => { 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 + + // Category: the dominant income/expense GL account across the journal + // lines (Buildium expands a check/bill/payment to its category lines). + const byCode = new Map(); + for (const ln of (tx?.Journal?.Lines ?? [])) { + const t = String(ln?.GLAccount?.Type ?? ""); + if (t !== "Expense" && t !== "Income") continue; + const code = String(ln?.GLAccount?.AccountNumber ?? "").trim(); + if (!code) continue; + byCode.set(code, (byCode.get(code) ?? 0) + Math.abs(Number(ln?.Amount) || 0)); } + let category_name: string | null = null; + let coa_account_id: string | null = null; + if (byCode.size) { + const topCode = [...byCode.entries()].sort((a, b) => b[1] - a[1])[0][0]; + const acct = acctByCode.get(topCode); + if (acct) { category_name = acct.name; coa_account_id = acct.id; } + } + 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 }); + if (coa_account_id) categoryHits++; + if (name || vendor_id || customer_id || coa_account_id) + rows.push({ company_id: companyId, external_id: id, party_name: name, vendor_id, customer_id, party_type: payeeType || null, category_name, coa_account_id }); } // Upsert in batches. let staged = 0; @@ -340,7 +360,7 @@ Deno.serve(async (req) => { if (error) throw error; staged += batch.length; } - return json({ mode, company: company.name, window: [dateFrom, dateTo], txns: txns.length, staged, vendorHits, customerHits }); + return json({ mode, company: company.name, window: [dateFrom, dateTo], txns: txns.length, staged, vendorHits, customerHits, categoryHits }); } // Load this company's imported entries and compute the new description. diff --git a/supabase/migrations/20260615190000_buildium_party_map_category.sql b/supabase/migrations/20260615190000_buildium_party_map_category.sql new file mode 100644 index 0000000..6e242c0 --- /dev/null +++ b/supabase/migrations/20260615190000_buildium_party_map_category.sql @@ -0,0 +1,7 @@ +-- Extend the payee/payor staging table to also carry the resolved income/expense +-- category (the dominant Journal line of each Buildium GL transaction) and its +-- local account, used to backfill accounting.transactions.category on the +-- materialized bank register of imported-GL companies. +alter table accounting.buildium_party_map + add column if not exists category_name text, + add column if not exists coa_account_id uuid;