← back to Filemaker Mcp
Fix client double-entry + Company/Name field swap in Shopify→FileMaker sync
de6eb8178daa2da1815d38b6233038500601a17f · 2026-07-16 09:24:31 -0700 · Steve Abrams
- Company field now holds the business name, Name holds the contact person
(was reversed: put e.g. 'Denny Wilson' in Company, 'Wilson Construction' in Name)
- Dedup rewritten as priority waterfall: email → company → client name,
searching the correct fields (old code searched Company against the person's
name, silently missing real dupes and creating second records)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
Diff
commit de6eb8178daa2da1815d38b6233038500601a17f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 16 09:24:31 2026 -0700
Fix client double-entry + Company/Name field swap in Shopify→FileMaker sync
- Company field now holds the business name, Name holds the contact person
(was reversed: put e.g. 'Denny Wilson' in Company, 'Wilson Construction' in Name)
- Dedup rewritten as priority waterfall: email → company → client name,
searching the correct fields (old code searched Company against the person's
name, silently missing real dupes and creating second records)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
lib/sync-core.js | 74 ++++++++++++++++++++++++++++++++++++--------------------
1 file changed, 48 insertions(+), 26 deletions(-)
diff --git a/lib/sync-core.js b/lib/sync-core.js
index 09ebf75..95ff20a 100644
--- a/lib/sync-core.js
+++ b/lib/sync-core.js
@@ -1,12 +1,11 @@
// Shopify order -> FileMaker Clients sync core.
// Maps a Shopify order's customer to a FileMaker Clients record, but only
-// creates one when NO similar client already exists (deduped by email, phone,
-// OR name — per Steve's rule "always check for similar or same name based on
-// name, email, or phone number").
+// creates one when NO similar client already exists. Dedup is checked in strict
+// priority order — EMAIL first, then COMPANY name, then CLIENT (contact) name —
+// per Steve's rule (2026-07-16). Company = business name, Name = contact person.
import * as fm from '../src/fm-client.js';
const LAYOUT = 'wALLPAPER oPTIONS'; // Clients layout exposing the client header fields
-const norm = (s) => (s || '').toString().trim().toLowerCase().replace(/\s+/g, ' ');
const digits = (s) => (s || '').toString().replace(/\D/g, '');
const last10 = (s) => digits(s).slice(-10);
@@ -18,8 +17,14 @@ export function mapOrder(o) {
orderNumber: o.order_number,
orderId: o.id,
fields: {
- 'Company': fullName || s.company || '',
- 'Name': s.company || '',
+ // FileMaker CLIENT record: Company = the BUSINESS name, Name = the CONTACT
+ // person. Previously these were reversed (fullName went into Company,
+ // company into Name), which put e.g. "Denny Wilson" in Company and
+ // "Wilson Construction" in Name. Fixed 2026-07-16 (Steve flagged).
+ // Fall back to the person's name for Company only when there is no
+ // business name (individual buyer), preserving the old behavior there.
+ 'Company': s.company || fullName || '',
+ 'Name': fullName || '',
'Address': s.address1 || '',
'City': s.city || '',
'State': s.province_code || '',
@@ -31,31 +36,48 @@ export function mapOrder(o) {
}
// Returns an array of matching existing clients (empty = safe to create).
+//
+// Steve's dedup rule (2026-07-16): ALWAYS check in priority order —
+// 1) Email address 2) Company name 3) Client (contact) name
+// We check tier by tier and return the FIRST tier that produces a match, so a
+// client already on file is never entered twice. Phone rides with the email
+// tier as an equally-strong identity signal. Matching the CORRECT fields is
+// essential: the old code searched Company against the person's name (because
+// the field mapping was reversed), which silently missed real dupes and let
+// second records get created.
export async function findDuplicates({ fields }) {
- const email = norm(fields['Email address']);
+ const email = fields['Email address'];
const phone10 = last10(fields['Phone']);
- const name = norm(fields['Company']);
- const queries = [];
- if (email) queries.push({ 'Email address': '==' + fields['Email address'] });
- if (phone10) queries.push({ 'Phone': '*' + phone10 + '*' }); // contains last-10 digits
- if (name) queries.push({ 'Company': '==' + fields['Company'] }); // exact display name
- if (name) queries.push({ 'Name': '==' + fields['Company'] });
+ const company = fields['Company']; // business name
+ const person = fields['Name']; // contact person
- const hits = new Map(); // recordId -> {acct, company, why[]}
- for (const q of queries) {
- let recs = [];
- try { recs = (await fm.findRecords('Clients', LAYOUT, q, { limit: 10 })).records; }
- catch (e) { if (e.fmCode === '401') continue; /* 401 = no records found */ else continue; }
- const why = Object.keys(q)[0];
- for (const r of recs) {
- // confirm phone match by normalized last-10 to avoid wildcard false positives
- if (why === 'Phone' && last10(r.fieldData['Phone']) !== phone10) continue;
- const e = hits.get(r.recordId) || { recordId: r.recordId, acct: r.fieldData['Account Number'], company: r.fieldData['Company'], why: [] };
- if (!e.why.includes(why)) e.why.push(why);
- hits.set(r.recordId, e);
+ const tiers = [
+ { queries: [
+ ...(email ? [{ 'Email address': '==' + email }] : []),
+ ...(phone10 ? [{ 'Phone': '*' + phone10 + '*' }] : []), // contains last-10
+ ] },
+ { queries: company ? [{ 'Company': '==' + company }] : [] }, // exact company
+ { queries: person ? [{ 'Name': '==' + person }] : [] }, // exact contact name
+ ];
+
+ for (const tier of tiers) {
+ const hits = new Map(); // recordId -> {acct, company, why[]}
+ for (const q of tier.queries) {
+ let recs = [];
+ try { recs = (await fm.findRecords('Clients', LAYOUT, q, { limit: 10 })).records; }
+ catch (e) { continue; /* 401 = no records found */ }
+ const why = Object.keys(q)[0];
+ for (const r of recs) {
+ // confirm phone match by normalized last-10 to avoid wildcard false positives
+ if (why === 'Phone' && last10(r.fieldData['Phone']) !== phone10) continue;
+ const e = hits.get(r.recordId) || { recordId: r.recordId, acct: r.fieldData['Account Number'], company: r.fieldData['Company'], why: [] };
+ if (!e.why.includes(why)) e.why.push(why);
+ hits.set(r.recordId, e);
+ }
}
+ if (hits.size) return [...hits.values()]; // first tier with a match wins
}
- return [...hits.values()];
+ return [];
}
// Process one order end-to-end: ensure the client exists (create if new, dedup
← 65eb72c fix: idempotency guard — skip invoice creation when FileMake
·
back to Filemaker Mcp
·
Harden client dedup + add read-only single-order test 96da496 →