From cc5f70bc5be5e9989ed31051b5c81fd87c1e2112 Mon Sep 17 00:00:00 2001 From: renee-png Date: Fri, 12 Jun 2026 18:46:24 -0400 Subject: [PATCH] =?UTF-8?q?Bills=20import:=20A/P=20cutover=20guard=20?= =?UTF-8?q?=E2=80=94=20only=20post=20JEs=20dated=20after=20the=20GL=20wate?= =?UTF-8?q?rmark?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Anything on/before buildium_gl.last_synced_date is already in the books as buildium_gl entries; ap_cutover_date freezes that boundary so direct buildium_bill/billpay postings never double-count history. Co-Authored-By: Claude Opus 4.8 --- supabase/functions/buildium-sync/index.ts | 36 ++++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/supabase/functions/buildium-sync/index.ts b/supabase/functions/buildium-sync/index.ts index 9167de8..e2f88f7 100644 --- a/supabase/functions/buildium-sync/index.ts +++ b/supabase/functions/buildium-sync/index.ts @@ -2207,11 +2207,21 @@ Deno.serve(async (req) => { return m; } - const companyByAssoc = new Map(); + const companyByAssoc = new Map(); async function getCompanyForAssoc(assocLocalId: string) { if (companyByAssoc.has(assocLocalId)) return companyByAssoc.get(assocLocalId)!; - const { data } = await acct.from("companies").select("id, gl_auto_post").eq("association_id", assocLocalId).maybeSingle(); - const out = data ? { id: data.id as string, gl_auto_post: data.gl_auto_post !== false } : null; + const { data } = await acct.from("companies").select("id, gl_auto_post, acmacc_sync_config").eq("association_id", assocLocalId).maybeSingle(); + // Cutover: anything dated on/before the GL pull's watermark is already + // in the books as buildium_gl journal entries — the direct import must + // only post A/P dated AFTER it, or we'd double count the history. + const cfg = (data?.acmacc_sync_config ?? {}) as Record; + const out = data + ? { + id: data.id as string, + gl_auto_post: data.gl_auto_post !== false, + apCutover: (cfg?.buildium_gl?.ap_cutover_date ?? cfg?.buildium_gl?.last_synced_date ?? null) as string | null, + } + : null; companyByAssoc.set(assocLocalId, out); return out; } @@ -2533,7 +2543,7 @@ Deno.serve(async (req) => { || [buildiumVendor?.FirstName, buildiumVendor?.LastName].filter(Boolean).join(" ").trim() || "Vendor"; const billDesc = `${vendorDisplayName}${invoiceNumber ? ` Inv # ${invoiceNumber}` : ""}`; - if (company && !company.gl_auto_post && billLineItems.length > 0) { + if (company && !company.gl_auto_post && billLineItems.length > 0 && (!company.apCutover || billDate > company.apCutover)) { const ap = await getApAccount(company.id); if (ap) { const ok = await postDirectJe( @@ -2562,6 +2572,8 @@ Deno.serve(async (req) => { const pAmount = Array.isArray(p.Lines) ? p.Lines.reduce((s: number, l: any) => s + Number(l?.Amount || 0), 0) : 0; if (!pAmount) continue; const pDate = String(p.EntryDate || billDate).slice(0, 10); + // Payments on/before the GL watermark already came in via buildium_gl + if (company.apCutover && pDate <= company.apCutover) continue; const checkNo = p.CheckNumber ? String(p.CheckNumber) : null; const acctBill = await getAcctBill(finalBillId, company.id); await postPaymentSide(assocLocalId, company, pid, pDate, billDesc, checkNo, p.BankAccountId ?? null, pAmount, acctBill?.id ?? null, acctBill?.vendor_id ?? null); @@ -2689,7 +2701,7 @@ Deno.serve(async (req) => { } const company = await getCompanyForAssoc(assocLocalId); - if (company && !company.gl_auto_post && pubBillId) { + if (company && !company.gl_auto_post && pubBillId && (!company.apCutover || ckDate > company.apCutover)) { const ap = await getApAccount(company.id); if (ap) { const ok = await postDirectJe(company.id, "buildium_bill", externalKey, ckDate, desc, checkNo, [ @@ -2729,9 +2741,19 @@ Deno.serve(async (req) => { for (const companyId of directPostedCompanies) { const { data: comp } = await acct.from("companies").select("acmacc_sync_config").eq("id", companyId).maybeSingle(); const cfg = (comp?.acmacc_sync_config ?? {}) as Record; - if (cfg?.buildium_gl?.exclude_ap) continue; + if (cfg?.buildium_gl?.exclude_ap && cfg?.buildium_gl?.ap_cutover_date) continue; await acct.from("companies").update({ - acmacc_sync_config: { ...cfg, buildium_gl: { ...(cfg.buildium_gl ?? {}), exclude_ap: true } }, + acmacc_sync_config: { + ...cfg, + buildium_gl: { + ...(cfg.buildium_gl ?? {}), + exclude_ap: true, + // Freeze the cutover so the boundary never moves as the GL + // watermark advances: A/P <= cutover lives in buildium_gl + // entries, A/P > cutover comes from the direct import. + ap_cutover_date: cfg?.buildium_gl?.ap_cutover_date ?? cfg?.buildium_gl?.last_synced_date ?? "1900-01-01", + }, + }, }).eq("id", companyId); }