← back to Jeffrey Stevens Margin Fix
221 verified below-cost reprice (UOM-aware: 200 YD->Sold Per Yard+min5, 21 price-only) + rollback; Steve ungated
f749084dab1b39a5e08b3f444a9c47ab1309b35b · 2026-09-09 16:48:21 -0700 · Steve
Files touched
A reprice-221-rollback.mjsA reprice-221.mjs
Diff
commit f749084dab1b39a5e08b3f444a9c47ab1309b35b
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Sep 9 16:48:21 2026 -0700
221 verified below-cost reprice (UOM-aware: 200 YD->Sold Per Yard+min5, 21 price-only) + rollback; Steve ungated
---
reprice-221-rollback.mjs | 12 ++++++++++
reprice-221.mjs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 71 insertions(+)
diff --git a/reprice-221-rollback.mjs b/reprice-221-rollback.mjs
new file mode 100644
index 0000000..0364ff8
--- /dev/null
+++ b/reprice-221-rollback.mjs
@@ -0,0 +1,12 @@
+import { readFileSync } from 'node:fs';
+const TOKEN=readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8').split('\n').find(l=>l.startsWith('SHOPIFY_ADMIN_TOKEN=')).split('=')[1].trim();
+const SHOP='https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/graphql.json';
+const DIR=new URL('.',import.meta.url).pathname;
+const gql=(q,v)=>fetch(SHOP,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})}).then(r=>r.json());
+const rows=readFileSync(DIR+'data/reprice221-restore.jsonl','utf8').trim().split('\n').map(l=>JSON.parse(l));
+let ok=0;
+for(const r of rows){const pid=`gid://shopify/Product/${r.pid}`;
+ await gql(`mutation($m:[MetafieldsSetInput!]!){metafieldsSet(metafields:$m){userErrors{message}}}`,{m:[{ownerId:pid,namespace:'global',key:'unit_of_measure',type:'single_line_text_field',value:r.old.uom||''}]});
+ await gql(`mutation($pid:ID!,$v:[ProductVariantsBulkInput!]!){productVariantsBulkUpdate(productId:$pid,variants:$v){userErrors{message}}}`,{pid,v:[{id:r.sellVid,price:r.old.price,optionValues:[{optionName:'Size',name:r.old.title}]}]});
+ console.log('reverted',r.pid,'->',r.old.price);ok++;}
+console.log('ROLLBACK done',ok);
diff --git a/reprice-221.mjs b/reprice-221.mjs
new file mode 100644
index 0000000..a5f1991
--- /dev/null
+++ b/reprice-221.mjs
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+// Reprice the 221 verified-below-cost JS products (retail = verified_cost/0.65/0.85).
+// UOM-aware (master UOM is authoritative — avoids width-parse trap):
+// YD -> Sold Per Yard + min5/step1 (product+variant) + variant option 'Sold Per Yard' + reprice + sellable pos1 (Steve: "52/54 must be per yard")
+// EA/BDR/other -> price-only reprice, keep current unit.
+// DRY-RUN default; --apply writes. Reversible: data/reprice221-restore.jsonl. Idempotent.
+import { readFileSync, appendFileSync } from 'node:fs';
+const APPLY=process.argv.includes('--apply');
+const TOKEN=readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8').split('\n').find(l=>l.startsWith('SHOPIFY_ADMIN_TOKEN=')).split('=')[1].trim();
+const SHOP='https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/graphql.json';
+const T='single_line_text_field', ND='number_decimal';
+const DIR=new URL('.',import.meta.url).pathname;
+const rows=JSON.parse(readFileSync(DIR+'data/js882-cost-manifest.json')).filter(r=>r.verified_cost>0);
+const REST=DIR+'data/reprice221-restore.jsonl';
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+async function gql(q,v){for(let a=0;a<4;a++){try{const r=await fetch(SHOP,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});const j=await r.json();if(j.errors&&/throttl/i.test(JSON.stringify(j.errors))){await sleep(2000);continue;}return j;}catch(e){await sleep(1500);}}throw new Error('gql');}
+const Q=`query($id:ID!){product(id:$id){uomMf:metafield(namespace:"global",key:"unit_of_measure"){value} variants(first:15){edges{node{id title price position sku}}}}}`;
+let ok=0,skip=0,fail=0,yd=0,pon=0;
+for(const t of rows){
+ const isYD=String(t.m_uom).toUpperCase()==='YD';
+ const pid=`gid://shopify/Product/${t.product_id}`;
+ const r=await gql(Q,{id:pid}); const p=r.data&&r.data.product;
+ if(!p){console.log(' MISS',t.sell_sku);fail++;continue;}
+ const vs=p.variants.edges.map(e=>e.node);
+ const sample=vs.find(v=>/sample/i.test(v.title)||v.price==='4.25'||/-sample$/i.test(v.sku||''));
+ const sell=vs.find(v=>v!==sample)||vs[0];
+ const newp=String(t.retail); const curUom=p.uomMf?.value||'';
+ const targetUom=isYD?'Sold Per Yard':curUom;
+ const already = sell.price===newp && (!isYD || (curUom==='Sold Per Yard' && sell.position===1));
+ if(already){console.log(' = already',t.sell_sku);skip++;continue;}
+ const tag=isYD?'[YD->per-yard]':'[price-only '+t.m_uom+']';
+ if(!APPLY){console.log(` DRY ${t.sell_sku} ${tag} $${sell.price}->$${newp} (cost $${t.verified_cost})`);ok++;isYD?yd++:pon++;continue;}
+ appendFileSync(REST,JSON.stringify({pid:t.product_id,sellVid:sell.id,sampleVid:sample?.id,old:{price:sell.price,uom:curUom,pos:sell.position,title:sell.title}})+'\n');
+ if(isYD){
+ const mfs=[
+ {ownerId:pid,namespace:'global',key:'unit_of_measure',type:T,value:'Sold Per Yard'},
+ {ownerId:pid,namespace:'global',key:'v_prods_quantity_order_min',type:T,value:'5'},
+ {ownerId:pid,namespace:'global',key:'v_prods_quantity_order_units',type:T,value:'1'},
+ {ownerId:pid,namespace:'custom',key:'wholesale_price',type:ND,value:t.verified_cost.toFixed(2)},
+ {ownerId:pid,namespace:'custom',key:'us_msrp',type:ND,value:(t.verified_cost*2).toFixed(2)},
+ {ownerId:sell.id,namespace:'custom',key:'v_prod_quantity_order_min',type:T,value:'5'},
+ {ownerId:sell.id,namespace:'custom',key:'v_prods_quantity_order_units',type:T,value:'1'},
+ ];
+ if(sample) mfs.push({ownerId:sample.id,namespace:'custom',key:'v_prod_quantity_order_min',type:T,value:'1'});
+ const m=await gql(`mutation($m:[MetafieldsSetInput!]!){metafieldsSet(metafields:$m){userErrors{message}}}`,{m:mfs});
+ if(m.data.metafieldsSet.userErrors.length){console.log(' MF-ERR',t.sell_sku,JSON.stringify(m.data.metafieldsSet.userErrors));fail++;continue;}
+ const vu=await gql(`mutation($pid:ID!,$v:[ProductVariantsBulkInput!]!){productVariantsBulkUpdate(productId:$pid,variants:$v){userErrors{message}}}`,
+ {pid,v:[{id:sell.id,price:newp,optionValues:[{optionName:'Size',name:'Sold Per Yard'}]}]});
+ if(vu.data.productVariantsBulkUpdate.userErrors.length){console.log(' VAR-ERR',t.sell_sku,JSON.stringify(vu.data.productVariantsBulkUpdate.userErrors));fail++;continue;}
+ if(sample && sell.position!==1) await gql(`mutation($pid:ID!,$m:[ProductVariantPositionInput!]!){productVariantsBulkReorder(productId:$pid,positions:$m){userErrors{message}}}`,{pid,m:[{id:sell.id,position:1},{id:sample.id,position:2}]});
+ yd++;
+ } else {
+ const vu=await gql(`mutation($pid:ID!,$v:[ProductVariantsBulkInput!]!){productVariantsBulkUpdate(productId:$pid,variants:$v){userErrors{message}}}`,{pid,v:[{id:sell.id,price:newp}]});
+ if(vu.data.productVariantsBulkUpdate.userErrors.length){console.log(' VAR-ERR',t.sell_sku,JSON.stringify(vu.data.productVariantsBulkUpdate.userErrors));fail++;continue;}
+ pon++;
+ }
+ console.log(` OK ${t.sell_sku} ${tag} $${sell.price}->$${newp}`);ok++;await sleep(400);
+}
+console.log(`\n${APPLY?'APPLIED':'DRY-RUN'} — ok=${ok} (yd=${yd} price-only=${pon}) skip=${skip} fail=${fail} of ${rows.length}`);
← a6a3bf8 auto-data-snapshot: 2026-09-09T16:44:54 (2 data files) — dat
·
back to Jeffrey Stevens Margin Fix
·
221 below-cost reprice APPLIED (200 per-yard + 21 price-only 1fa3bd8 →