← back to Designer Wallcoverings
audits/designtex-uom-fix/flip-uom-3-followup.mjs
62 lines
#!/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));