[object Object]

← back to Designer Wallcoverings

Fix 2: drive per-yard-fabric min from Kravet master sheet (137->1, leave genuine 6@2 + 1@2.77)

6e27c66c12512e6e98f44e41ec246e24d4e8ca31 · 2026-07-09 18:31:30 -0700 · Steve

Files touched

Diff

commit 6e27c66c12512e6e98f44e41ec246e24d4e8ca31
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jul 9 18:31:30 2026 -0700

    Fix 2: drive per-yard-fabric min from Kravet master sheet (137->1, leave genuine 6@2 + 1@2.77)
---
 shopify/scripts/fix-per-yard-fabric-min.js | 71 +++++++++++++++---------------
 1 file changed, 35 insertions(+), 36 deletions(-)

diff --git a/shopify/scripts/fix-per-yard-fabric-min.js b/shopify/scripts/fix-per-yard-fabric-min.js
index f38aab7a..275110fb 100644
--- a/shopify/scripts/fix-per-yard-fabric-min.js
+++ b/shopify/scripts/fix-per-yard-fabric-min.js
@@ -2,28 +2,29 @@
 /**
  * fix-per-yard-fabric-min.js  (2026-07-09)  owner: vp-dw-commerce  — GATED, dry-run default
  *
- * Fix 2 (SECONDARY, MOQ decision required) for the roll-on-fabric issue.
- * The ~144 per-yard Kravet-family fabrics carry a 2-yard minimum enforced by the
- * metafield `global.v_prods_quantity_order_min = 2` (the field the MinMax/theme buy-box
- * reads; UFAC=2 is its 1:1 proxy in the mirror). This sets it to 1 so the fabrics can be
- * ordered by the single yard.
+ * Fix 2 for the roll-on-fabric issue — MASTER-SHEET DRIVEN (Steve: "look in the kravet
+ * master price sheets"). The ~144 per-yard Kravet fabrics carry a wrong 2-yard minimum
+ * (Shopify metafield global.v_prods_quantity_order_min=2, a UFAC=2 import artifact). The
+ * authoritative "Minimum order qty" comes from the Kravet master export — for these SKUs
+ * it is 1/blank for ~137 and genuinely 2 (or 2.77) for ~7.
  *
- * ONLY RUN if Steve decides these fabrics should NOT have a 2-yard minimum. If a 2-yard
- * cut minimum is intended, DO NOT run — Fix 1 (theme guard) already removes the wrong
- * "2 roll increments" LABEL, leaving a correct "$X / yard, qty 2 (2-yard min)".
+ * This sets v_prods_quantity_order_min to the MASTER's Minimum order qty PER SKU:
+ *   - target_min == 1  -> corrects the artifact (137 SKUs)         [WRITES]
+ *   - target_min != 1  -> genuine master minimum (6 @2, 1 @2.77)   [LEFT UNTOUCHED, flagged]
  *
- * Worklist = live query: active, Unit of measure ~ yard, UFAC=2.
- * Writes Shopify metafield global.v_prods_quantity_order_min="1" (+ leaves units=1).
- * The dw_unified mirror re-syncs on its normal cadence.
+ * Worklist file (dw_sku, mfr_sku, master_min_raw, target_min, source) is produced by the
+ * master-join step and lives at:
+ *   ~/.claude/yolo-queue/pending-approval/roll-on-fabric-2026-07-09.fix2-master-min.tsv
  *
  *   node fix-per-yard-fabric-min.js               # DRY-RUN (prints, writes nothing)
- *   node fix-per-yard-fabric-min.js --apply       # LIVE metafield write  (GATED)
+ *   node fix-per-yard-fabric-min.js --apply       # LIVE metafield write (GATED)
  *   node fix-per-yard-fabric-min.js --apply --limit 5
  */
 const https = require('https'); const fs = require('fs'); const os = require('os');
 const { execFileSync } = require('child_process');
 const APPLY = process.argv.includes('--apply');
 const LIMIT = (() => { const i = process.argv.indexOf('--limit'); return i >= 0 ? parseInt(process.argv[i + 1], 10) : Infinity; })();
+const TSV = os.homedir() + '/.claude/yolo-queue/pending-approval/roll-on-fabric-2026-07-09.fix2-master-min.tsv';
 const env = fs.readFileSync(os.homedir() + '/Projects/secrets-manager/.env', 'utf8');
 const TOKEN = (env.match(/^SHOPIFY_ADMIN_TOKEN=(.*)$/m) || [])[1].replace(/['"]/g, '').trim();
 const STORE = 'designer-laboratory-sandbox.myshopify.com', API = '2024-10';
@@ -38,38 +39,36 @@ function gql(query, variables) {
     req.on('error', reject); req.write(body); req.end();
   });
 }
-
-function worklist() {
-  // pull the 144 shopify_ids from the local mirror (UFAC=2 proxy)
-  const sql = `SELECT shopify_id FROM shopify_products
-     WHERE status ILIKE 'active'
-       AND coalesce(metafields->'global'->'Unit of measure'->>'value','') ~* 'yard'
-       AND metafields->'global'->'UFAC'->>'value'='2'
-     ORDER BY dw_sku`;
-  const out = execFileSync('psql', ['-tAc', sql],
+// dw_sku -> gid (from the mirror)
+function gidFor(dwList) {
+  const inlist = dwList.map(s => `'${s}'`).join(',');
+  const out = execFileSync('psql', ['-tAF\t', '-c',
+    `SELECT dw_sku, shopify_id FROM shopify_products WHERE dw_sku IN (${inlist})`],
     { env: { ...process.env, PGDATABASE: 'dw_unified', PGHOST: '/tmp', PGUSER: 'stevestudio2' }, encoding: 'utf8' });
-  return out.split('\n').map(s => s.trim()).filter(Boolean).slice(0, LIMIT);
+  const m = {}; out.split('\n').filter(Boolean).forEach(l => { const [d, g] = l.split('\t'); m[d] = g; }); return m;
 }
 
 (async () => {
-  const ids = worklist();
-  console.log(`${APPLY ? 'APPLY' : 'DRY-RUN'} — ${ids.length} per-yard fabrics -> v_prods_quantity_order_min = 1`);
+  const rows = fs.readFileSync(TSV, 'utf8').trim().split('\n').slice(1)
+    .map(l => { const [dw_sku, mfr, raw, target, source] = l.split('\t'); return { dw_sku, mfr, raw, target, source }; });
+  const toFix = rows.filter(r => r.target === '1').slice(0, LIMIT);
+  const leave = rows.filter(r => r.target !== '1');
+  console.log(`${APPLY ? 'APPLY' : 'DRY-RUN'} — master-driven. ${toFix.length} SKUs -> min 1 (artifact). LEFT untouched (genuine master min): ${leave.length} [${leave.map(r => r.dw_sku + '=' + r.target).join(', ')}]`);
+  const gids = gidFor(toFix.map(r => r.dw_sku));
   let done = 0, skip = 0, err = 0;
-  for (const gid of ids) {
-    // read current value first (idempotent / audit)
-    const r = await gql(`query($id:ID!){ product(id:$id){ handle
-      cur: metafield(namespace:"global", key:"v_prods_quantity_order_min"){ id value } } }`, { id: gid });
-    const p = r.data && r.data.product; if (!p) { err++; console.log(`  ERR no-product ${gid}`); continue; }
-    const cur = p.cur && p.cur.value;
-    if (cur === '1') { skip++; continue; }
-    if (!APPLY) { console.log(`  would set ${p.handle}: ${cur == null ? '(unset)' : cur} -> 1`); done++; continue; }
+  for (const r of toFix) {
+    const gid = gids[r.dw_sku]; if (!gid) { err++; console.log(`  ERR no-gid ${r.dw_sku}`); continue; }
+    const cur = await gql(`query($id:ID!){ product(id:$id){ mf: metafield(namespace:"global", key:"v_prods_quantity_order_min"){ value } } }`, { id: gid });
+    const v = cur.data && cur.data.product && cur.data.product.mf && cur.data.product.mf.value;
+    if (v === '1') { skip++; continue; }
+    if (!APPLY) { console.log(`  would set ${r.dw_sku}: ${v == null ? '(unset)' : v} -> 1  (master ${r.source})`); done++; continue; }
     const w = await gql(`mutation($mf:[MetafieldsSetInput!]!){ metafieldsSet(metafields:$mf){ userErrors{ field message } } }`,
       { mf: [{ ownerId: gid, namespace: 'global', key: 'v_prods_quantity_order_min', type: 'single_line_text_field', value: '1' }] });
     const ue = w.data && w.data.metafieldsSet && w.data.metafieldsSet.userErrors;
-    if (ue && ue.length) { err++; console.log(`  ERR ${p.handle}: ${JSON.stringify(ue)}`); }
-    else { done++; console.log(`  set ${p.handle}: ${cur == null ? '(unset)' : cur} -> 1`); }
+    if (ue && ue.length) { err++; console.log(`  ERR ${r.dw_sku}: ${JSON.stringify(ue)}`); }
+    else { done++; console.log(`  set ${r.dw_sku}: ${v == null ? '(unset)' : v} -> 1`); }
     await sleep(250);
   }
-  console.log(`\n${APPLY ? 'applied' : 'would-change'}=${done}  already-1=${skip}  errors=${err}`);
-  if (!APPLY) console.log('Re-run with --apply (GATED) to write. Only if a 2-yard minimum is NOT intended.');
+  console.log(`\n${APPLY ? 'applied' : 'would-change'}=${done}  already-1=${skip}  errors=${err}  left-genuine=${leave.length}`);
+  if (!APPLY) console.log('Re-run with --apply (GATED) to write.');
 })();

← 8414f280 Add Fix 2 (gated, dry-run): set v_prods_quantity_order_min=1  ·  back to Designer Wallcoverings  ·  auto-save: 2026-07-09T18:41:42 (5 files) — pending-approval/ c2c68ac6 →