← back to Flock Fix Viewer

add-sqft.mjs

59 lines

// Add square footage per single roll + double roll to metafields, computed from each
// product's real width x length. Standing pattern for all roll-sold items. 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 DRY=!process.argv.includes('--apply');

async function gql(q,v={}){const r=await fetch(`https://${SHOP}/admin/api/2024-10/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});return r.json();}
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
const Q=`query($c:String){ products(first:100, query:"(tag:'Flock Velvet' OR title:flock OR handle:flock)", after:$c){
  pageInfo{hasNextPage endCursor}
  edges{node{ id handle status
    variants(first:6){edges{node{title price}}}
    w: metafield(namespace:"global",key:"width"){value}
    l: metafield(namespace:"global",key:"length"){value}
    sr: metafield(namespace:"global",key:"sqft_single_roll"){value}
  }}
}}`;
const SETMF=`mutation($mf:[MetafieldsSetInput!]!){ metafieldsSet(metafields:$mf){ userErrors{field message} } }`;
const num=s=>{const m=String(s||'').match(/([\d.]+)/);return m?parseFloat(m[1]):null;};
const r2=n=>Math.round(n*100)/100;

(async()=>{
  let cur=null,all=[];
  do{const r=await gql(Q,{c:cur});const p=r?.data?.products;if(!p){console.error(JSON.stringify(r));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;
    const priced=n.variants.edges.map(e=>e.node).some(v=>!/sample/i.test(v.title||'')&&parseFloat(v.price)>10);
    return priced && num(n.w?.value) && num(n.l?.value);   // need width + length to compute
  });
  console.log(`priced flock with width+length: ${targets.length}`);
  let done=0;
  for(const n of targets){
    const widthIn=num(n.w?.value);          // 27
    const drYd=num(n.l?.value);              // 5.5 (double roll length)
    const srYd=drYd/2;                       // 2.75
    const sqSR=r2((widthIn/12)*(srYd*3));    // 2.25 * 8.25 = 18.56
    const sqDR=r2((widthIn/12)*(drYd*3));    // 2.25 * 16.5 = 37.13
    const vSR=`${sqSR} Sq Ft`, vDR=`${sqDR} Sq Ft`;
    if(DRY){ if(done<8) console.log(`  ${n.handle.slice(0,40)}  W=${widthIn}" DR=${drYd}yd -> S/R ${vSR} | D/R ${vDR}`); done++; continue; }
    const mf=[
      {ownerId:n.id,namespace:'global',key:'sqft_single_roll',type:'single_line_text_field',value:vSR},
      {ownerId:n.id,namespace:'global',key:'sqft_double_roll',type:'single_line_text_field',value:vDR},
      {ownerId:n.id,namespace:'custom',key:'sqft_single_roll',type:'single_line_text_field',value:vSR},
      {ownerId:n.id,namespace:'custom',key:'sqft_double_roll',type:'single_line_text_field',value:vDR}
    ];
    const r=await gql(SETMF,{mf});
    const errs=r?.data?.metafieldsSet?.userErrors;
    if(errs&&errs.length){console.log('  FAIL',n.handle,JSON.stringify(errs));continue;}
    done++;
    fs.appendFileSync(LEDGER,JSON.stringify({ts:new Date().toISOString(),agent:'main:flock-sqft',ticket:'flock-sqft',
      action:`add sqft S/R ${vSR} + D/R ${vDR} to ${n.handle}`,blast_radius:1,
      undo_cmd:`delete metafields global/custom.sqft_single_roll & sqft_double_roll on ${n.id}`,verify:`sqft metafields present`})+'\n');
    await sleep(220);
  }
  console.log(`${DRY?'DRY':'DONE'} · ${done}`);
})();