[object Object]

← back to Designer Wallcoverings

fix-dup-skus-0717: reassign 29 colliding 7/17 PR SKUs to unique stride-10 numbers (Steve go)

a67f4620fb773510f06c7e5c272910cd9a675599 · 2026-07-21 14:00:39 -0700 · Steve

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files touched

Diff

commit a67f4620fb773510f06c7e5c272910cd9a675599
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jul 21 14:00:39 2026 -0700

    fix-dup-skus-0717: reassign 29 colliding 7/17 PR SKUs to unique stride-10 numbers (Steve go)
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 .../wallquest-refresh/data/dup-sku-remap-0717.json | 176 +++++++++++++++++++++
 scripts/wallquest-refresh/fix-dup-skus-0717.cjs    |  92 +++++++++++
 2 files changed, 268 insertions(+)

diff --git a/scripts/wallquest-refresh/data/dup-sku-remap-0717.json b/scripts/wallquest-refresh/data/dup-sku-remap-0717.json
new file mode 100644
index 00000000..f6a42b5c
--- /dev/null
+++ b/scripts/wallquest-refresh/data/dup-sku-remap-0717.json
@@ -0,0 +1,176 @@
+[
+ {
+  "pid": "7887542517811",
+  "handle": "anhe-abaca-greige-pr-46125",
+  "old": "ABA-46125",
+  "new": "ABA-46165"
+ },
+ {
+  "pid": "7887542550579",
+  "handle": "anhe-abaca-peppercorn-pr-46135",
+  "old": "ABA-46135",
+  "new": "ABA-46175"
+ },
+ {
+  "pid": "7887542583347",
+  "handle": "meilin-grasscloth-lagoon-pr-800986",
+  "old": "GRS-800986",
+  "new": "GRS-801436"
+ },
+ {
+  "pid": "7887542616115",
+  "handle": "meilin-grasscloth-lake-forest-green-pr-800996",
+  "old": "GRS-800996",
+  "new": "GRS-801446"
+ },
+ {
+  "pid": "7887542648883",
+  "handle": "meilin-grasscloth-moss-pr-801006",
+  "old": "GRS-801006",
+  "new": "GRS-801456"
+ },
+ {
+  "pid": "7887542681651",
+  "handle": "meilin-grasscloth-pebblestone-pr-801016",
+  "old": "GRS-801016",
+  "new": "GRS-801466"
+ },
+ {
+  "pid": "7887550775347",
+  "handle": "bergamo-azure-pr-801136",
+  "old": "GRS-801136",
+  "new": "GRS-801476"
+ },
+ {
+  "pid": "7887563849779",
+  "handle": "huanzhu-leaf-stripe-chocolate-pr-801136",
+  "old": "GRS-801136",
+  "new": "GRS-801486"
+ },
+ {
+  "pid": "7887550808115",
+  "handle": "bergamo-tan-pr-801146",
+  "old": "GRS-801146",
+  "new": "GRS-801496"
+ },
+ {
+  "pid": "7887563882547",
+  "handle": "huanzhu-leaf-stripe-navy-pr-801146",
+  "old": "GRS-801146",
+  "new": "GRS-801506"
+ },
+ {
+  "pid": "7887550840883",
+  "handle": "chess-cappuccino-pr-801156",
+  "old": "GRS-801156",
+  "new": "GRS-801516"
+ },
+ {
+  "pid": "7887563915315",
+  "handle": "huanzhu-leaf-stripe-oyster-gold-pr-801156",
+  "old": "GRS-801156",
+  "new": "GRS-801526"
+ },
+ {
+  "pid": "7887550873651",
+  "handle": "chess-oat-pr-801166",
+  "old": "GRS-801166",
+  "new": "GRS-801536"
+ },
+ {
+  "pid": "7887563948083",
+  "handle": "huanzhu-leaf-stripe-sage-silver-pr-801166",
+  "old": "GRS-801166",
+  "new": "GRS-801546"
+ },
+ {
+  "pid": "7887550906419",
+  "handle": "manila-gold-glam-pr-801176",
+  "old": "GRS-801176",
+  "new": "GRS-801556"
+ },
+ {
+  "pid": "7887550939187",
+  "handle": "manila-oat-pr-801186",
+  "old": "GRS-801186",
+  "new": "GRS-801566"
+ },
+ {
+  "pid": "7887550971955",
+  "handle": "palais-gold-glam-pr-801196",
+  "old": "GRS-801196",
+  "new": "GRS-801576"
+ },
+ {
+  "pid": "7887551037491",
+  "handle": "palais-juniper-pr-801206",
+  "old": "GRS-801206",
+  "new": "GRS-801586"
+ },
+ {
+  "pid": "7887551070259",
+  "handle": "palais-new-jade-pr-801216",
+  "old": "GRS-801216",
+  "new": "GRS-801596"
+ },
+ {
+  "pid": "7887551135795",
+  "handle": "palais-peacock-pr-801226",
+  "old": "GRS-801226",
+  "new": "GRS-801606"
+ },
+ {
+  "pid": "7887551168563",
+  "handle": "palais-stone-pr-801236",
+  "old": "GRS-801236",
+  "new": "GRS-801616"
+ },
+ {
+  "pid": "7887551201331",
+  "handle": "shantung-aged-stone-pr-801246",
+  "old": "GRS-801246",
+  "new": "GRS-801626"
+ },
+ {
+  "pid": "7887551234099",
+  "handle": "shantung-celadon-pr-801256",
+  "old": "GRS-801256",
+  "new": "GRS-801636"
+ },
+ {
+  "pid": "7887551266867",
+  "handle": "shantung-deep-aqua-pr-801266",
+  "old": "GRS-801266",
+  "new": "GRS-801646"
+ },
+ {
+  "pid": "7887551299635",
+  "handle": "shantung-new-jade-pr-801276",
+  "old": "GRS-801276",
+  "new": "GRS-801656"
+ },
+ {
+  "pid": "7887551365171",
+  "handle": "sisal-pr-100227",
+  "old": "SIS-100226",
+  "new": "SIS-100746"
+ },
+ {
+  "pid": "7887551397939",
+  "handle": "sisal-pr-100237",
+  "old": "SIS-100236",
+  "new": "SIS-100756"
+ },
+ {
+  "pid": "7887551430707",
+  "handle": "sisal-pr-100247",
+  "old": "SIS-100246",
+  "new": "SIS-100766"
+ },
+ {
+  "pid": "7887551463475",
+  "handle": "sisal-pr-100257",
+  "old": "SIS-100256",
+  "new": "SIS-100776"
+ }
+]
\ No newline at end of file
diff --git a/scripts/wallquest-refresh/fix-dup-skus-0717.cjs b/scripts/wallquest-refresh/fix-dup-skus-0717.cjs
new file mode 100644
index 00000000..998c8906
--- /dev/null
+++ b/scripts/wallquest-refresh/fix-dup-skus-0717.cjs
@@ -0,0 +1,92 @@
+// Re-assign unique SKUs to the 7/17 PR ACTIVE duplicate-SKU collision groups.
+// Steve go 2026-07-21 ("use unique skus"). Rule: lowest product id keeps the SKU;
+// every other product in the group gets the next free stride-10 number in its
+// prefix series (uniqueness checked against ALL variant_skus in the mirror,
+// including the archived Lillian August ranges). Sample variant follows.
+// Usage: node fix-dup-skus-0717.cjs [--dry]
+const { execSync } = require('child_process');
+const https = require('https');
+const fs = require('fs');
+
+const TOK = process.env.SHOPIFY_ADMIN_TOKEN;
+const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
+if (!TOK) { console.error('SHOPIFY_ADMIN_TOKEN missing'); process.exit(1); }
+const DRY = process.argv.includes('--dry');
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+function rest(method, path, body) {
+  return new Promise((res, rej) => {
+    const data = body ? JSON.stringify(body) : null;
+    const r = https.request({
+      hostname: DOMAIN, path: `/admin/api/2024-10/${path}`, method,
+      headers: { 'X-Shopify-Access-Token': TOK, 'Content-Type': 'application/json', ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}) }
+    }, rs => { let d = ''; rs.on('data', c => d += c); rs.on('end', () => { try { res({ status: rs.statusCode, body: d ? JSON.parse(d) : {} }); } catch (e) { res({ status: rs.statusCode, body: {} }); } }); });
+    r.on('error', rej); if (data) r.write(data); r.end();
+  });
+}
+function psqlRows(sql) {
+  const out = execSync(`psql "host=/tmp dbname=dw_unified" -tA -F'\x1f' -c ${JSON.stringify(sql.replace(/\s+/g, ' ').trim())}`, { maxBuffer: 64 * 1024 * 1024 }).toString().trim();
+  return out ? out.split('\n').map(l => l.split('\x1f')) : [];
+}
+
+(async () => {
+  // all existing SKUs (global uniqueness pool)
+  const allSkus = new Set(psqlRows(`SELECT DISTINCT variant_sku FROM shopify_products WHERE variant_sku ~ '^(GRS|SIS|RAF|ABA|PWV|JUT|CORK|MIC)-'`).map(r => r[0].toUpperCase()));
+
+  // collision groups among 7/17 ACTIVE PR
+  const rows = psqlRows(`
+    SELECT variant_sku, replace(shopify_id,'gid://shopify/Product/',''), handle
+    FROM shopify_products
+    WHERE vendor='Phillipe Romano' AND created_at_shopify::date='2026-07-17' AND status='ACTIVE'
+      AND variant_sku NOT LIKE '%-Sample'
+    GROUP BY 1,2,3 ORDER BY 1, 2::bigint`);
+  const groups = {};
+  for (const [sku, pid, handle] of rows) (groups[sku] = groups[sku] || []).push({ pid, handle });
+
+  // per-prefix max number for allocation
+  const maxByPrefix = {};
+  for (const s of allSkus) {
+    const m = s.match(/^([A-Z]+)-(\d+)$/);
+    if (m) maxByPrefix[m[1]] = Math.max(maxByPrefix[m[1]] || 0, parseInt(m[2], 10));
+  }
+  const alloc = prefix => {
+    let n = (maxByPrefix[prefix] || 0) + 10;
+    while (allSkus.has(`${prefix}-${n}`)) n += 10;
+    maxByPrefix[prefix] = n;
+    const sku = `${prefix}-${n}`;
+    allSkus.add(sku);
+    return sku;
+  };
+
+  const mapping = [];
+  let changed = 0, failed = 0;
+  for (const [sku, prods] of Object.entries(groups)) {
+    if (prods.length < 2) continue;
+    const prefix = sku.split('-')[0];
+    // keeper = lowest product id (first created)
+    for (const p of prods.slice(1)) {
+      const newSku = alloc(prefix);
+      mapping.push({ pid: p.pid, handle: p.handle, old: sku, new: newSku });
+      if (DRY) { console.log(`DRY ${p.handle}: ${sku} -> ${newSku}`); continue; }
+      // fetch variants, update roll + sample
+      const vr = await rest('GET', `products/${p.pid}/variants.json`);
+      let ok = true;
+      for (const v of vr.body.variants || []) {
+        const cur = (v.sku || '').toUpperCase();
+        if (cur !== sku.toUpperCase() && cur !== `${sku.toUpperCase()}-SAMPLE`) continue;
+        const target = cur.endsWith('-SAMPLE') ? `${newSku}-Sample` : newSku;
+        const ur = await rest('PUT', `variants/${v.id}.json`, { variant: { id: v.id, sku: target } });
+        if (ur.status !== 200) { console.error(`  ❌ ${p.handle} variant ${v.id}: HTTP ${ur.status}`); ok = false; }
+        await sleep(350);
+      }
+      if (ok) {
+        execSync(`psql "host=/tmp dbname=dw_unified" -q -c "UPDATE shopify_products SET variant_sku=replace(variant_sku,'${sku}','${newSku}'), dw_sku=CASE WHEN dw_sku='${sku}' THEN '${newSku}' ELSE dw_sku END WHERE shopify_id='gid://shopify/Product/${p.pid}'"`);
+        changed++;
+        console.log(`  ✅ ${p.handle}: ${sku} -> ${newSku}`);
+      } else failed++;
+      await sleep(200);
+    }
+  }
+  fs.writeFileSync(__dirname + '/data/dup-sku-remap-0717.json', JSON.stringify(mapping, null, 1));
+  console.log(`\n${DRY ? 'DRY RUN — ' : ''}groups: ${Object.values(groups).filter(g => g.length > 1).length}, reassigned: ${DRY ? mapping.length + ' planned' : changed}, failed: ${failed}`);
+})().catch(e => { console.error('FATAL', e); process.exit(1); });

← 45941f84 backfill: --drafts flag; 40 PR 7/17 drafts backfilled (Steve  ·  back to Designer Wallcoverings  ·  chore: v1.2.4 (session close — dup-sku fix + drafts backfill 7ac09087 →