[object Object]

← back to Filemaker Mcp

Harden client dedup + add read-only single-order test

96da4967f65ae580612f09be7d2980df5743a6d4 · 2026-07-16 09:35:30 -0700 · Steve Abrams

- findDuplicates now only swallows FileMaker 401 (no-records) as an empty
  result; any other find error is surfaced (per-order handler logs it) instead
  of silently returning no-match, which would double-enter the client
- scripts/test-one.mjs: read-only (commit:false) single-order test proving the
  Company/Name split and live dedup without writing to FileMaker

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files touched

Diff

commit 96da4967f65ae580612f09be7d2980df5743a6d4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 16 09:35:30 2026 -0700

    Harden client dedup + add read-only single-order test
    
    - findDuplicates now only swallows FileMaker 401 (no-records) as an empty
      result; any other find error is surfaced (per-order handler logs it) instead
      of silently returning no-match, which would double-enter the client
    - scripts/test-one.mjs: read-only (commit:false) single-order test proving the
      Company/Name split and live dedup without writing to FileMaker
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
 lib/sync-core.js     |  8 +++++++-
 scripts/test-one.mjs | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/lib/sync-core.js b/lib/sync-core.js
index 95ff20a..361a4d3 100644
--- a/lib/sync-core.js
+++ b/lib/sync-core.js
@@ -65,7 +65,13 @@ export async function findDuplicates({ fields }) {
     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 */ }
+      catch (e) {
+        // 401 = "no records match" (a genuine empty result) → keep looking.
+        // ANY other error means the dedup could not run reliably; surface it
+        // rather than silently returning no-match (which would double-enter).
+        if (String(e.fmCode) === '401') continue;
+        throw new Error(`dedup find failed (${Object.keys(q)[0]}): ${e.message}`);
+      }
       const why = Object.keys(q)[0];
       for (const r of recs) {
         // confirm phone match by normalized last-10 to avoid wildcard false positives
diff --git a/scripts/test-one.mjs b/scripts/test-one.mjs
new file mode 100644
index 0000000..5cd3ae4
--- /dev/null
+++ b/scripts/test-one.mjs
@@ -0,0 +1,38 @@
+#!/usr/bin/env node
+// Read-only single-order test of the client-sync fix (commit:false → no writes).
+// Fetches the most recent Shopify order (or #<arg>), shows the mapped Company/Name
+// field split, then runs the live FileMaker dedup and prints what it WOULD do.
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
+import { mapOrder, syncOrder } from '../lib/sync-core.js';
+
+const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
+for (const l of readFileSync(join(ROOT, '.env'), 'utf8').split('\n')) {
+  const m = l.match(/^([A-Z0-9_]+)=(.*)$/); if (m && !process.env[m[1]]) process.env[m[1]] = m[2];
+}
+const STORE = process.env.FM_SHOPIFY_STORE || process.env.SHOPIFY_STORE;
+const TOKEN = process.env.SHOPIFY_ORDERS_TOKEN;
+
+const arg = process.argv[2];
+const url = arg
+  ? `https://${STORE}/admin/api/2024-10/orders.json?status=any&name=${encodeURIComponent('#' + arg)}`
+  : `https://${STORE}/admin/api/2024-10/orders.json?status=any&limit=1`;
+const r = await fetch(url, { headers: { 'X-Shopify-Access-Token': TOKEN } });
+if (!r.ok) throw new Error(`Shopify fetch failed: ${r.status}`);
+const order = (await r.json()).orders?.[0];
+if (!order) { console.log('no order found'); process.exit(0); }
+
+const m = mapOrder(order);
+console.log(`\nOrder #${order.order_number}`);
+console.log('  shipping.company :', JSON.stringify(order.shipping_address?.company || ''));
+console.log('  customer name    :', JSON.stringify(((order.customer?.first_name||'')+' '+(order.customer?.last_name||'')).trim()));
+console.log('  → FM Company     :', JSON.stringify(m.fields['Company']), '(should be the BUSINESS, or the person if no business)');
+console.log('  → FM Name        :', JSON.stringify(m.fields['Name']), '(should be the CONTACT person)');
+
+const res = await syncOrder(order, { commit: false }); // read-only: dedup runs, no writes
+console.log('\nDedup result (read-only):');
+console.log('  client action :', res.client);          // would-create | existing | updated
+console.log('  matched on    :', res.matchedOn || '(none — no existing client)');
+console.log('  account #     :', res.accountNumber || '(n/a)');
+console.log('\nNo FileMaker records were created or modified (commit:false).');

← de6eb81 Fix client double-entry + Company/Name field swap in Shopify  ·  back to Filemaker Mcp  ·  auto-save: 2026-07-16T09:42:29 (1 files) — scripts/audit-cli 8e0d01f →