[object Object]

← back to Filemaker Mcp

Fix invoice dup-SKU + wrong-VID at the source (lib/wallpaper.js)

47d22bccdfedcd1d3b4eeb63c62e9dc6c6aab81a · 2026-07-30 07:52:13 -0700 · Steve

lib/wallpaper.js was the single source of both reported invoice bugs.

WRONG VID — vid was ALWAYS the "most common vid among 25 same-Series
records" (vidForSeries), cached process-wide. That pushed a series-level
guess — or a blank, when the find errored — onto invoice lines instead of
the SKU's real vendor code. resolveWallpaperSource now prefers the existing
WALLPAPER master's OWN stored vid, and only falls back to a hardened
series-modal (samples 200, never caches a blank or an error-derived result)
for a genuinely-new SKU.

DUPLICATE MASTERS — ensureWallpaper's existence check (1) collapsed EVERY
FileMaker error into "not found" (auth blip/timeout -> duplicate create) —
the same bug already fixed in sync-core.js's client dedup but missed here;
and (2) only matched the current-prefix `combo sku`, so a line renamed
Cork<->DWGL<->DWLG (combo sku is a Series&JS-Pattern CALC) hid the existing
master and minted a duplicate. New findExistingMaster is alias-aware (checks
combo sku + comboskuwithdash across renamed prefixes) and error-honest (only
fmCode 401/102/105/106 = try-next/not-found; any real error means existence
UNVERIFIED and we refuse to create).

Safe-local: changes only what future runs compute/write; no backfill of
already-corrupted masters/VIDs (gated, separate). Read-only smoke test:
DWKK120156 -> vid KRA from its own master (was a series guess);
DWSW5003662 (no master) -> hardened series fallback, verified-absent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 47d22bccdfedcd1d3b4eeb63c62e9dc6c6aab81a
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jul 30 07:52:13 2026 -0700

    Fix invoice dup-SKU + wrong-VID at the source (lib/wallpaper.js)
    
    lib/wallpaper.js was the single source of both reported invoice bugs.
    
    WRONG VID — vid was ALWAYS the "most common vid among 25 same-Series
    records" (vidForSeries), cached process-wide. That pushed a series-level
    guess — or a blank, when the find errored — onto invoice lines instead of
    the SKU's real vendor code. resolveWallpaperSource now prefers the existing
    WALLPAPER master's OWN stored vid, and only falls back to a hardened
    series-modal (samples 200, never caches a blank or an error-derived result)
    for a genuinely-new SKU.
    
    DUPLICATE MASTERS — ensureWallpaper's existence check (1) collapsed EVERY
    FileMaker error into "not found" (auth blip/timeout -> duplicate create) —
    the same bug already fixed in sync-core.js's client dedup but missed here;
    and (2) only matched the current-prefix `combo sku`, so a line renamed
    Cork<->DWGL<->DWLG (combo sku is a Series&JS-Pattern CALC) hid the existing
    master and minted a duplicate. New findExistingMaster is alias-aware (checks
    combo sku + comboskuwithdash across renamed prefixes) and error-honest (only
    fmCode 401/102/105/106 = try-next/not-found; any real error means existence
    UNVERIFIED and we refuse to create).
    
    Safe-local: changes only what future runs compute/write; no backfill of
    already-corrupted masters/VIDs (gated, separate). Read-only smoke test:
    DWKK120156 -> vid KRA from its own master (was a series guess);
    DWSW5003662 (no master) -> hardened series fallback, verified-absent.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 lib/wallpaper.js | 68 +++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 60 insertions(+), 8 deletions(-)

diff --git a/lib/wallpaper.js b/lib/wallpaper.js
index ef2422c..7e20f71 100644
--- a/lib/wallpaper.js
+++ b/lib/wallpaper.js
@@ -26,6 +26,41 @@ function parseCombo(combo) {
   return m ? { prefix: m[1].toUpperCase(), num: m[2], dashSku: `${m[1].toUpperCase()}-${m[2]}` } : null;
 }
 
+// Series that are the SAME physical line under different DW prefixes (renamed over
+// time — e.g. Greenland cork bounced Cork<->DWGL<->DWLG). `combo sku` = Series & JS
+// Pattern is a FileMaker CALC, so a renamed master hides from a current-prefix "=="
+// lookup and a duplicate master gets minted. Check these aliases before creating.
+const SERIES_ALIASES = { CORK: ['DWGL', 'DWLG'], DWGL: ['CORK', 'DWLG'], DWLG: ['CORK', 'DWGL'] };
+// FileMaker codes that mean "this query found nothing / doesn't apply" (try the next
+// query) — as opposed to a REAL error (auth/timeout/lock) that means existence is
+// UNVERIFIABLE and we must NOT create. Swallowing a real error as "not found" is what
+// minted duplicates. 401=no match, 102=field not on layout, 105=layout, 106=table.
+const FM_SKIP_CODES = new Set(['401', '102', '105', '106']);
+
+// Alias- and error-aware existence check for a WALLPAPER master.
+// Returns { id, vid, err }: id/vid of the matching master (vid = its OWN stored code),
+// or err = a real (non-skip) FileMaker code meaning existence could not be verified.
+async function findExistingMaster(combo, p) {
+  const prefixes = [p.prefix, ...(SERIES_ALIASES[p.prefix] || [])];
+  const queries = [];
+  for (const pre of prefixes) {
+    queries.push({ 'combo sku': '==' + pre + p.num });
+    queries.push({ comboskuwithdash: '==' + pre + '-' + p.num });
+  }
+  for (const q of queries) {
+    try {
+      const r = await fm.findRecords('WALLPAPER', FULL, q, { limit: 1 });
+      const rec = r.records?.[0];
+      if (rec) return { id: rec.recordId, vid: (rec.fieldData.vid || '').trim(), err: null };
+    } catch (e) {
+      const code = String(e.fmCode || '');
+      if (FM_SKIP_CODES.has(code)) continue;              // no-match / field not on layout -> next query
+      return { id: null, vid: '', err: code || e.message }; // real error -> UNVERIFIABLE
+    }
+  }
+  return { id: null, vid: '', err: null };
+}
+
 // shopify_products SELECT expression (metafields-first) — reused by both lookups.
 // Metafields come in TWO shapes: nested {type,value} objects AND flat scalar strings
 // (e.g. Designtex rows store "manufacturer_sku":"8405252" directly). Read both:
@@ -81,14 +116,22 @@ function sourceFor(dashSku) {
   return c ? { ...c, src: 'connie_our_catalog' } : null;
 }
 
-// vendor vid code = the most common vid among existing WALLPAPER records of this Series
+// LAST-RESORT vid guess for a genuinely-new SKU: the most common vid among existing
+// WALLPAPER records of this Series. Only reached when the SKU has no master of its own
+// to read a real vid from (see resolveWallpaperSource). Hardened: sample broadly,
+// NEVER cache a blank, and NEVER cache/return a series result derived from a failed
+// find — a real error must not freeze a wrong/blank vid for the whole run.
 const vidCache = {};
 async function vidForSeries(series) {
   if (series in vidCache) return vidCache[series];
-  const r = await fm.findRecords('WALLPAPER', FULL, [{ Series: series, vid: '*' }], { limit: 25 }).catch(() => ({ records: [] }));
+  let recs = [];
+  try { recs = (await fm.findRecords('WALLPAPER', FULL, [{ Series: series, vid: '*' }], { limit: 200 })).records || []; }
+  catch (e) { if (String(e.fmCode) !== '401') return ''; } // real error -> don't cache, allow a later retry
   const counts = {};
-  for (const rec of r.records) { const v = (rec.fieldData.vid || '').trim(); if (v) counts[v] = (counts[v] || 0) + 1; }
-  return (vidCache[series] = Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0] || '');
+  for (const rec of recs) { const v = (rec.fieldData.vid || '').trim(); if (v) counts[v] = (counts[v] || 0) + 1; }
+  const best = Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0] || '';
+  if (best) vidCache[series] = best; // cache only a real, non-blank result
+  return best;
 }
 
 // READ-ONLY resolve — what WOULD be written for a combo sku, with NO FileMaker writes.
@@ -99,10 +142,15 @@ export async function resolveWallpaperSource(combo) {
   if (!p) return { ok: false, flagged: combo, reason: 'unparseable sku' };
   const s = sourceFor(p.dashSku);
   if (!s || !s.mfr) return { ok: false, flagged: combo, reason: `no mfr number (${s ? s.src : 'not in dw_unified'})` };
-  const vid = await vidForSeries(p.prefix);
+  // vid is the SKU's OWN vendor code. Prefer the existing WALLPAPER master's stored vid
+  // (authoritative, per-SKU); fall back to the series-modal guess ONLY for a genuinely
+  // -new SKU. Previously vid was ALWAYS the series guess — which pushed the wrong (or a
+  // blank) VID onto invoices for products that already had a correct code on file.
+  const ex = await findExistingMaster(combo, p);
+  const vid = ex.vid || await vidForSeries(p.prefix);
   const name = s.pattern || '', color = s.color || '', width = widthClean(s.width);
   const jpg = [name, color].filter(Boolean).join(' - ');
-  return { ok: true, mfr: s.mfr, vid, name, color, width, supplier: s.supplier || '', jpg, src: s.src, _p: p };
+  return { ok: true, mfr: s.mfr, vid, name, color, width, supplier: s.supplier || '', jpg, src: s.src, _p: p, _existing: ex };
 }
 
 // Ensure/complete the WALLPAPER record for a combo sku (DW SKU, no dashes, no -Sample).
@@ -112,8 +160,12 @@ export async function ensureWallpaper(combo) {
   if (!r.ok) return r;
   const p = r._p, s = { mfr: r.mfr, supplier: r.supplier };
   const vid = r.vid, name = r.name, color = r.color, width = r.width, jpg = r.jpg;
-  const ex = await fm.findRecords('WALLPAPER', FULL, { 'combo sku': '==' + combo }, { limit: 1 }).catch(() => ({ records: [] }));
-  let id = ex.records[0]?.recordId;
+  // Reuse the alias-/error-aware lookup already done in resolveWallpaperSource.
+  // NEVER create when existence could not be verified (auth blip / timeout / lock):
+  // treating a real error as "not found" is exactly what minted duplicate masters.
+  const ex = r._existing || { id: null, err: null };
+  if (!ex.id && ex.err) return { ok: false, flagged: combo, reason: `existence unverified (${ex.err}) — not creating (would risk a duplicate)` };
+  let id = ex.id;
   if (!id) {
     const res = await fm.createRecord('WALLPAPER', ENTRY, { 'Mfr Pattern': s.mfr, 'JS Pattern': p.num, Series: p.prefix, Supplier: s.supplier || '', Width: width }, { dryRun: false }).catch((e) => ({ err: e.fmCode }));
     if (res.err) return { ok: false, flagged: combo, reason: `create failed ${res.err}` };

← 1d1f33b auto-save: 2026-07-29T20:11:57 (1 files) — package-lock.json  ·  back to Filemaker Mcp  ·  chore: v0.2.5 (session close — invoice SKU/VID audit + fix) 92722e0 →