← back to Dw Yolo Loop

scripts/kravet-backfill-mfrsku.js

43 lines

#!/usr/bin/env node
/* Backfill the custom.manufacturer_sku metafield on Kravet-family products that are missing it,
   by mapping the product's variant SKU (DW sku, e.g. DWKK-130259) → mfr_sku via kravet_catalog.
   INTERNAL metadata write only (metafields are NOT customer-facing; no price/visibility change).
   Reads /tmp/kravet_2026_gaps.csv (the coverage-audit gap list) rows where mfr_sku='(no mfr_sku)'.
   DRY RUN unless --apply. Needs /tmp/dwsku_to_mfr.json (dump from kravet_catalog). */
const fs = require('fs');
const STORE = 'designer-laboratory-sandbox.myshopify.com';
const TOKEN = process.env.T;
const APPLY = process.argv.includes('--apply');
const sleep = ms => new Promise(r => setTimeout(r, ms));

const MAP = JSON.parse(fs.readFileSync('/tmp/dwsku_to_mfr.json','utf8'));   // {DW_SKU_UPPER: mfr_sku}
const clean = s => (s||'').trim().toUpperCase().replace(/-SAMPLE$/,'');
// gap CSV columns: vendor,mfr_sku,dw_sku,title,handle
const rows = fs.readFileSync('/tmp/kravet_2026_gaps.csv','utf8').trim().split('\n').slice(1)
  .map(l => { const m=l.match(/^([^,]*),([^,]*),([^,]*),(.*),([^,]*)$/); return m?{vendor:m[1],mfr:m[2],dw:m[3],handle:m[5]}:null; })
  .filter(r => r && r.mfr === '(no mfr_sku)' && r.dw);

async function api(p, opts={}, tries=5){
  for(let i=0;i<tries;i++){ const r=await fetch(`https://${STORE}/admin/api/2024-10${p}`,{...opts,headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json',...(opts.headers||{})}});
    if(r.status===429||r.status>=500){await sleep(1500*(i+1));continue;} await sleep(95); return r; }
  throw new Error('fail '+p);
}

(async()=>{
  const todo = rows.map(r => ({...r, recovered: MAP[clean(r.dw)] })).filter(r => r.recovered);
  console.log(`no-mfr-sku gap rows: ${rows.length} | recoverable from kravet_catalog: ${todo.length} | mode: ${APPLY?'APPLY':'DRY RUN'}\n`);
  todo.slice(0,10).forEach(r=>console.log(`  ${r.dw} → mfr_sku ${r.recovered}  (${r.handle})`));
  if(!APPLY){ console.log(`\nDRY RUN — re-run with --apply to write ${todo.length} manufacturer_sku metafields.`); return; }
  // need product id per handle — resolve handle→id, then PUT metafield
  let ok=0, fail=0;
  for(const r of todo){
    try{
      const d = await (await api(`/products.json?handle=${encodeURIComponent(r.handle)}&fields=id`)).json();
      const pid = (d.products||[])[0]?.id; if(!pid){ fail++; continue; }
      const res = await api(`/products/${pid}/metafields.json`, { method:'POST', body: JSON.stringify({ metafield:{ namespace:'custom', key:'manufacturer_sku', type:'single_line_text_field', value:String(r.recovered) }})});
      if(res.ok){ ok++; if(ok%50===0)console.log(`  …${ok}/${todo.length}`); } else { fail++; }
    }catch(e){ fail++; }
  }
  console.log(`\nDONE — ${ok} backfilled, ${fail} failed.`);
})().catch(e=>{console.error(e);process.exit(1);});