[object Object]

← back to Sister Parish Onboarding

sp: check_sp_inventory.js — audit + set all 250 SP variants to qty 2026

aac311d43f079ec6602f61c14010539eeb89a922 · 2026-05-18 09:36:39 -0700 · SteveStudio2

Files touched

Diff

commit aac311d43f079ec6602f61c14010539eeb89a922
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Mon May 18 09:36:39 2026 -0700

    sp: check_sp_inventory.js — audit + set all 250 SP variants to qty 2026
---
 scripts/check_sp_inventory.js | 80 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 80 insertions(+)

diff --git a/scripts/check_sp_inventory.js b/scripts/check_sp_inventory.js
new file mode 100644
index 0000000..f9188d1
--- /dev/null
+++ b/scripts/check_sp_inventory.js
@@ -0,0 +1,80 @@
+#!/usr/bin/env node
+/**
+ * Audit inventory_quantity across every Sister Parish variant on the DW sandbox.
+ * Reports how many variants are at the standing-rule qty (2026) vs. off.
+ * With --fix, sets every off-target variant to 2026.
+ */
+require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
+
+const SHOP = process.env.SHOPIFY_STORE;
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+if (!SHOP || !TOKEN) { console.error('Need SHOPIFY_STORE + SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+
+const BASE = `https://${SHOP}/admin/api/2026-01`;
+const TARGET = 2026;
+const RATE_DELAY_MS = 350;
+const FIX = process.argv.includes('--fix');
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+async function shopify(method, url, body) {
+  const res = await fetch(url.startsWith('http') ? url : `${BASE}${url}`, {
+    method,
+    headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+    body: body ? JSON.stringify(body) : undefined,
+  });
+  const text = await res.text();
+  let json; try { json = JSON.parse(text); } catch { json = { raw: text }; }
+  if (!res.ok) { const e = new Error(`${method} ${url} → ${res.status}: ${text.slice(0,300)}`); e.status = res.status; throw e; }
+  return { json, link: res.headers.get('link') || '' };
+}
+const nextPage = link => {
+  const m = link.split(',').find(s => s.includes('rel="next"'));
+  return m ? m.match(/<([^>]+)>/)[1] : null;
+};
+
+(async () => {
+  const products = [];
+  let url = `${BASE}/products.json?vendor=${encodeURIComponent('Sister Parish')}&limit=250`;
+  while (url) {
+    const { json, link } = await shopify('GET', url);
+    products.push(...json.products);
+    url = nextPage(link);
+    if (url) await sleep(RATE_DELAY_MS);
+  }
+
+  const variants = products.flatMap(p => p.variants.map(v => ({ ...v, _title: p.title })));
+  const onTarget = variants.filter(v => v.inventory_quantity === TARGET);
+  const off = variants.filter(v => v.inventory_quantity !== TARGET);
+  const untracked = variants.filter(v => v.inventory_management !== 'shopify');
+
+  console.log(`Sister Parish: ${products.length} products, ${variants.length} variants`);
+  console.log(`  inventory_quantity == ${TARGET}: ${onTarget.length}`);
+  console.log(`  off-target:                  ${off.length}`);
+  console.log(`  not tracked by Shopify:      ${untracked.length}`);
+  if (off.length && off.length <= 60) {
+    console.log('\nOff-target variants:');
+    for (const v of off) console.log(`  ${v._title} [${v.sku || v.id}] qty=${v.inventory_quantity} tracked=${v.inventory_management || 'none'}`);
+  }
+
+  if (!FIX || !off.length) { if (FIX) console.log('\nNothing to fix.'); return; }
+
+  console.log(`\n--fix: setting ${off.length} variants to ${TARGET}…`);
+  // Token lacks read_locations — derive location_id from an existing inventory level instead.
+  const sampleIid = off[0].inventory_item_id;
+  const lvl = (await shopify('GET', `/inventory_levels.json?inventory_item_ids=${sampleIid}`)).json.inventory_levels[0];
+  const locationId = lvl.location_id;
+  await sleep(RATE_DELAY_MS);
+  let ok = 0, fail = 0;
+  for (let i = 0; i < off.length; i++) {
+    const v = off[i];
+    const stamp = `[${String(i+1).padStart(3,'0')}/${off.length}]`;
+    try {
+      await shopify('POST', '/inventory_levels/set.json', { location_id: locationId, inventory_item_id: v.inventory_item_id, available: TARGET });
+      ok++; console.log(`${stamp} ✓ ${v._title} [${v.sku || v.id}] → ${TARGET}`);
+    } catch (e) {
+      fail++; console.log(`${stamp} ✗ ${v._title} [${v.sku || v.id}]: ${e.message.slice(0,150)}`);
+    }
+    await sleep(RATE_DELAY_MS);
+  }
+  console.log(`\nFixed: ${ok}   Failed: ${fail}`);
+})().catch(e => { console.error('FATAL', e); process.exit(1); });

← a28adca sp: activate_sp.js — flip all 50 draft Sister Parish product  ·  back to Sister Parish Onboarding  ·  sp: private_label.js — dedupe + renumber to DWSH-10001 serie 386a360 →