← back to Designer Wallcoverings
Versace cleanup: revert 11 Smart Art borders to single-roll + repair 9 DWVS-NaN SKUs to DWVS-<mfr> and DRAFT them
0996e036b9d8cb45300feb414cca570becec20ba · 2026-07-14 10:15:59 -0700 · Steve
Files touched
A DW-Programming/repair-versace-nan-sku.jsA DW-Programming/revert-versace-smartart-single.js
Diff
commit 0996e036b9d8cb45300feb414cca570becec20ba
Author: Steve <steve@designerwallcoverings.com>
Date: Tue Jul 14 10:15:59 2026 -0700
Versace cleanup: revert 11 Smart Art borders to single-roll + repair 9 DWVS-NaN SKUs to DWVS-<mfr> and DRAFT them
---
DW-Programming/repair-versace-nan-sku.js | 83 ++++++++++++++++++++++++
DW-Programming/revert-versace-smartart-single.js | 74 +++++++++++++++++++++
2 files changed, 157 insertions(+)
diff --git a/DW-Programming/repair-versace-nan-sku.js b/DW-Programming/repair-versace-nan-sku.js
new file mode 100644
index 00000000..285c8ef9
--- /dev/null
+++ b/DW-Programming/repair-versace-nan-sku.js
@@ -0,0 +1,83 @@
+#!/usr/bin/env node
+/**
+ * Repair the 9 DWVS-NaN-Bolt Versace rows: give each a unique SKU derived from the
+ * mfr article number in its title (DWVS-<mfr>-Sample / DWVS-<mfr>-Bolt), and set the
+ * product to DRAFT (they lack width/images/real pattern name → fail the activate-gate).
+ * Titles keep the mfr-number fallback (compliant: no real name exists in source).
+ *
+ * Usage: node repair-versace-nan-sku.js [--live]
+ */
+require('dotenv').config({ path: require('os').homedir() + '/Projects/secrets-manager/.env' });
+const { Client } = require('pg');
+const LIVE = process.argv.includes('--live');
+const STORE = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-01';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const DB = process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified';
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+// mfr -> { gid, sample variant gid, bolt variant gid } (captured live)
+const TARGETS = {
+ '366924': { p:'7821709475891', s:'44220970270771', b:'44220970303539' },
+ '370507': { p:'7821709508659', s:'44220970369075', b:'44220970401843' },
+ '370551': { p:'7821709541427', s:'44220970467379', b:'44220970500147' },
+ '791333': { p:'7821709606963', s:'44220970598451', b:'44220970631219' },
+ '791351': { p:'7821709672499', s:'44220970729523', b:'44220970762291' },
+ '791352': { p:'7821709705267', s:'44220970827827', b:'44220970860595' },
+ '791361': { p:'7821709738035', s:'44220970926131', b:'44220970958899' },
+ '791755': { p:'7821709770803', s:'44220971024435', b:'44220971057203' },
+ '935834': { p:'7821709803571', s:'44220971122739', b:'44220971155507' },
+};
+
+async function gql(query, variables) {
+ const res = await fetch(`https://${STORE}/admin/api/${API}/graphql.json`, {
+ method: 'POST',
+ headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+ body: JSON.stringify({ query, variables }),
+ });
+ const j = await res.json();
+ if (j.errors) throw new Error(JSON.stringify(j.errors).slice(0, 180));
+ return j.data;
+}
+async function rest(method, path, body) {
+ const res = await fetch(`https://${STORE}/admin/api/${API}/${path}`, {
+ method,
+ headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+ body: body ? JSON.stringify(body) : undefined,
+ });
+ if (!res.ok) throw new Error(`${res.status}: ${(await res.text()).slice(0,160)}`);
+ return res.json();
+}
+async function setVariantSku(varId, sku) {
+ await rest('PUT', `variants/${varId}.json`, { variant: { id: Number(varId), sku } });
+}
+async function setDraft(pId) {
+ await rest('PUT', `products/${pId}.json`, { product: { id: Number(pId), status: 'draft' } });
+}
+
+async function main() {
+ const client = new Client(DB); await client.connect();
+ const entries = Object.entries(TARGETS);
+ console.log(`Mode: ${LIVE ? '🔴 LIVE' : '🟡 DRY RUN'} | ${entries.length} NaN products → SKU repair + DRAFT`);
+ if (!LIVE) {
+ entries.forEach(([mfr]) => console.log(` ${mfr}: -> DWVS-${mfr}-Sample / DWVS-${mfr}-Bolt, status DRAFT`));
+ console.log('Re-run with --live.'); await client.end(); return;
+ }
+ let ok=0, err=0;
+ for (const [mfr, g] of entries) {
+ try {
+ await setVariantSku(g.s, `DWVS-${mfr}-Sample`); await sleep(300);
+ await setVariantSku(g.b, `DWVS-${mfr}-Bolt`); await sleep(300);
+ await setDraft(g.p); await sleep(300);
+ // mirror: reflect new sku + DRAFT (daily sync will also reconcile)
+ await client.query(
+ `UPDATE shopify_products SET sku=$2, variant_sku=$2, status='DRAFT'
+ WHERE shopify_id = $1`,
+ [ `gid://shopify/Product/${g.p}`, `DWVS-${mfr}-Bolt` ]);
+ ok++; console.log(` ✓ ${mfr} -> DWVS-${mfr}-* , DRAFT`);
+ } catch(e) { err++; console.log(` ✗ ${mfr}: ${String(e.message).slice(0,150)}`); }
+ }
+ await client.end();
+ console.log(`\n=== DONE === repaired ${ok}, errors ${err} | Cost $0`);
+}
+main().catch(e => { console.error(e); process.exit(1); });
diff --git a/DW-Programming/revert-versace-smartart-single.js b/DW-Programming/revert-versace-smartart-single.js
new file mode 100644
index 00000000..6481474a
--- /dev/null
+++ b/DW-Programming/revert-versace-smartart-single.js
@@ -0,0 +1,74 @@
+#!/usr/bin/env node
+/**
+ * Revert the 11 VER- "Smart Art Easy Fototapete" border/mural items to SINGLE roll.
+ * They were incorrectly swept into the double-roll set (generic titles hid that they
+ * are 2"-7"-wide borders). Sets global.v_prods_quantity_order_min/units = 1 and
+ * DELETES the global.packaged="Packaged in Double Rolls" I added, to match the true
+ * single-roll sibling borders (which carry no packaged value).
+ *
+ * Usage: node revert-versace-smartart-single.js [--live]
+ */
+require('dotenv').config({ path: require('os').homedir() + '/Projects/secrets-manager/.env' });
+const { Client } = require('pg');
+const LIVE = process.argv.includes('--live');
+const STORE = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-01';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const DB = process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified';
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+const SKUS = ['VER-47200','VER-47201','VER-47202','VER-47203','VER-47204','VER-47218','VER-47229','VER-47230','VER-47231','VER-47233','VER-47276'];
+
+async function gql(query, variables) {
+ const res = await fetch(`https://${STORE}/admin/api/${API}/graphql.json`, {
+ method: 'POST',
+ headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+ body: JSON.stringify({ query, variables }),
+ });
+ const j = await res.json();
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${JSON.stringify(j).slice(0,200)}`);
+ return j.data;
+}
+
+async function main() {
+ const client = new Client(DB); await client.connect();
+ const { rows } = await client.query(
+ `SELECT id, sku, shopify_id FROM shopify_products WHERE sku = ANY($1)`, [SKUS]);
+ console.log(`Mode: ${LIVE ? '🔴 LIVE' : '🟡 DRY RUN'} | ${rows.length} Smart Art borders → single roll`);
+ if (!LIVE) { rows.forEach(r => console.log(` would single-roll ${r.sku}`)); console.log('Re-run with --live.'); await client.end(); return; }
+
+ let ok=0, err=0;
+ for (const r of rows) {
+ try {
+ // 1) set min/units = 1
+ const setRes = await gql(
+ `mutation($mf:[MetafieldsSetInput!]!){ metafieldsSet(metafields:$mf){ userErrors{ message } } }`,
+ { mf: [
+ { ownerId: r.shopify_id, namespace:'global', key:'v_prods_quantity_order_min', type:'single_line_text_field', value:'1' },
+ { ownerId: r.shopify_id, namespace:'global', key:'v_prods_quantity_order_units', type:'single_line_text_field', value:'1' },
+ ]});
+ const ue = setRes.metafieldsSet.userErrors; if (ue.length) throw new Error(JSON.stringify(ue));
+ // 2) find + delete the packaged metafield
+ const q = await gql(`query($id:ID!){ product(id:$id){ metafield(namespace:"global",key:"packaged"){ id } } }`, { id: r.shopify_id });
+ const mfId = q.product?.metafield?.id;
+ if (mfId) {
+ const del = await gql(`mutation($input:MetafieldDeleteInput!){ metafieldDelete(input:$input){ deletedId userErrors{ message } } }`, { input:{ id: mfId } });
+ const de = del.metafieldDelete.userErrors; if (de.length) throw new Error('del '+JSON.stringify(de));
+ }
+ // 3) mirror: set min/units=1, remove packaged
+ await client.query(`
+ UPDATE shopify_products SET metafields =
+ (coalesce(metafields,'{}'::jsonb) || jsonb_build_object('global',
+ (coalesce(metafields->'global','{}'::jsonb)
+ || jsonb_build_object(
+ 'v_prods_quantity_order_min', jsonb_build_object('type','string','value','1'),
+ 'v_prods_quantity_order_units', jsonb_build_object('type','string','value','1')))
+ - 'packaged'))
+ WHERE id = $1`, [ r.id ]);
+ ok++; console.log(` ✓ ${r.sku}`);
+ } catch(e) { err++; console.log(` ✗ ${r.sku}: ${String(e.message).slice(0,140)}`); }
+ await sleep(550);
+ }
+ await client.end();
+ console.log(`\n=== DONE === single-rolled ${ok}, errors ${err} | Cost $0`);
+}
+main().catch(e => { console.error(e); process.exit(1); });
← f124efc4 auto-save: 2026-07-14T10:00:24 (5 files) — DW-Programming/Im
·
back to Designer Wallcoverings
·
auto-save: 2026-07-14T10:30:32 (5 files) — DW-Programming/Im f9f91dd1 →