Direct Buildium A/P import: bills, payments, one-off checks via GL Account Map

- public.bills.line_items + line-aware accounting mirror (one bill_item per
  Buildium line with its mapped platform account)
- buildium-sync bills: strict per-line resolution through
  buildium_gl_account_links (unmapped -> bill held + flagged); pulls
  /bills/{id}/payments (check#, bank, date) and /bankaccounts/{id}/checks
  (one-off checks become paid bill+payment pairs)
- import-mode companies get direct JEs (buildium_bill Dr expense/Cr AP,
  buildium_billpay Dr AP/Cr mapped bank) + cleared register rows; sets
  buildium_gl.exclude_ap so the nightly GL pull skips Bill/Bill Payment/Check
- buildium-gl-sync: exclude_ap transaction-type filter; preserve buildium_gl
  config keys when advancing the watermark
- Settings: Pull Bills & Expenses card with held/unmapped reporting

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 18:43:58 -04:00
parent 4e77098f88
commit 25064d8418
4 changed files with 376 additions and 30 deletions
+3
View File
@@ -2058,6 +2058,7 @@ export type Database = {
expense_account_id: string | null
id: string
invoice_number: string | null
line_items: Json | null
notes: string | null
paid_date: string | null
payment_method: string | null
@@ -2085,6 +2086,7 @@ export type Database = {
expense_account_id?: string | null
id?: string
invoice_number?: string | null
line_items?: Json | null
notes?: string | null
paid_date?: string | null
payment_method?: string | null
@@ -2112,6 +2114,7 @@ export type Database = {
expense_account_id?: string | null
id?: string
invoice_number?: string | null
line_items?: Json | null
notes?: string | null
paid_date?: string | null
payment_method?: string | null
+20 -4
View File
@@ -11,7 +11,7 @@ import { useToast } from "@/hooks/use-toast";
import { invokeEdgeFunction } from "@/lib/edgeFunctionUtils";
import { supabase } from "@/integrations/supabase/client";
import {
CheckCircle2, XCircle, Loader2, RefreshCw, Building2, Users, BookOpen, DollarSign, Home, Trash2, AlertTriangle, Upload, CalendarIcon, Clock, RotateCcw, Eye,
CheckCircle2, XCircle, Loader2, RefreshCw, Building2, Users, BookOpen, DollarSign, Home, Trash2, AlertTriangle, Upload, CalendarIcon, Clock, RotateCcw, Eye, FileText,
} from "lucide-react";
import { cn } from "@/lib/utils";
import BuildiumGLMappingCard from "@/components/settings/BuildiumGLMappingCard";
@@ -23,7 +23,7 @@ import {
AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
} from "@/components/ui/alert-dialog";
type SyncType = "associations" | "units" | "owners" | "financials" | "ledger" | "charges" | "push_charges" | "push_payments" | "push_all" | "reset_ledgers" | "all";
type SyncType = "associations" | "units" | "owners" | "financials" | "ledger" | "charges" | "bills" | "push_charges" | "push_payments" | "push_all" | "reset_ledgers" | "all";
type DeleteType = "charges" | "payments" | "owners" | "units" | "financials" | "all";
interface SyncResults {
@@ -32,6 +32,7 @@ interface SyncResults {
owners?: { fetched: number; imported: number; skipped: number };
financials?: { fetched: number; upserted: number };
ledger?: { fetched: number; imported: number; skipped: number; updated?: number };
bills?: { fetched: number; created: number; updated: number; skipped: number; held_unmapped?: number; payments_posted?: number; payments_held?: number; checks_created?: number; checks_held?: number; vendors_created?: number; vendors_linked?: number };
push?: { pushed: number; skipped: number; errors: number; skipSamples?: any[]; errorSamples?: any[]; dryRun?: boolean; dryRunSamples?: any[] };
reset?: { deleted: number };
unmapped?: { buildium_gl_id: string; buildium_name: string | null; buildium_number: string | null; count: number }[];
@@ -170,7 +171,7 @@ export default function BuildiumSettingsPage() {
body: {
syncType: type,
selectedAssociationIds: selectedIds,
...(type === "ledger" || type === "charges" || type === "all" ? {
...(type === "ledger" || type === "charges" || type === "bills" || type === "all" ? {
dateFrom: dateFrom ? format(dateFrom, "yyyy-MM-dd") : undefined,
dateTo: dateTo ? format(dateTo, "yyyy-MM-dd") : undefined,
} : {}),
@@ -291,6 +292,7 @@ export default function BuildiumSettingsPage() {
{ type: "financials", title: "GL Accounts", desc: "Import chart of accounts and GL structure", icon: BookOpen, deleteType: "financials" },
{ type: "ledger", title: "Pull Payments", desc: "Pull payments from Buildium into unit ledgers (charges are not pulled)", icon: DollarSign, deleteType: "charges" },
{ type: "charges", title: "Pull Charges", desc: "Pull charges from Buildium into unit ledgers (payments are not pulled). Requires the GL Account Map — charges on unmapped accounts are held.", icon: BookOpen },
{ type: "bills", title: "Pull Bills & Expenses", desc: "Import bills, their payments, and one-off checks directly into Accounting via the GL Account Map — no GL pull needed for A/P. Unmapped accounts hold the bill.", icon: FileText },
];
const pushCards: { type: SyncType; title: string; desc: string; icon: any }[] = [
@@ -688,6 +690,20 @@ export default function BuildiumSettingsPage() {
)}
</div>
)}
{results.bills && (
<div className="bg-background p-4 rounded-lg border text-center">
<div className="text-2xl font-bold text-emerald-600">{results.bills.created + (results.bills.checks_created ?? 0)}</div>
<div className="text-xs text-muted-foreground mt-1">Bills & Checks Imported</div>
<div className="text-[10px] text-muted-foreground">
{results.bills.updated} updated · {results.bills.payments_posted ?? 0} payment(s) posted
</div>
{((results.bills.held_unmapped ?? 0) > 0 || (results.bills.payments_held ?? 0) > 0 || (results.bills.checks_held ?? 0) > 0) && (
<div className="text-[10px] text-amber-500 mt-1">
{(results.bills.held_unmapped ?? 0) + (results.bills.checks_held ?? 0)} held · {results.bills.payments_held ?? 0} payment(s) held
</div>
)}
</div>
)}
{Array.isArray(results.unmapped) && results.unmapped.length > 0 && (
<div className="bg-background p-4 rounded-lg border border-amber-200 col-span-full">
<div className="text-xs font-semibold text-amber-600 mb-1 flex items-center gap-1">
@@ -702,7 +718,7 @@ export default function BuildiumSettingsPage() {
))}
</ul>
<p className="text-[11px] text-muted-foreground mt-2">
Map these in the <strong>GL Account Map</strong> tab above, then run Pull Charges again.
Map these in the <strong>GL Account Map</strong> tab above, then re-run the pull.
</p>
{(results.charges_missing_gl_info ?? 0) > 0 && (
<p className="text-[11px] text-amber-600 mt-1">{results.charges_missing_gl_info} charge(s) had no GL account info from Buildium and were skipped.</p>