← back to Dw Dead Image Recovery
data/tk11050-item4-33corrected/create-33-draft.mjs
94 lines
#!/usr/bin/env node
// TK-11050 item-4 — create 33 corrected Rebel Walls mural DRAFT products on the LIVE store.
// SAFE BY DEFAULT: dry-run. Real create requires RUN=1 . Undo writes created ids to created-ids.json.
// Model A: flat $191 "Sold Per Square Meter" variant + per-sqm carried in custom.mural_price_per_sqm metafield.
import fs from 'node:fs';
import path from 'node:path';
const DOMAIN='designer-laboratory-sandbox.myshopify.com';
const API='2024-10';
const TOKEN=process.env.SHOPIFY_ADMIN_TOKEN;
const RUN=process.env.RUN==='1';
const DIR=path.dirname(new URL(import.meta.url).pathname);
const items=JSON.parse(fs.readFileSync(path.join(DIR,'new33.json'),'utf8'));
if(RUN && !TOKEN){ console.error('SHOPIFY_ADMIN_TOKEN required for RUN=1'); process.exit(1); }
// PARITY FIX (TK-11050, Steve 2026-09-01): the 3,809 LIVE Rebel Walls siblings carry
// sellable variant price = $191 FLAT + custom.mural_price_per_sqm = MARKED-UP per-m2
// (raw retail x 1.0909, e.g. 76.40->83.38, 64.90->70.81). new33.json holds RAW retail in
// mf_mural_price_per_sqm; mark it up so the metafield matches sibling parity. Variant price
// stays 191.0 (already correct). Verified live: R19794/R19548/R18628 all = 83.38 for 76.40 raw.
// Exact per-m2 parity map from LIVE siblings (verified: raw 76.40 -> 83.38, raw 64.90 -> 70.81).
// Use the exact sibling values, not a lossy factor, so the metafield is bit-for-bit parity.
const PARITY_PER_SQM = { '76.4': 83.38, '64.9': 70.81 };
for(const it of items){
const raw = Number(it.mf_mural_price_per_sqm);
const mapped = PARITY_PER_SQM[String(raw)];
if(mapped === undefined){ console.error(`UNMAPPED raw per-sqm ${raw} for ${it.dw_sku} — refusing to guess`); process.exit(1); }
it.mf_mural_price_per_sqm = mapped; // marked-up per-m2, matches the 3,809 live siblings
}
const base=`https://${DOMAIN}/admin/api/${API}`;
const H={'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'};
function buildProduct(it){
return {
product:{
title: it.title,
vendor: 'Rebel Walls',
product_type: 'Wallcovering',
status: 'draft', // NEVER active without image+width verified — start draft
tags: 'Rebel Walls, Mural, Wallcovering, Non-woven',
images: [{ src: it.image_url }],
variants: [
{ title:'Sold Per Square Meter', option1:'Sold Per Square Meter',
sku: it.product_variant_sku, price: String(it.product_variant_price),
inventory_management:'shopify', inventory_policy:'continue' },
{ title:'Sample', option1:'Sample',
sku: it.sample_variant_sku, price: String(it.sample_price),
inventory_management:null, inventory_policy:'continue' } // sample: no inventory tracking
],
options:[{name:'Title'}],
metafields: [
{namespace:'custom', key:'mural_price_per_sqm', type:'number_decimal', value:String(it.mf_mural_price_per_sqm)},
{namespace:'custom', key:'mural_price_base', type:'number_decimal', value:String(it.mf_mural_price_base)},
{namespace:'custom', key:'manufacturer_sku', type:'single_line_text_field', value:it.mfr_sku},
{namespace:'global', key:'manufacturer_sku', type:'single_line_text_field', value:it.mfr_sku},
{namespace:'dwc', key:'manufacturer_sku', type:'single_line_text_field', value:it.mfr_sku},
{namespace:'custom', key:'pattern_name', type:'single_line_text_field', value:it.pattern_color},
{namespace:'dwc', key:'pattern_name', type:'single_line_text_field', value:it.pattern_color},
{namespace:'custom', key:'brand', type:'single_line_text_field', value:'Rebel Walls'},
{namespace:'dwc', key:'brand', type:'single_line_text_field', value:'Rebel Walls'},
{namespace:'custom', key:'supplier_name', type:'single_line_text_field', value:'Rebel Walls'},
{namespace:'dwc', key:'real_vendor', type:'single_line_text_field', value:'Rebel Walls'},
{namespace:'custom', key:'real_vendor', type:'single_line_text_field', value:'Rebel Walls'},
{namespace:'custom', key:'width', type:'single_line_text_field', value:it.mf_width},
{namespace:'dwc', key:'width', type:'single_line_text_field', value:it.mf_width},
{namespace:'global', key:'width', type:'single_line_text_field', value:it.mf_width},
{namespace:'custom', key:'product_class', type:'single_line_text_field', value:'Wallcovering'},
{namespace:'global', key:'unit_of_measure', type:'single_line_text_field', value:'Full Roll'}
]
}
};
}
const created=[];
let i=0;
for(const it of items){
i++;
const payload=buildProduct(it);
if(!RUN){
if(i<=2) console.log(JSON.stringify(payload,null,2));
console.log(`[dry-run ${i}/33] ${it.dw_sku} "${it.title}" variant=$${it.product_variant_price} per_sqm_mf=${it.mf_mural_price_per_sqm}`);
continue;
}
const r=await fetch(`${base}/products.json`,{method:'POST',headers:H,body:JSON.stringify(payload)});
const j=await r.json();
if(!r.ok||!j.product){ console.error(`FAIL ${it.dw_sku}: ${r.status} ${JSON.stringify(j).slice(0,300)}`); continue; }
created.push({dw_sku:it.dw_sku, id:j.product.id, admin_gid:`gid://shopify/Product/${j.product.id}`});
console.log(`[created ${i}/33] ${it.dw_sku} -> ${j.product.id}`);
fs.writeFileSync(path.join(DIR,'created-ids.json'),JSON.stringify(created,null,2));
await new Promise(s=>setTimeout(s, 700)); // gentle pacing; well under 1k/day variant limit (66 variants)
}
if(RUN) console.log(`DONE. created ${created.length}/33. ids -> created-ids.json`);