← back to Flock Fix Viewer

priceup-windsor.mjs

65 lines

// Price up the 20.5" WINDSOR flock sample-shells into 2-variant products, modeled on the
// active Bella's Windsor (FLOCK-1011): roll 20.5"x5.5yd @ $190.99 + sample $4.25. Reversible + ledgered.
import fs from 'fs'; import os from 'os'; import path from 'path';
const SHOP='designer-laboratory-sandbox.myshopify.com';
const TOKEN=fs.readFileSync(new URL('./.token',import.meta.url),'utf8').trim();
const LEDGER=path.join(os.homedir(),'.claude/yolo-queue/executed-reversible/ledger.jsonl');
const SNAPDIR=new URL('./windsor-snapshots/',import.meta.url); try{fs.mkdirSync(SNAPDIR,{recursive:true});}catch(e){}
const DRY=!process.argv.includes('--apply');
const API=`https://${SHOP}/admin/api/2024-10`, H={'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'};
const gql=async(q,v={})=>(await (await fetch(`${API}/graphql.json`,{method:'POST',headers:H,body:JSON.stringify({query:q,variables:v})})).json());
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
const ROLL_OPT='Sold per single roll (20.5" x 5.5 yards)', ROLL_PRICE='190.99', SAMPLE_PRICE='4.25';

const SET=`mutation($input:ProductSetInput!){ productSet(synchronous:true, input:$input){ product{id handle variants(first:6){edges{node{title price sku}}}} userErrors{field message} } }`;
function buildInput(id, rollSku, sampleSku){
  return { id, productType:'Wallcovering',
    productOptions:[{name:'Size', values:[{name:ROLL_OPT},{name:'Sample'}]}],
    variants:[
      {optionValues:[{optionName:'Size',name:ROLL_OPT}], price:ROLL_PRICE, sku:rollSku, inventoryPolicy:'CONTINUE'},
      {optionValues:[{optionName:'Size',name:'Sample'}], price:SAMPLE_PRICE, sku:sampleSku, inventoryPolicy:'CONTINUE'}
    ],
    metafields:[
      {namespace:'global',key:'v_prods_quantity_order_min',type:'single_line_text_field',value:'2'},
      {namespace:'global',key:'v_prods_quantity_order_units',type:'single_line_text_field',value:'2'},
      {namespace:'global',key:'unit_of_measure',type:'single_line_text_field',value:'Priced Per Single Roll. Ships untrimmed (30" untrimmed / 20.5" trimmed width).'},
      {namespace:'global',key:'width',type:'single_line_text_field',value:'20.5 Inches Wide'},
      {namespace:'custom',key:'width',type:'single_line_text_field',value:'20.5 Inches Wide'},
      {namespace:'global',key:'length',type:'single_line_text_field',value:'5.5 Yards'},
      {namespace:'global',key:'sqft_single_roll',type:'single_line_text_field',value:'14.09 Sq Ft'},
      {namespace:'global',key:'sqft_double_roll',type:'single_line_text_field',value:'28.19 Sq Ft'},
      {namespace:'custom',key:'sqft_single_roll',type:'single_line_text_field',value:'14.09 Sq Ft'},
      {namespace:'custom',key:'sqft_double_roll',type:'single_line_text_field',value:'28.19 Sq Ft'},
      {namespace:'global',key:'print_type',type:'single_line_text_field',value:'Flock'},
      {namespace:'custom',key:'print_type',type:'single_line_text_field',value:'Flock'}
    ]
  };
}
const maxRoll=n=>n.variants.edges.map(e=>e.node).filter(v=>!/sample/i.test(v.title||'')).reduce((m,v)=>Math.max(m,parseFloat(v.price)||0),0);

(async()=>{
  let cur=null,all=[];
  const Q=`query($c:String){ products(first:100, query:"windsor flock", after:$c){ pageInfo{hasNextPage endCursor} edges{node{ id handle title status variants(first:8){edges{node{title price sku}}} }}}}`;
  do{const r=await gql(Q,{c:cur});const p=r?.data?.products;if(!p)break;all=all.concat(p.edges);cur=p.pageInfo.hasNextPage?p.pageInfo.endCursor:null;}while(cur);
  const targets=all.map(e=>e.node).filter(n=>{
    if(n.status==='ARCHIVED') return false;
    if(!/windsor/i.test(n.handle+' '+n.title)) return false;
    return maxRoll(n)<=10;   // sample-only shells only
  });
  console.log(`Windsor sample-only shells: ${targets.length}`);
  const list = DRY ? targets : targets;
  for(const n of list){
    const base=(n.variants.edges.map(e=>e.node).find(v=>v.sku)?.sku||n.handle).replace(/-sample$/i,'').replace(/-Sample$/,'');
    if(DRY){ console.log('  would price up',n.status,n.handle.slice(0,46),'-> roll',base,'@ $190.99'); continue; }
    fs.writeFileSync(new URL(n.handle.replace(/[^a-z0-9-]/gi,'_')+'.json',SNAPDIR), JSON.stringify(n,null,2));
    const r=await gql(SET,{input:buildInput(n.id, base, base+'-Sample')});
    const errs=r?.data?.productSet?.userErrors;
    if(errs&&errs.length){console.log('  FAIL',n.handle,JSON.stringify(errs));continue;}
    const vs=r.data.productSet.product.variants.edges.map(e=>e.node);
    console.log('  OK',n.handle.slice(0,46),'->',vs.map(v=>v.title.slice(0,20)+'=$'+v.price).join(' | '));
    fs.appendFileSync(LEDGER,JSON.stringify({ts:new Date().toISOString(),agent:'main:priceup-windsor',ticket:'flock-windsor-205',action:`price up ${n.handle} to 20.5" roll $190.99 + sample`,blast_radius:1,undo_cmd:`restore windsor-snapshots/${n.handle}.json`,verify:`2 variants roll $190.99 + sample $4.25, width 20.5"`})+'\n');
    await sleep(400);
  }
  console.log(`${DRY?'DRY':'DONE'} · ${targets.length} Windsor shells`);
})();