diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts
index d70b474..fe925f3 100644
--- a/src/integrations/supabase/types.ts
+++ b/src/integrations/supabase/types.ts
@@ -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
diff --git a/src/pages/settings/BuildiumSettingsPage.tsx b/src/pages/settings/BuildiumSettingsPage.tsx
index aa31e75..ac14185 100644
--- a/src/pages/settings/BuildiumSettingsPage.tsx
+++ b/src/pages/settings/BuildiumSettingsPage.tsx
@@ -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() {
)}
)}
+ {results.bills && (
+
+
{results.bills.created + (results.bills.checks_created ?? 0)}
+
Bills & Checks Imported
+
+ {results.bills.updated} updated · {results.bills.payments_posted ?? 0} payment(s) posted
+
+ {((results.bills.held_unmapped ?? 0) > 0 || (results.bills.payments_held ?? 0) > 0 || (results.bills.checks_held ?? 0) > 0) && (
+
+ {(results.bills.held_unmapped ?? 0) + (results.bills.checks_held ?? 0)} held · {results.bills.payments_held ?? 0} payment(s) held
+
+ )}
+
+ )}
{Array.isArray(results.unmapped) && results.unmapped.length > 0 && (
@@ -702,7 +718,7 @@ export default function BuildiumSettingsPage() {
))}
- Map these in the GL Account Map tab above, then run Pull Charges again.
+ Map these in the GL Account Map tab above, then re-run the pull.
{(results.charges_missing_gl_info ?? 0) > 0 && (
{results.charges_missing_gl_info} charge(s) had no GL account info from Buildium and were skipped.
diff --git a/supabase/functions/buildium-gl-sync/index.ts b/supabase/functions/buildium-gl-sync/index.ts
index c4ec18f..14d16b6 100644
--- a/supabase/functions/buildium-gl-sync/index.ts
+++ b/supabase/functions/buildium-gl-sync/index.ts
@@ -273,6 +273,23 @@ Deno.serve(async (req) => {
}
companyResult.pulled = txById.size;
+ // Direct A/P import (buildium-sync "bills") posts Bill / Bill Payment /
+ // Check journal entries itself for this company — skip those
+ // transaction types here so they aren't double counted.
+ if (cfg?.buildium_gl?.exclude_ap) {
+ // NOTE: owner "Refund" transactions stay in the GL pull — they are
+ // not returned by the /checks endpoint the direct import reads.
+ const AP_TYPES = new Set(["bill", "bill payment", "billpayment", "check", "bill credit", "vendor credit", "applied vendor credit"]);
+ let excludedAp = 0;
+ for (const [txId, tx] of [...txById.entries()]) {
+ if (AP_TYPES.has(String(tx.transactionType || "").toLowerCase())) {
+ txById.delete(txId);
+ excludedAp++;
+ }
+ }
+ companyResult.excluded_ap = excludedAp;
+ }
+
// ---- Already-imported transaction ids for this company ----
const existingIds = new Set
();
for (let offset = 0; ; offset += 1000) {
@@ -432,6 +449,7 @@ Deno.serve(async (req) => {
const nextCfg = {
...cfg,
buildium_gl: {
+ ...(cfg.buildium_gl ?? {}),
last_synced_date: until,
last_run_at: new Date().toISOString(),
last_result: {
diff --git a/supabase/functions/buildium-sync/index.ts b/supabase/functions/buildium-sync/index.ts
index f91b833..9167de8 100644
--- a/supabase/functions/buildium-sync/index.ts
+++ b/supabase/functions/buildium-sync/index.ts
@@ -2186,6 +2186,171 @@ Deno.serve(async (req) => {
const buildiumVendorById = new Map();
for (const bv of buildiumVendors) buildiumVendorById.set(String(bv.Id), bv);
+ // ---- Direct A/P import support ------------------------------------
+ // Bills/payments/checks resolve their GL accounts STRICTLY through
+ // buildium_gl_account_links and, for import-mode companies
+ // (gl_auto_post=false), post their own journal entries — so A/P activity
+ // no longer depends on the nightly GL pull.
+ const acct = (supabase as any).schema("accounting");
+
+ const apLinksByAssoc = new Map>();
+ async function getApLinks(assocLocalId: string): Promise