[object Object]

← back to Designer Wallcoverings

Carl Robinson: harden Shopify SKU update — key on product-id+variant-role, live duplicate-guard, idempotent (prevents GRS/MIC number dupes)

2faa01a52b15498edfb34a5315a73418e0b1879f · 2026-07-06 12:27:18 -0700 · steve@designerwallcoverings.com

Files touched

Diff

commit 2faa01a52b15498edfb34a5315a73418e0b1879f
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Mon Jul 6 12:27:18 2026 -0700

    Carl Robinson: harden Shopify SKU update — key on product-id+variant-role, live duplicate-guard, idempotent (prevents GRS/MIC number dupes)
---
 .../update-shopify-skus-carl-robinson.cjs          | 64 ++++++++++++++--------
 1 file changed, 40 insertions(+), 24 deletions(-)

diff --git a/scripts/wallquest-refresh/update-shopify-skus-carl-robinson.cjs b/scripts/wallquest-refresh/update-shopify-skus-carl-robinson.cjs
index b0809821..d939aa70 100644
--- a/scripts/wallquest-refresh/update-shopify-skus-carl-robinson.cjs
+++ b/scripts/wallquest-refresh/update-shopify-skus-carl-robinson.cjs
@@ -1,7 +1,9 @@
-// Update the 67 live Carl Robinson Shopify products' variant SKUs DWCR-*  →  material-based
-// (DWGRS-/DWPWV-/DWCORK-/DWMIC-…), and re-key dw_sku_registry. Beach-city titles unchanged.
-// Reads /tmp/cr-sku-remap.json (old,new,shopify_product_id). Env: SHOPIFY_ADMIN_TOKEN, DATABASE_URL.
-const fs = require('fs');
+// Set the 67 Carl Robinson products' variant SKUs to the final material-series scheme
+// (GRS-100020…, NET-100042…, MIC-100065). HARDENED against concurrency + duplicates:
+//  • keys off shopify_product_id + variant role (Standard/Sample), NOT a stale "old" SKU
+//  • DUPLICATE GUARD: before setting a SKU, checks no OTHER product already holds it → skip+flag
+//  • idempotent: if a variant already has the target SKU, it's left alone
+// Env: SHOPIFY_ADMIN_TOKEN, DATABASE_URL.
 const https = require('https');
 const { Client } = require('pg');
 
@@ -10,7 +12,6 @@ const API = '2024-10';
 const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
 if (!TOKEN) { console.error('no SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
 const CONN = process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified';
-const map = JSON.parse(fs.readFileSync('/tmp/cr-sku-remap.json', 'utf8')).filter(m => m.shopify_product_id);
 const sleep = ms => new Promise(r => setTimeout(r, ms));
 
 function rest(method, path, body) {
@@ -23,32 +24,47 @@ function rest(method, path, body) {
     if (data) req.write(data); req.end();
   });
 }
+function gql(query) {
+  return new Promise((resolve, reject) => {
+    const body = JSON.stringify({ query });
+    const req = https.request({ hostname: STORE, path: `/admin/api/${API}/graphql.json`, method: 'POST',
+      headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }, timeout: 40000 },
+      res => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch (e) { reject(new Error('bad json')); } }); });
+    req.on('error', reject); req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
+    req.write(body); req.end();
+  });
+}
+// returns true if some variant with this sku exists on a DIFFERENT product
+async function skuTakenElsewhere(sku, myPid) {
+  const q = `{ productVariants(first:5, query:"sku:'${sku.replace(/'/g, "")}'"){edges{node{id sku product{id legacyResourceId}}}} }`;
+  const r = await gql(q);
+  const edges = r.data?.productVariants?.edges || [];
+  return edges.some(e => String(e.node.product.legacyResourceId) !== String(myPid));
+}
 
 (async () => {
   const db = new Client({ connectionString: CONN }); await db.connect();
-  let ok = 0, err = 0;
-  for (const m of map) {
+  const { rows } = await db.query("SELECT c.mfr_sku, c.dw_sku, r.shopify_product_id FROM carl_robinson_catalog c JOIN dw_sku_registry r ON r.mfr_sku=c.mfr_sku WHERE r.shopify_product_id IS NOT NULL ORDER BY c.dw_sku");
+  console.log(`checking + updating ${rows.length} products (duplicate-guarded)…`);
+  let set = 0, already = 0, skipped = 0, err = 0;
+  for (const p of rows) {
     try {
-      const g = await rest('GET', `/products/${m.shopify_product_id}.json?fields=id,variants`);
+      const g = await rest('GET', `/products/${p.shopify_product_id}.json?fields=id,variants`);
       const variants = g.j?.product?.variants || [];
-      let changed = 0;
       for (const v of variants) {
-        let nsku = null;
-        if (v.sku === m.old) nsku = m.new;
-        else if (v.sku === `${m.old}-Sample`) nsku = `${m.new}-Sample`;
-        if (nsku && nsku !== v.sku) {
-          const u = await rest('PUT', `/variants/${v.id}.json`, { variant: { id: v.id, sku: nsku } });
-          if (u.status >= 200 && u.status < 300) changed++;
-          else console.log(`  ✗ variant ${v.id} ${v.sku}→${nsku} HTTP ${u.status}`);
-          await sleep(550);
-        }
+        const isSample = /sample/i.test(v.title || v.option1 || '') || /-sample$/i.test(v.sku || '');
+        const target = isSample ? `${p.dw_sku}-Sample` : p.dw_sku;
+        if (v.sku === target) { already++; continue; }
+        if (await skuTakenElsewhere(target, p.shopify_product_id)) { skipped++; console.log(`⚠ DUP GUARD: ${target} already on another product — skipped ${p.mfr_sku}`); await sleep(400); continue; }
+        const u = await rest('PUT', `/variants/${v.id}.json`, { variant: { id: v.id, sku: target } });
+        if (u.status >= 200 && u.status < 300) { set++; console.log(`✓ ${p.mfr_sku} ${isSample ? 'Sample' : 'Standard'} → ${target}`); }
+        else { err++; console.log(`✗ ${p.mfr_sku} → ${target} HTTP ${u.status}`); }
+        await sleep(500);
       }
-      // re-key registry old → new
-      await db.query('UPDATE dw_sku_registry SET dw_sku=$1, updated_at=now() WHERE dw_sku=$2', [m.new, m.old]);
-      ok++; console.log(`✓ ${m.old} → ${m.new} (${changed} variant sku${changed===1?'':'s'}) [${ok}/${map.length}]`);
-    } catch (e) { err++; console.log(`✗ ${m.old} ${String(e).slice(0, 90)}`); }
-    await sleep(300);
+      await db.query("UPDATE dw_sku_registry SET dw_sku=$1, updated_at=now() WHERE mfr_sku=$2", [p.dw_sku, p.mfr_sku]);
+    } catch (e) { err++; console.log(`✗ ${p.mfr_sku} ${String(e).slice(0, 90)}`); }
+    await sleep(200);
   }
-  console.log(`\nSKU UPDATE DONE: products=${ok} errors=${err} (variant SKUs → material-based; registry re-keyed)`);
+  console.log(`\nSKU UPDATE DONE: set=${set} already-correct=${already} dup-skipped=${skipped} errors=${err}`);
   await db.end();
 })().catch(e => { console.error('FATAL', e.message); process.exit(1); });

← 366e5801 Carl Robinson: unique running 6-digit SKU numbers across boo  ·  back to Designer Wallcoverings  ·  Carl Robinson: fix publish-channels to anchor on carl_robins 2347c1e6 →