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