[object Object]

← back to Dw Yolo Loop

Artmura go-live: inventory=2026 both variants + publish to all 13 DW channels + status=ACTIVE

8f441ef92ab5098dcb2fb9c8485f330fe6bd433b · 2026-06-12 10:24:06 -0700 · Steve Abrams

- inventoryItemUpdate tracked + inventoryActivate + inventorySetQuantities on_hand=2026 @ Ventura Blvd location (5795643504)
- publishablePublish to all 13 publications (Online Store, POS, Google, FB/IG, Houzz, Fabricut, Pinterest, Rakuten, Shop, Inbox, TikTok, Buy Button, AutoPostBlog)
- productUpdate status ACTIVE; idempotent (skips already-active); canary verified (Ardesia Silver: active, 2026, 13 channels)

Files touched

Diff

commit 8f441ef92ab5098dcb2fb9c8485f330fe6bd433b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jun 12 10:24:06 2026 -0700

    Artmura go-live: inventory=2026 both variants + publish to all 13 DW channels + status=ACTIVE
    
    - inventoryItemUpdate tracked + inventoryActivate + inventorySetQuantities on_hand=2026 @ Ventura Blvd location (5795643504)
    - publishablePublish to all 13 publications (Online Store, POS, Google, FB/IG, Houzz, Fabricut, Pinterest, Rakuten, Shop, Inbox, TikTok, Buy Button, AutoPostBlog)
    - productUpdate status ACTIVE; idempotent (skips already-active); canary verified (Ardesia Silver: active, 2026, 13 channels)
---
 scripts/artmura-onboard/go-live-artmura.js | 106 +++++++++++++++++++++++++++++
 1 file changed, 106 insertions(+)

diff --git a/scripts/artmura-onboard/go-live-artmura.js b/scripts/artmura-onboard/go-live-artmura.js
new file mode 100644
index 0000000..033f655
--- /dev/null
+++ b/scripts/artmura-onboard/go-live-artmura.js
@@ -0,0 +1,106 @@
+#!/usr/bin/env node
+/**
+ * Go-live for the 161 Artmura draft products. Per product:
+ *   1. enable inventory tracking on both variants (inventoryItemUpdate tracked:true)
+ *   2. activate at location + set on_hand = 2026 (inventoryActivate + inventorySetQuantities)
+ *   3. publish to all DW sales channels (publishablePublish, 13 publications)
+ *   4. set status = ACTIVE
+ *
+ *   node go-live-artmura.js                 # dry-run summary
+ *   node go-live-artmura.js --apply --limit=1   # canary
+ *   node go-live-artmura.js --apply             # all remaining drafts
+ *
+ * Idempotent-ish: skips products already status=active (re-runnable on failures).
+ */
+const fs = require('fs');
+const { Pool } = require('pg');
+
+const SHOP = process.env.SHOPIFY_STORE || 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const API = '2024-10';
+const EP = `https://${SHOP}/admin/api/${API}/graphql.json`;
+const DB = process.env.DATABASE_URL || 'postgresql:///dw_unified?host=/tmp';
+const APPLY = process.argv.includes('--apply');
+const LIMIT = parseInt((process.argv.find(a => a.startsWith('--limit=')) || '').split('=')[1] || '0', 10);
+
+const LOCATION_ID = 'gid://shopify/Location/5795643504'; // 15442 Ventura Blvd.
+const TARGET_QTY = 2026;
+const PUBLICATIONS = [
+  22208643184, 22497296496, 29646651457, 29739483201, 29776969793, 37904089153,
+  43657658419, 44234276915, 44317474867, 44317507635, 71898464307, 115856375859, 140027723827,
+].map(n => `gid://shopify/Publication/${n}`);
+
+if (!TOKEN) { console.error('FATAL: set SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+const pool = new Pool({ connectionString: DB });
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+async function gql(query, variables = {}) {
+  for (let attempt = 0; attempt < 4; attempt++) {
+    try {
+      const res = await fetch(EP, { method: 'POST',
+        headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+        body: JSON.stringify({ query, variables }) });
+      const json = await res.json();
+      if (json.errors) { if (attempt === 3) throw new Error(JSON.stringify(json.errors)); await sleep(900 * (attempt + 1)); continue; }
+      const rem = json.extensions?.cost?.throttleStatus?.currentlyAvailable;
+      if (rem !== undefined && rem < 250) await sleep(600);
+      return json.data;
+    } catch (e) { if (attempt === 3) throw e; await sleep(900 * (attempt + 1)); }
+  }
+}
+
+const Q_VARIANTS = `query($id:ID!){ product(id:$id){ status variants(first:10){edges{node{id sku inventoryItem{id}}}}}}`;
+const M_TRACK = `mutation($id:ID!){ inventoryItemUpdate(id:$id, input:{tracked:true}){ userErrors{message} } }`;
+const M_ACTIVATE = `mutation($iid:ID!,$loc:ID!){ inventoryActivate(inventoryItemId:$iid, locationId:$loc){ userErrors{message} } }`;
+const M_SETQTY = `mutation($input:InventorySetQuantitiesInput!){ inventorySetQuantities(input:$input){ userErrors{field message code} } }`;
+const M_PUBLISH = `mutation($id:ID!,$pubs:[PublicationInput!]!){ publishablePublish(id:$id, input:$pubs){ userErrors{message} } }`;
+const M_ACTIVE = `mutation($id:ID!){ productUpdate(input:{id:$id, status:ACTIVE}){ product{status} userErrors{message} } }`;
+
+async function goLive(pid) {
+  const gidP = `gid://shopify/Product/${pid}`;
+  const errs = [];
+  const d = await gql(Q_VARIANTS, { id: gidP });
+  if (d.product?.status === 'ACTIVE') return { skipped: true };
+  const items = d.product.variants.edges.map(e => e.node.inventoryItem.id);
+  // 1+2: track, activate, set 2026 per item
+  for (const iid of items) {
+    let r = await gql(M_TRACK, { id: iid }); (r.inventoryItemUpdate?.userErrors || []).forEach(e => errs.push('track:' + e.message));
+    r = await gql(M_ACTIVATE, { iid, loc: LOCATION_ID });
+    const ae = (r.inventoryActivate?.userErrors || []).filter(e => !/already/i.test(e.message));
+    ae.forEach(e => errs.push('activate:' + e.message));
+  }
+  const r2 = await gql(M_SETQTY, { input: { name: 'on_hand', reason: 'correction', ignoreCompareQuantity: true,
+    quantities: items.map(iid => ({ inventoryItemId: iid, locationId: LOCATION_ID, quantity: TARGET_QTY })) } });
+  (r2.inventorySetQuantities?.userErrors || []).forEach(e => errs.push('setqty:' + e.message));
+  // 3: publish to all channels
+  const r3 = await gql(M_PUBLISH, { id: gidP, pubs: PUBLICATIONS.map(p => ({ publicationId: p })) });
+  (r3.publishablePublish?.userErrors || []).forEach(e => errs.push('publish:' + e.message));
+  // 4: activate product
+  const r4 = await gql(M_ACTIVE, { id: gidP });
+  (r4.productUpdate?.userErrors || []).forEach(e => errs.push('active:' + e.message));
+  return { status: r4.productUpdate?.product?.status, errs };
+}
+
+(async () => {
+  const { rows } = await pool.query(
+    `SELECT handle, dw_sku, shopify_product_id FROM newwall_catalog WHERE vendor_name='Artmura' AND shopify_product_id IS NOT NULL AND shopify_product_id<>'' ORDER BY dw_sku`);
+  let todo = rows;
+  if (LIMIT > 0) todo = todo.slice(0, LIMIT);
+  console.log(`go-live: ${todo.length} products · location ${LOCATION_ID} · qty ${TARGET_QTY} · channels ${PUBLICATIONS.length} · ${APPLY ? 'APPLY' : 'DRY-RUN'}`);
+  if (!APPLY) { console.log('first 3:', todo.slice(0, 3).map(r => r.dw_sku).join(', ')); console.log('DRY-RUN. --apply to execute.'); await pool.end(); return; }
+
+  let ok = 0, skip = 0, withErr = 0;
+  for (const r of todo) {
+    try {
+      const res = await goLive(r.shopify_product_id);
+      if (res.skipped) { skip++; }
+      else if (res.errs && res.errs.length) { withErr++; console.error(`  ⚠ ${r.dw_sku} ${res.status||'?'}: ${res.errs.slice(0,3).join(' | ')}`); }
+      else { ok++; }
+      if (!res.skipped && res.status === 'ACTIVE') await pool.query(`UPDATE newwall_catalog SET on_shopify=true, updated_at=now() WHERE handle=$1`, [r.handle]);
+      if ((ok + skip + withErr) % 20 === 0) console.log(`  ...${ok + skip + withErr}/${todo.length} (active=${ok} skip=${skip} err=${withErr})`);
+      await sleep(350);
+    } catch (e) { withErr++; console.error(`  ERR ${r.dw_sku}: ${e.message.slice(0, 160)}`); await sleep(600); }
+  }
+  console.log(`\nDONE. active=${ok} skipped=${skip} with-errors=${withErr} of ${todo.length}`);
+  await pool.end();
+})().catch(e => { console.error(e); process.exit(1); });

← 52f8e55 Artmura titles: insert 'Wallcovering' → '<name> Wallcovering  ·  back to Dw Yolo Loop  ·  Vendor-landing template: config-driven + links to live Shopi 8489a63 →