← 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
M shopify/scripts/fix-per-yard-fabric-min.js
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 →