← back to Dw Photo Capture
Scan → show WALLPAPER FileMaker details: new /api/wallpaper-details (queries 'Basic List of Fields' by combo sku→Mfr Pattern, coalesces non-empty specs across the mega-table's multiple rows, portal:[] so no stall) + loadWallpaperDetails() renders a spec card (Mfr#/Collection/Width/Repeat/Retail/Net/Cost/Roll/Min/Border/Price-code/Line/SKU) under every scan result. 58k+ SKUs carry rich FM specs; sparse scraped lines show what FM has
c3d842972ed0308a1817a2bc4ff628d12360369e · 2026-07-08 12:42:42 -0700 · Steve Abrams
Files touched
M public/index.htmlM server.js
Diff
commit c3d842972ed0308a1817a2bc4ff628d12360369e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jul 8 12:42:42 2026 -0700
Scan → show WALLPAPER FileMaker details: new /api/wallpaper-details (queries 'Basic List of Fields' by combo sku→Mfr Pattern, coalesces non-empty specs across the mega-table's multiple rows, portal:[] so no stall) + loadWallpaperDetails() renders a spec card (Mfr#/Collection/Width/Repeat/Retail/Net/Cost/Roll/Min/Border/Price-code/Line/SKU) under every scan result. 58k+ SKUs carry rich FM specs; sparse scraped lines show what FM has
---
public/index.html | 26 +++++++++++++++++++++++++-
server.js | 32 ++++++++++++++++++++++++++++++++
2 files changed, 57 insertions(+), 1 deletion(-)
diff --git a/public/index.html b/public/index.html
index bfeab04..ffcea6e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -172,6 +172,12 @@
.inc-sub{font-size:10px;color:#9a917d;margin-top:2px}
.inc-recv{margin-top:5px;width:100%;padding:5px 4px;font-size:11px;font-weight:600;border:1px solid #2bd06a;background:rgba(43,208,106,.12);color:#2bd06a;border-radius:7px;cursor:pointer}
.inc-recv:hover{background:rgba(43,208,106,.22)}.inc-recv:disabled{opacity:.55;cursor:default}
+ .wp-d-card{margin-top:10px;border:1px solid var(--line);border-radius:12px;padding:11px 12px;background:#0f0e0b}
+ .wp-d-hd{font-size:12px;font-weight:600;color:#c8bfa8;margin-bottom:8px}.wp-d-empty{color:#7d7663;font-weight:400}
+ .wp-d-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:5px 14px}
+ .wp-d-row{display:flex;justify-content:space-between;gap:8px;font-size:12px;border-bottom:1px dotted #2a2721;padding-bottom:3px}
+ .wp-d-lbl{color:#8f887a}.wp-d-val{color:#eadfc6;font-weight:600;text-align:right}
+ .wp-d-desc{margin-top:8px;font-size:11px;color:#b3aa93;line-height:1.4}
.fb-thumb{width:100%;aspect-ratio:1/1;border-radius:10px;background:#000 center/cover no-repeat;display:flex;align-items:center;justify-content:center;font-size:30px}
#fbResult{margin-top:12px}
.fb-hit{display:flex;gap:10px;align-items:center;padding:10px;border:1px solid var(--line);border-radius:12px;margin-top:8px}
@@ -1521,11 +1527,29 @@ async function fbIdentify(){
const p=r.primary, conf=(r.confidence||'').replace('-','');
const via=r.source==='qr'?'🔗 QR':r.source==='code'?'📄 label code':'🎨 visual';
$('#fbNote').innerHTML=`Source: ${via}${r.corroborated?' · ✓ front+back agree':''}`;
- $('#fbResult').innerHTML=`<div class="fb-hit">${p.image?`<img src="${p.image}">`:''}<div><b>${p.pattern||p.mfr_sku||p.dw_sku||'—'}</b><span class="fb-conf ${conf}">${r.confidence}</span><br><small>${[p.vendor,p.dw_sku,p.mfr_code].filter(Boolean).join(' · ')}</small></div></div>`+fbVisualHtml(r.visual);
+ $('#fbResult').innerHTML=`<div class="fb-hit">${p.image?`<img src="${p.image}">`:''}<div><b>${p.pattern||p.mfr_sku||p.dw_sku||'—'}</b><span class="fb-conf ${conf}">${r.confidence}</span><br><small>${[p.vendor,p.dw_sku,p.mfr_code].filter(Boolean).join(' · ')}</small></div></div>`+`<div id="fbWpDetails"></div>`+fbVisualHtml(r.visual);
+ loadWallpaperDetails(p); // pull the master product record from the WALLPAPER FileMaker file
if(!_fbLookup){ matchIncoming(p); checkSampleRequest(p.dw_sku||p.mfr_code, p.mfr_code); } // Check-Sample-In: flag the incoming match + print popup; pure Look-Up SKU skips both
}catch(e){ $('#fbNote').textContent='Failed: '+e.message; }
$('#fbGo').disabled=false;
}
+// On any scan, pull the master product record from the WALLPAPER FileMaker file and show its details.
+async function loadWallpaperDetails(p){ const box=$('#fbWpDetails'); if(!box) return;
+ const sku=encodeURIComponent(p.dw_sku||p.internal_sku||''), code=encodeURIComponent(p.mfr_code||p.mfr_sku||'');
+ if(!sku && !code) return;
+ box.innerHTML='<div class="wp-d-hd">Wallpaper file…</div>';
+ let r; try{ r=await(await fetch(`/api/wallpaper-details?sku=${sku}&code=${code}`)).json(); }catch(e){ box.innerHTML=''; return; }
+ if(!r||!r.ok||!r.found||!r.details){ box.innerHTML='<div class="wp-d-hd wp-d-empty">No matching record in the wallpaper file.</div>'; return; }
+ const d=r.details;
+ const rows=[['Mfr #','mfr_pattern'],['Collection','series'],['Width','width'],['Repeat','repeat'],
+ ['Retail','retail'],['Net','net'],['Cost','cost'],['Roll','roll_size'],['Minimum','minimum'],
+ ['Border','border'],['Price code','five_ten'],['Line','line'],['SKU','combo_sku']]
+ .filter(([,k])=>d[k]).map(([lbl,k])=>{
+ let v=d[k]; if((k==='retail'||k==='net'||k==='cost')&&/^\d/.test(v)) v='$'+v;
+ return `<div class="wp-d-row"><span class="wp-d-lbl">${lbl}</span><span class="wp-d-val">${v}</span></div>`; });
+ const desc=d.internal_desc?`<div class="wp-d-desc">${d.internal_desc}</div>`:'';
+ box.innerHTML=`<div class="wp-d-card"><div class="wp-d-hd">📒 Wallpaper file${r.matched>1?` · ${r.matched} records`:''}</div><div class="wp-d-grid">${rows.join('')}</div>${desc}</div>`;
+}
// INCOMING SAMPLES (Check Sample In): show the ordered-not-received queue with image+spec; on a scan, flag the match.
let _incoming=[];
async function loadIncoming(){ const box=$('#fbIncoming'); if(!box) return; box.innerHTML='<div class="inc-hd">Loading samples coming in…</div>';
diff --git a/server.js b/server.js
index 571516b..c16ff3f 100644
--- a/server.js
+++ b/server.js
@@ -1415,6 +1415,38 @@ const appHandler = (req, res) => {
return;
}
+ // Full product record from the WALLPAPER FileMaker file, keyed by the scanned SKU. WALLPAPER2 is one
+ // mega-table (product master + sample-request rows share a combo sku), so a lookup returns several
+ // records — most spec fields blank on the sample rows. We COALESCE the non-empty value per field across
+ // all matches so the master record's specs surface. 'Basic List of Fields' has the spec fields + NO
+ // portals (so it can't stall like the sample-request layout does).
+ if (u.pathname === '/api/wallpaper-details' && req.method === 'GET') {
+ if (!FM_ENABLED()) return send(res, 200, { ok: false, err: 'FileMaker not configured' });
+ (async () => {
+ const sku = (u.searchParams.get('sku') || '').trim(); // house combo sku (DWxx…)
+ const code = (u.searchParams.get('code') || '').trim(); // mfr pattern (vendor code)
+ if (!sku && !code) return send(res, 400, { ok: false, err: 'sku or code required' });
+ const LAYOUT2 = process.env.FM_WP_LAYOUT || 'Basic List of Fields';
+ // fields to surface: [clean key, FileMaker field name]
+ const MAP = [['mfr_pattern','Mfr Pattern'],['js_pattern','JS Pattern'],['series','Series'],
+ ['width','Width'],['repeat','Repeat'],['retail','Retail Price'],['net','Net Price'],['cost','Cost'],
+ ['supplier','Supplier'],['roll_size','HP Roll Size'],['minimum','Minimum'],['border','Border'],
+ ['five_ten','Five Ten Code'],['line','Line'],['internal_desc','Internal Description'],['combo_sku','combo sku']];
+ let recs = [];
+ try {
+ if (sku) { const r = await FM.fmFind(FM_DB, LAYOUT2, [{ 'combo sku': '==' + sku }], { limit: 20, portal: [] }); recs = r.records || []; }
+ if (!recs.length && code) { const r = await FM.fmFind(FM_DB, LAYOUT2, [{ 'Mfr Pattern': '==' + code }], { limit: 20, portal: [] }); recs = r.records || []; }
+ } catch (e) { return send(res, 200, { ok: false, err: e.message }); }
+ if (!recs.length) return send(res, 200, { ok: true, found: false, details: null, matched: 0 });
+ const details = {};
+ for (const [key, fld] of MAP) { // coalesce: first non-empty value across matches
+ for (const rec of recs) { const v = (rec.fieldData[fld] || '').toString().trim(); if (v && v !== '=') { details[key] = v; break; } }
+ }
+ send(res, 200, { ok: true, found: true, details, matched: recs.length });
+ })();
+ return;
+ }
+
// Print-sticker "send": STAMP the sample as sent (Date WP Sample Sent = today — Steve's "field to
// fill in") and, if a flag field is configured, ALSO flag the record so the Mac's FileMaker Pro
// poller runs the physical Zebra print. Write is dryRun unless FM_WRITE=1 (protects the live file).
← d4db33e doc: cross-reference the shared Date WP Sample Sent writer (
·
back to Dw Photo Capture
·
Scan: partial/fuzzy wallpaper-file lookup — new /api/wallpap 8837b5f →