Checks: print PDF now uses Check Setup settings (matches the sample)

Bulk "Print Checks" was rendering via a separate generator (utils/checkPdfGenerator
+ check_layouts), so the printed PDF ignored the accounting Check Setup: wrong
return address (generic company vs association name + mailing address), missing
vendor address, and ignored x/y field-position offsets.

Route the print through the same generator as the working sample
(lib/checkPdf.generateCheckPDF) fed by the accounting company (return address),
check_settings (style/offsets/field_positions/signature/MICR gaps), the chosen
bank account (routing/account), and the vendor address as payee. DB side effects
(checks, transactions, bill paid status, check numbering) unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 11:45:30 -04:00
parent a81e4f51ab
commit dd4a088e9d
+55 -19
View File
@@ -4,7 +4,8 @@ import { supabase } from "@/integrations/supabase/client";
import { useToast } from "@/hooks/use-toast"; import { useToast } from "@/hooks/use-toast";
import { useAuth } from "@/contexts/AuthContext"; import { useAuth } from "@/contexts/AuthContext";
import { UserCheck, Plus, Search, Eye, Upload, X, ArrowUpDown, Edit, Trash2, MoreHorizontal, AlertTriangle, Loader2, Bell, Printer, Sparkles, Download } from "lucide-react"; import { UserCheck, Plus, Search, Eye, Upload, X, ArrowUpDown, Edit, Trash2, MoreHorizontal, AlertTriangle, Loader2, Bell, Printer, Sparkles, Download } from "lucide-react";
import { downloadChecksPdf, type CheckData } from "@/utils/checkPdfGenerator"; import { generateCheckPDF, type CheckData } from "@/pages/accounting/lib/checkPdf";
import { accounting } from "@/lib/accountingClient";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -697,13 +698,19 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
try { try {
const { data: userData } = await supabase.auth.getUser(); const { data: userData } = await supabase.auth.getUser();
// Load per-association check layouts (optional) // Load the accounting company (return address = association name + mailing
const { data: layoutsData } = await supabase // address) and the saved Check Setup settings per association, so printed
.from("check_layouts") // checks match the Check Setup sample exactly.
.select("*") const { data: companiesData } = await accounting
.in("association_id", assocIds); .from("companies").select("id, association_id, name, address").in("association_id", assocIds);
const layoutsByAssoc: Record<string, any> = {}; const companyByAssoc: Record<string, any> = {};
(layoutsData || []).forEach((l: any) => { layoutsByAssoc[l.association_id] = l; }); (companiesData || []).forEach((c: any) => { companyByAssoc[c.association_id] = c; });
const companyIds = (companiesData || []).map((c: any) => c.id);
const settingsByCompany: Record<string, any> = {};
if (companyIds.length) {
const { data: settingsData } = await accounting.from("check_settings").select("*").in("company_id", companyIds);
(settingsData || []).forEach((s: any) => { settingsByCompany[s.company_id] = s; });
}
let totalPrinted = 0; let totalPrinted = 0;
let totalReprinted = 0; let totalReprinted = 0;
@@ -717,7 +724,10 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
const billStatusUpdates: Array<{ billId: string; checkId: string; checkNumber: string }> = []; const billStatusUpdates: Array<{ billId: string; checkId: string; checkNumber: string }> = [];
const txInserts: any[] = []; const txInserts: any[] = [];
const today = new Date().toISOString().slice(0, 10); const today = new Date().toISOString().slice(0, 10);
const todayDisplay = new Date(today + "T00:00:00").toLocaleDateString("en-US", { month: "2-digit", day: "2-digit", year: "numeric" });
const assocName = associations.find((a: any) => a.id === assocId)?.name || ""; const assocName = associations.find((a: any) => a.id === assocId)?.name || "";
const company = companyByAssoc[assocId] || null;
const settings: any = company ? (settingsByCompany[company.id] || {}) : {};
// Group bills by payee (vendor) when combining is enabled. // Group bills by payee (vendor) when combining is enabled.
// Otherwise treat each bill as its own group. // Otherwise treat each bill as its own group.
@@ -845,18 +855,27 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
} }
checkDataList.push({ checkDataList.push({
check_number: checkNumber, companyName: company?.name || assocName || "",
check_date: today, companyAddress: company?.address || undefined,
bankName: bank.bank_name || undefined,
bankAddress: settings.bank_address || undefined,
routingNumber: printIncludeMicr ? (bank.routing_number || undefined) : undefined,
accountNumber: printIncludeMicr ? (bank.account_number || undefined) : undefined,
fractionalRouting: settings.fractional_routing || undefined,
checkNumber: Number(checkNumber) || 0,
date: todayDisplay,
payee, payee,
payee_address: payeeAddress, payeeAddress: payeeAddress || undefined,
amount: totalAmount, amount: totalAmount,
memo, memo: memo || undefined,
line_items: lineItems.length > 1 ? lineItems : null, lineItems: lineItems.length > 1
bank_account_name: bank.account_name, ? lineItems.map((li: any) => ({
bank_routing_number: bank.routing_number, description: [li.invoice_number ? `Inv #${li.invoice_number}` : null, li.description].filter(Boolean).join(" — ") || "Payment",
bank_account_number: bank.account_number, amount: Number(li.amount) || 0,
association_name: assocName, }))
layout: (layoutsByAssoc[assocId] as any) || null, : undefined,
printSignature: !!settings.print_signature,
signatureDataUrl: settings.signature_url || undefined,
}); });
} }
@@ -882,7 +901,24 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
} }
const safeName = (assocName || "association").replace(/[^a-z0-9-_]+/gi, "_"); const safeName = (assocName || "association").replace(/[^a-z0-9-_]+/gi, "_");
await downloadChecksPdf(checkDataList, `checks-${safeName}-${today}.pdf`, { includeMicr: printIncludeMicr }); const opts: any = {
style: settings.default_style || "voucher",
position: settings.default_position || "top",
fontSize: settings.font_size || "medium",
offsetX: Number(settings.offset_x ?? 0),
offsetY: Number(settings.offset_y ?? 0),
micrOffsetY: Number(settings.micr_offset_y ?? 0),
micrGap1: Number(settings.micr_gap_1 ?? 1),
micrGap2: Number(settings.micr_gap_2 ?? 1),
fieldPositions: { ...(settings.field_positions || {}), ...(printIncludeMicr ? {} : { micr: { hidden: true } }) },
};
const dataUrl = generateCheckPDF(checkDataList, opts);
const a = document.createElement("a");
a.href = dataUrl;
a.download = `checks-${safeName}-${today}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
} }
const parts: string[] = []; const parts: string[] = [];