← back to Designer Wallcoverings
Designtex UOM followup: 3-SKU flip driver + verify (drift trickle after 77-fix; source already fixed in vendors.js 974c7dd0)
18fa34c8eb4fe42971963432f13f20dc84af423a · 2026-06-26 12:35:49 -0700 · Steve
Files touched
A audits/designtex-uom-fix/flip-uom-3-followup.mjsA audits/designtex-uom-fix/verify-uom-3-followup.mjs
Diff
commit 18fa34c8eb4fe42971963432f13f20dc84af423a
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Jun 26 12:35:49 2026 -0700
Designtex UOM followup: 3-SKU flip driver + verify (drift trickle after 77-fix; source already fixed in vendors.js 974c7dd0)
---
audits/designtex-uom-fix/flip-uom-3-followup.mjs | 61 ++++++++++++++++++++++
audits/designtex-uom-fix/verify-uom-3-followup.mjs | 20 +++++++
2 files changed, 81 insertions(+)
diff --git a/audits/designtex-uom-fix/flip-uom-3-followup.mjs b/audits/designtex-uom-fix/flip-uom-3-followup.mjs
new file mode 100644
index 00000000..94e4eeb8
--- /dev/null
+++ b/audits/designtex-uom-fix/flip-uom-3-followup.mjs
@@ -0,0 +1,61 @@
+#!/usr/bin/env node
+// FOLLOW-UP to the 77-SKU Designtex UOM fix. Flips the 3 SKUs that drifted in AFTER
+// the 2026-06-23 cleanup + 2026-06-26 line-wide repair (created 10:40-10:41 AM PDT,
+// just before the vendors.js soldBy:'Yard' source fix committed at 10:45). They missed
+// both prior repairs. Same relabel-only logic as flip-uom-live-77.mjs:
+// global.unit_of_measure 'Priced Per Single Roll' -> 'YARD'
+// tag 'Priced Per Single Roll' -> 'Priced Per Yard'
+// variant title containing 'Roll' -> 'Sold Per Yard' (skip Sample; these already read
+// 'Sold Per Yard' so the variant step is a no-op, kept for safety/idempotence)
+// Price UNTOUCHED. Designtex-only (DWDX). GATED — dry-run by default, --execute to write.
+// $0 (Shopify Admin API, no metered AI).
+import fs from 'fs'; import os from 'os'; import path from 'path';
+const EXECUTE = process.argv.includes('--execute');
+const env = fs.readFileSync(path.join(os.homedir(),'Projects/secrets-manager/.env'),'utf8');
+const tok = env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)[1].trim();
+const DOMAIN='designer-laboratory-sandbox.myshopify.com', VER='2024-10';
+const HERE = path.dirname(new URL(import.meta.url).pathname);
+const gql=(q,v)=>fetch(`https://${DOMAIN}/admin/api/${VER}/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':tok,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})}).then(r=>r.json());
+
+// The 3 drifted SKUs (DWDX-only). Hard-coded gids — verified Designtex, sequential newest.
+const rows = [
+ { sku:'DWDX-220366', status:'ACTIVE', id:'gid://shopify/Product/7869010870323' },
+ { sku:'DWDX-220367', status:'DRAFT', id:'gid://shopify/Product/7869010935859' },
+ { sku:'DWDX-220368', status:'DRAFT', id:'gid://shopify/Product/7869010968627' },
+];
+// Hard guard: refuse anything that isn't a DWDX (Designtex) SKU.
+if (rows.some(r => !/^DWDX-/.test(r.sku))) { console.error('ABORT: non-DWDX SKU in set'); process.exit(1); }
+
+console.log(`Mode: ${EXECUTE?'EXECUTE':'DRY RUN'} | rows: ${rows.length} (Designtex DWDX only)`);
+if (!EXECUTE) {
+ rows.forEach(r=>console.log(` ${r.sku} [${r.status}] uom 'Priced Per Single Roll' -> 'YARD', tag fix, variant ensure 'Sold Per Yard'`));
+ console.log('Re-run with --execute to apply.'); process.exit(0);
+}
+const mfMut=`mutation($mf:[MetafieldsSetInput!]!){metafieldsSet(metafields:$mf){userErrors{field message}}}`;
+const tagAdd=`mutation($id:ID!,$tags:[String!]!){tagsAdd(id:$id,tags:$tags){userErrors{message}}}`;
+const tagRm=`mutation($id:ID!,$tags:[String!]!){tagsRemove(id:$id,tags:$tags){userErrors{message}}}`;
+const varQ=`query($id:ID!){product(id:$id){variants(first:5){edges{node{id title}}}}}`;
+const varMut=`mutation($pid:ID!,$variants:[ProductVariantsBulkInput!]!){productVariantsBulkUpdate(productId:$pid,variants:$variants){userErrors{message}}}`;
+const results=[];
+for (const r of rows) {
+ const id=r.id, errs=[];
+ const m=await gql(mfMut,{mf:[{ownerId:id,namespace:'global',key:'unit_of_measure',type:'single_line_text_field',value:'YARD'}]});
+ (m.data?.metafieldsSet?.userErrors||[]).forEach(e=>errs.push('mf:'+e.message));
+ const tr=await gql(tagRm,{id,tags:['Priced Per Single Roll']});
+ (tr.data?.tagsRemove?.userErrors||[]).forEach(e=>errs.push('tagRm:'+e.message));
+ const ta=await gql(tagAdd,{id,tags:['Priced Per Yard']});
+ (ta.data?.tagsAdd?.userErrors||[]).forEach(e=>errs.push('tagAdd:'+e.message));
+ const vq=await gql(varQ,{id});
+ const variants=(vq.data?.product?.variants?.edges||[]).map(e=>e.node)
+ .filter(v=>!/Sample/i.test(v.title) && /Roll/i.test(v.title));
+ if (variants.length) {
+ const vm=await gql(varMut,{pid:id,variants:variants.map(v=>({id:v.id,optionValues:[{optionName:'Title',name:'Sold Per Yard'}]}))});
+ (vm.data?.productVariantsBulkUpdate?.userErrors||[]).forEach(e=>errs.push('var:'+e.message));
+ }
+ results.push({sku:r.sku,ok:errs.length===0,errs});
+ if (errs.length) console.error('ERR',r.sku,errs.join('; '));
+ await new Promise(x=>setTimeout(x,400));
+}
+const failed=results.filter(r=>!r.ok);
+console.log(`DONE. ${results.length-failed.length} ok, ${failed.length} failed.`);
+fs.writeFileSync(path.join(HERE,'flip-uom-3-followup-results.json'),JSON.stringify(results,null,2));
diff --git a/audits/designtex-uom-fix/verify-uom-3-followup.mjs b/audits/designtex-uom-fix/verify-uom-3-followup.mjs
new file mode 100644
index 00000000..858f8a8c
--- /dev/null
+++ b/audits/designtex-uom-fix/verify-uom-3-followup.mjs
@@ -0,0 +1,20 @@
+#!/usr/bin/env node
+// Read-only verify for the 3 follow-up Designtex SKUs. Expect uom_drift: 0 after the flip.
+import fs from 'fs'; import os from 'os'; import path from 'path';
+const env = fs.readFileSync(path.join(os.homedir(),'Projects/secrets-manager/.env'),'utf8');
+const tok = env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)[1].trim();
+const DOMAIN='designer-laboratory-sandbox.myshopify.com', VER='2024-10';
+const gql=(q,v)=>fetch(`https://${DOMAIN}/admin/api/${VER}/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':tok,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})}).then(r=>r.json());
+const gids=['gid://shopify/Product/7869010870323','gid://shopify/Product/7869010935859','gid://shopify/Product/7869010968627'];
+const q=`query($id:ID!){product(id:$id){status uom:metafield(namespace:"global",key:"unit_of_measure"){value} variants(first:5){edges{node{title sku}}} tags}}`;
+let yard=0,drift=0,still=[];
+for(const id of gids){
+ const r=await gql(q,{id}); const p=r.data?.product;
+ const u=(p?.uom?.value||'').toUpperCase();
+ const ok = u==='YARD' || u.includes('YARD');
+ if(ok)yard++; else {drift++; still.push({id,uom:p?.uom?.value});}
+ const prod=p.variants.edges.find(e=>!/sample/i.test(e.node.title));
+ console.log(`${prod?.node.sku} | ${p.status} | uom='${p.uom?.value}' | rollTag=${p.tags.includes('Priced Per Single Roll')}`);
+}
+console.log(`\nchecked: ${gids.length} | uom_YARD: ${yard} | uom_drift: ${drift} | stillDrift: ${JSON.stringify(still)}`);
+process.exit(drift?1:0);
← 97406872 auto-save: 2026-06-26T12:33:24 (3 files) — pending-approval/
·
back to Designer Wallcoverings
·
snapshot before git gc 08e99376 →