← 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);});