[object Object]

← back to Dw Validator Debug TK11314

block sample prices from roll imports

3c6d03f68b92fc4a7130eef904673f313f999fcd · 2026-08-27 17:05:45 -0700 · Steve

Files touched

Diff

commit 3c6d03f68b92fc4a7130eef904673f313f999fcd
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 27 17:05:45 2026 -0700

    block sample prices from roll imports
---
 .../scripts/import-queue-runner.js                 | 24 ++++++++++++++++++++--
 .../ImportNewSkufromURL/test/cost-guard.test.js    | 22 ++++++++++++++------
 2 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.js b/DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.js
index 68a12e4c..ca24bbc2 100644
--- a/DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.js
+++ b/DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.js
@@ -50,6 +50,24 @@ function mergeSidecar(preview, sc) {
 // sidecar), build the create-product ProductDTO straight from the sidecar we
 // already have. Re-scrape is the only broken link; everything else is present.
 const crypto = require('crypto');
+function normalizeConfirmedRollPrice(value) {
+  let amount;
+  if (typeof value === 'number') {
+    amount = value;
+  } else if (typeof value === 'string' && value.trim()) {
+    const normalized = value.trim().replace(/^\$\s*/, '').replace(/,/g, '');
+    if (!/^\d+(?:\.\d+)?$/.test(normalized)) return null;
+    amount = Number(normalized);
+  } else {
+    return null;
+  }
+
+  // $4.25 is the memo-sample price. A missing or sample-sized amount must
+  // never become a roll/product price.
+  if (!Number.isFinite(amount) || amount <= 5) return null;
+  return amount.toFixed(2);
+}
+
 function buildPreviewFromSidecar(vendor, sku, url, sc) {
   if (!sc) return null;
   const isUrl = (u) => typeof u === 'string' && /^https?:\/\//.test(u);
@@ -60,6 +78,8 @@ function buildPreviewFromSidecar(vendor, sku, url, sc) {
   if (sc.color && !String(title).toLowerCase().includes(String(sc.color).toLowerCase())) title += ' ' + sc.color;
   if (!/\|/.test(title)) title += ' | ' + brand;
   const s = sc.specs || {};
+  const price = normalizeConfirmedRollPrice(s.price);
+  if (!price) return null;                                  // no confirmed roll price → not import-ready
   const specs = { sku, brand,
     collection: sc.collection || s.collection,
     product_type: 'Wallcovering',
@@ -75,7 +95,7 @@ function buildPreviewFromSidecar(vendor, sku, url, sc) {
     title, bodyHtml: sc.description || '',
     tags: [brand].concat(sc.color ? [sc.color] : []),
     images, specs,
-    price: (s.price != null && String(s.price)) || '4.25',
+    price,
     vendor: brand,
   };
 }
@@ -187,7 +207,7 @@ function applyBrandOverride(p, brand) {
 // invariants without standing up the :9830 app or running the live drain. The
 // drain only runs when this file is invoked directly as the main module.
 module.exports = {
-  buildPreviewFromSidecar, mergeSidecar, applyBrandOverride,
+  buildPreviewFromSidecar, normalizeConfirmedRollPrice, mergeSidecar, applyBrandOverride,
   normalizeTitle, scrubStr, SCRUB_RES, TITLE_KEEP,
 };
 
diff --git a/DW-Programming/ImportNewSkufromURL/test/cost-guard.test.js b/DW-Programming/ImportNewSkufromURL/test/cost-guard.test.js
index 6b1af678..1a2b8c47 100644
--- a/DW-Programming/ImportNewSkufromURL/test/cost-guard.test.js
+++ b/DW-Programming/ImportNewSkufromURL/test/cost-guard.test.js
@@ -69,12 +69,22 @@ const URL_C = 'https://vendor.example.com/products/tek-wall';
 
 test('I2: sample-only SKU never imports with the $4.25 memo-sample as the product price', () => {
   const pv = runner.buildPreviewFromSidecar('phillip-jeffries', 'PJ-1001', URL_A, sidecarSampleOnly);
-  // A sample-only SKU has no real roll price. The correct behavior is to refuse
-  // to build an importable preview (return null) — NOT to stamp on $4.25.
-  if (pv === null) return; // correct: refused to fabricate a price
-  const price = parseFloat(pv.price);
-  assert.notEqual(price, SAMPLE_PRICE,
-    `runner emitted the memo-SAMPLE price ($4.25) as the product price for a sample-only SKU (price=${pv.price})`);
+  assert.equal(pv, null, 'sample-only SKU must not produce an importable preview');
+});
+
+test('I1: zero, malformed, and explicit sample prices are rejected', () => {
+  for (const price of [0, 'not-a-price', SAMPLE_PRICE]) {
+    const pv = runner.buildPreviewFromSidecar('thibaut', 'TH-BAD', URL_B, {
+      ...sidecarCostedRoll,
+      specs: { ...sidecarCostedRoll.specs, price },
+    });
+    assert.equal(pv, null, `invalid roll price ${JSON.stringify(price)} must be rejected`);
+  }
+});
+
+test('I1: confirmed numeric and currency-formatted roll prices are normalized', () => {
+  assert.equal(runner.normalizeConfirmedRollPrice(168), '168.00');
+  assert.equal(runner.normalizeConfirmedRollPrice('$1,234.50'), '1234.50');
 });
 
 test('I1: no preview ever carries a roll price <= $5 (placeholder/sample leak)', () => {

← 59499ae1 snapshot before fixing roll price validation  ·  back to Dw Validator Debug TK11314  ·  improve Shopify purchase and collection UX a6461a60 →