← back to Mfr Review Viewer Corruption
viewer: auto-apply keep/delete rule (delete broken no-sample masters, keep real sample records) + sample-history badges
8c74f843d675a1df555ecb440ec4cf43c001cd14 · 2026-08-27 09:00:49 -0700 · Steve Abrams
Files touched
M lib/candidates.mjsM public/index.html
Diff
commit 8c74f843d675a1df555ecb440ec4cf43c001cd14
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 27 09:00:49 2026 -0700
viewer: auto-apply keep/delete rule (delete broken no-sample masters, keep real sample records) + sample-history badges
---
lib/candidates.mjs | 37 ++++++++++++++++++++++++++++++++++++-
public/index.html | 29 +++++++++++++++++++++++++++++
2 files changed, 65 insertions(+), 1 deletion(-)
diff --git a/lib/candidates.mjs b/lib/candidates.mjs
index af5b83b..2a86dde 100644
--- a/lib/candidates.mjs
+++ b/lib/candidates.mjs
@@ -157,6 +157,19 @@ async function fmCandidates(dwSku) {
const mfrNote = mfrPattern || (/#/.test(chaseMemo) ? chaseMemo : '');
const noteMfr = parseNoteMfr(mfrNote) || parseNoteMfr(chaseMemo);
+ // ---- sample-order history (Steve's rule: a "master" = a record where a client
+ // sample was NEVER ordered). Both fields live on the Full View layout. Non-empty
+ // in EITHER = a client sample was ordered against this record -> a KEEP record. ----
+ const sampleReq = String(fd['today for client'] || '').trim();
+ const sampleSent = String(fd['Date WP Sample Sent'] || '').trim();
+ const sampleOrdered = !!(sampleReq || sampleSent);
+ const sampleDate = sampleSent || sampleReq || '';
+ // ---- placeholder mfr = the parsed code is purely the DW# numeric tail (no real
+ // alpha code) i.e. the "DW# == Mfr SKU" corruption, not a real manufacturer code. ----
+ const codeHasAlpha = /[A-Za-z]/.test(noteMfr || '');
+ const codeDigits = String(noteMfr || mfrPattern || '').replace(/[^0-9]/g, '');
+ const mfrPlaceholder = !codeHasAlpha && !!tail && codeDigits === tail;
+
const cand = {
recordId: rec.recordId,
comboSku: String(fd['combo sku'] || '').trim(),
@@ -173,6 +186,9 @@ async function fmCandidates(dwSku) {
mfrNote, // free-text carrier of the real code
noteMfr, // best-guess real mfr parsed from the note
skuMatch, // true = Series/combo normalizes to this dw_sku
+ sampleOrdered, // true = a client sample WAS ordered (a KEEP record)
+ sampleDate, // the sample sent/request date for display
+ mfrPlaceholder, // true = mfr is just the DW# placeholder (no real code)
};
byId.set(rec.recordId, cand);
}
@@ -248,5 +264,24 @@ export async function candidatesForSku(dwSku) {
fmCandidates(dwSku).catch((e) => ({ filemaker: [], fmError: e.message })),
unifiedCandidates(dwSku).catch(() => []),
]);
- return { filemaker: fm.filemaker || [], unified: unified || [], fmError: fm.fmError || null };
+ const recs = fm.filemaker || [];
+
+ // ---- apply Steve's rule (2026-08-27): DELETE the broken no-sample "masters",
+ // KEEP the real sample-order records. A record is DELETE-suggested iff it has NO
+ // sample history AND its mfr is just the DW# placeholder — GUARDED so we never
+ // delete a SKU to zero: at least one KEEP must remain. A KEEP = a record that
+ // either had a client sample ordered OR carries a real (non-placeholder) mfr. ----
+ const isKeepWorthy = (r) => r.sampleOrdered || (!r.mfrPlaceholder && /[A-Za-z]/.test(r.noteMfr || r.mfrPattern || ''));
+ const keepers = recs.filter(isKeepWorthy);
+ // the real mfr to pre-fill: prefer a sample-ordered record's real code, else any real code.
+ const realSrc = keepers.find((r) => r.sampleOrdered && /[A-Za-z]/.test(r.noteMfr || '')) || keepers.find((r) => /[A-Za-z]/.test(r.noteMfr || ''));
+ const realMfr = realSrc ? (realSrc.noteMfr || realSrc.mfrPattern || '') : '';
+ const keepRid = realSrc ? String(realSrc.recordId) : (keepers[0] ? String(keepers[0].recordId) : '');
+ const canPrune = keepers.length > 0; // guard: only suggest deletes if a keeper survives
+ for (const r of recs) {
+ r.deleteSuggested = canPrune && !r.sampleOrdered && r.mfrPlaceholder && String(r.recordId) !== keepRid;
+ }
+
+ return { filemaker: recs, unified: unified || [], fmError: fm.fmError || null,
+ suggestion: { realMfr, keepRid, deleteRids: recs.filter((r) => r.deleteSuggested).map((r) => String(r.recordId)) } };
}
diff --git a/public/index.html b/public/index.html
index 0a8e8dc..0666bb9 100644
--- a/public/index.html
+++ b/public/index.html
@@ -123,6 +123,10 @@
.prune-summary { font-size:11px; color:var(--muted); margin-top:2px; }
.prune-summary .n-del { color:var(--red); font-weight:700; }
.prune-summary .n-keep { color:var(--teal); font-weight:700; }
+ .samp-badge { display:inline-block; font-size:10px; padding:1px 6px; border-radius:8px; margin-right:5px; font-weight:600; }
+ .samp-yes { background:#123024; color:#7fe0b0; }
+ .samp-no { background:#2a2320; color:#d9b48a; }
+ .samp-sug { background:#3a1a1f; color:#ff9aa6; border:1px solid #6b2b33; }
/* ---- Sticky action bar ---- */
.action { position:fixed; left:280px; right:0; bottom:0; background:var(--panel);
@@ -368,12 +372,31 @@ async function toggleCandidates(sku, tog, panel){
const r = await fetch('api/candidates?dw_sku='+encodeURIComponent(sku));
const j = await r.json();
state.cand[sku] = j;
+ applySuggestion(sku, j); // auto-apply Steve's keep/delete rule (once per SKU)
renderCandidates(sku, panel);
} catch(e){
panel.innerHTML = '<div class="cand-err">Failed to load candidates: '+esc(e.message)+'</div>';
}
}
+// Auto-apply the backend's keep/delete suggestion ONCE per SKU. Steve's rule
+// (2026-08-27): DELETE the broken no-sample "masters", KEEP the real sample-order
+// records. Pre-marks the suggested deletes, remembers the keep, and pre-fills the
+// real mfr — the reviewer can still override any of it before staging.
+function applySuggestion(sku, j){
+ if(!state.applied) state.applied = {};
+ if(state.applied[sku]) return; // never clobber a prior manual choice
+ state.applied[sku] = true;
+ const sug = j && j.suggestion; if(!sug) return;
+ if(sug.deleteRids && sug.deleteRids.length){
+ const set = state.delRids[sku] || new Set();
+ for(const rid of sug.deleteRids) set.add(String(rid));
+ state.delRids[sku] = set;
+ }
+ if(sug.keepRid) state.keepRid[sku] = String(sug.keepRid);
+ if(sug.realMfr) chooseMfr(sku, sug.realMfr); // pre-fill the confirmed real mfr
+}
+
// Write a chosen real mfr into this row's confirmed-mfr input + edits state, re-render card.
function chooseMfr(sku, value){
state.edits[sku] = value;
@@ -435,6 +458,12 @@ function renderCandidates(sku, panel){
`<span class="cand-line cand-meta">`+
(c.name?`${esc(c.name)} · `:'')+(c.color?`${esc(c.color)} · `:'')+(c.vid?`vid ${esc(c.vid)}`:'')+
`</span>`+
+ `<span class="cand-line">`+
+ (c.sampleOrdered
+ ? `<span class="samp-badge samp-yes" title="a client sample WAS ordered against this record — KEEP">🧾 sampled${c.sampleDate?' '+esc(c.sampleDate):''}</span>`
+ : `<span class="samp-badge samp-no" title="no client sample ever ordered — a 'master' template (deletable when its mfr is just the DW# placeholder)">⛔ never sampled</span>`)+
+ (c.deleteSuggested?`<span class="samp-badge samp-sug" title="auto-suggested for deletion by the rule (no sample + placeholder mfr, a keeper survives)">auto-delete</span>`:'')+
+ `</span>`+
(real?`<span class="cand-line">→ real mfr: <span class="cand-realmfr">${esc(real)}</span></span>`:'')+
`</span>
<span class="del-wrap" title="Mark THIS master for deletion (keep the rest)">
← 8e3663b mfr-review-viewer: gated delete-record (keep-rest) staging w
·
back to Mfr Review Viewer Corruption
·
viewer: per-chip red 'Remove suggested (N)' button — one-cli 377ff9b →