← back to Filemaker Mcp
fix: idempotency guard — skip invoice creation when FileMaker already has one for this Cust PO (order#)
65eb72c98eb59d80a6f14d66ec97f2b04448479e · 2026-07-14 10:28:35 -0700 · Steve Abrams
Prevents the systemic double-billing (e.g. #32609). Dedup was local-only (state.processed[]); any re-process minted a duplicate invoice. Now checks FileMaker truth by Cust PO before creating.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
Diff
commit 65eb72c98eb59d80a6f14d66ec97f2b04448479e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jul 14 10:28:35 2026 -0700
fix: idempotency guard — skip invoice creation when FileMaker already has one for this Cust PO (order#)
Prevents the systemic double-billing (e.g. #32609). Dedup was local-only (state.processed[]); any re-process minted a duplicate invoice. Now checks FileMaker truth by Cust PO before creating.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
lib/invoice.js | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/lib/invoice.js b/lib/invoice.js
index 0127910..3731a6e 100644
--- a/lib/invoice.js
+++ b/lib/invoice.js
@@ -136,6 +136,28 @@ export async function createInvoiceForOrder(order, accountNumber, { commit = tru
stubRecords: items.slice(1).map((li) => ({ sku: li.sku, qty: li.quantity || 1, dollars: 0 })) };
}
+ // ── Idempotency guard (2026-07-14): never create a second invoice for an
+ // order FileMaker already has one for. Dedup was previously local-only
+ // (state.processed[] in shopify-sync.js), so any re-process — a state reset,
+ // manual keying, a second machine, or a run overlapping the daemon — minted a
+ // duplicate invoice (e.g. #32609 → #101633 + #102735). Check FileMaker truth
+ // by Cust PO (= unique Shopify order number) and return the existing invoice.
+ const existing = await fm
+ .findRecords('invoice', HDR, { 'Cust PO': '==' + order.order_number }, { limit: 10 })
+ .catch(() => ({ records: [] }));
+ const prior = (existing.records || [])
+ .map((r) => r.fieldData || {})
+ .filter((f) => /^\d+$/.test(String(f['Invoice'] || '').trim()))
+ .map((f) => parseInt(f['Invoice'], 10))
+ .sort((a, b) => a - b);
+ if (prior.length) {
+ return {
+ invoiceNumber: String(prior[0]), recordId: null, grandTotal: 0,
+ cust_po: order.order_number, account: accountNumber, invoiceCount: prior.length,
+ invoices: prior.map((n) => ({ invoiceNumber: String(n) })), skipped: 'already-invoiced',
+ };
+ }
+
const sharedHeader = {
'Cust PO': String(order.order_number), 'Account #': String(accountNumber || ''),
'SOLD NAME': name, 'SOLD COMPANY': s.company || '', 'SOLD ADDRESS': s.address1 || '', 'SOLD CITY': s.city || '',
← aa0cddd fix: resolve George basic-auth from live fleet cred (was emp
·
back to Filemaker Mcp
·
Fix client double-entry + Company/Name field swap in Shopify de6eb81 →