[object Object]

← back to Designer Wallcoverings

Anthology import: UOM resolver + ready-to-push DRAFT creator (DRY-RUN default)

2a58e23b6240ab76e958a8a6ba80eac21458a73a · 2026-07-22 08:40:54 -0700 · Steve

Files touched

Diff

commit 2a58e23b6240ab76e958a8a6ba80eac21458a73a
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jul 22 08:40:54 2026 -0700

    Anthology import: UOM resolver + ready-to-push DRAFT creator (DRY-RUN default)
---
 shopify/scripts/cadence/anthology-import-push.mjs | 79 +++++++++++++++++++++++
 shopify/scripts/cadence/sdg-resolve-attrs.mjs     | 44 +++++++++++++
 2 files changed, 123 insertions(+)

diff --git a/shopify/scripts/cadence/anthology-import-push.mjs b/shopify/scripts/cadence/anthology-import-push.mjs
new file mode 100644
index 00000000..3fccf2c1
--- /dev/null
+++ b/shopify/scripts/cadence/anthology-import-push.mjs
@@ -0,0 +1,79 @@
+#!/usr/bin/env node
+/** Ready-to-push Anthology import: creates the 48 staged anthology_catalog rows as
+ *  DW-compliant Shopify products. DRAFT by default (activation is a separate gated
+ *  step after validate-before-activate). DRY-RUN unless --push. Steve 2026-07-22.
+ *
+ *  Each product: vendor=Anthology, productType=Wallcovering, body=description,
+ *  options[Size]=[Sample,<UOM>], variants: <dw_sku>-Sample @4.25 + <dw_sku> @retail
+ *  (cost=trade), images from all_images, tags (Wallcovering/Anthology/<collection>/
+ *  color:<color>/New Arrival/display_variant), SEO metafields. Auto-joins the
+ *  anthology-wallcoverings smart collection via vendor=Anthology.
+ */
+import { execSync } from 'node:child_process';
+import fs from 'node:fs';
+const PUSH = process.argv.includes('--push');
+const ENV = fs.readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8');
+const TOKEN = (ENV.match(/^SHOPIFY_ADMIN_TOKEN=(.*)$/m)||[])[1].trim();
+const API='https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10';
+const q=(sql)=>execSync(`psql -h /tmp -d dw_unified -tAc "${sql}"`,{encoding:'utf8'});
+const sleep=(ms)=>new Promise(r=>setTimeout(r,ms));
+
+// description isn't in the table; pull from the source JSON by mfr_sku
+const srcDesc={};
+try{ for(const p of JSON.parse(fs.readFileSync('/tmp/sdg/anthology_full.json'))) srcDesc[p.sku]=p.description||''; }catch{}
+const rows=JSON.parse(q(`SELECT COALESCE(json_agg(t),'[]') FROM (SELECT dw_sku,mfr_sku,pattern_name,color_name,collection,uom,price_trade,price_retail,image_url,all_images,width,length,repeat_v FROM anthology_catalog ORDER BY dw_sku) t`).trim())
+  .map(r=>({...r, description:srcDesc[r.mfr_sku]||''}));
+
+async function shopify(path,method,body){
+  const r=await fetch(`${API}${path}`,{method,headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:body?JSON.stringify(body):undefined});
+  const j=await r.json().catch(()=>({}));
+  if(!r.ok) throw new Error(`${r.status} ${JSON.stringify(j).slice(0,200)}`);
+  return j;
+}
+
+function buildProduct(x){
+  const title=`Anthology - ${x.pattern_name} ${x.color_name} Wallcovering | Anthology`;
+  const imgs=(()=>{try{return JSON.parse(x.all_images||'[]');}catch{return x.image_url?[x.image_url]:[];}})();
+  const tags=['Wallcovering','Anthology',x.collection,`color:${x.color_name}`,'New Arrival','display_variant'].filter(Boolean).join(', ');
+  return {product:{
+    title, vendor:'Anthology', product_type:'Wallcovering', status:'draft',
+    body_html:x.description?`<p>${x.description}</p>`:`<p>${x.pattern_name} ${x.color_name} — Anthology wallcovering.</p>`,
+    tags,
+    options:[{name:'Size'}],
+    variants:[
+      {option1:'Sample', sku:`${x.dw_sku}-Sample`, price:'4.25', inventory_management:null, requires_shipping:true, taxable:true},
+      {option1:x.uom, sku:x.dw_sku, price:String(x.price_retail), cost:String(x.price_trade), inventory_management:null, requires_shipping:true, taxable:true},
+    ],
+    images: imgs.slice(0,6).map(src=>({src})),
+    metafields:[
+      {namespace:'global',key:'title_tag',type:'single_line_text_field',value:`${x.pattern_name} ${x.color_name} — Anthology at Designer Wallcoverings`},
+      {namespace:'global',key:'description_tag',type:'single_line_text_field',value:`Anthology ${x.pattern_name} ${x.color_name} wallcovering. Samples & purchasing at Designer Wallcoverings. 1-888-373-4564`},
+      {namespace:'custom',key:'width',type:'single_line_text_field',value:x.width||''},
+    ],
+  }};
+}
+
+(async()=>{
+  console.log(`${PUSH?'*** LIVE PUSH ***':'DRY-RUN (no writes) — pass --push to create'}  | ${rows.length} products`);
+  let ok=0, fail=[];
+  for(const x of rows){
+    const payload=buildProduct(x);
+    if(!PUSH){
+      const v=payload.product.variants;
+      console.log(`  ${x.dw_sku} [${x.uom}]  ${payload.product.title}  | sample $4.25 + ${x.uom} $${x.price_retail} (cost $${x.price_trade}) | ${payload.product.images.length} img`);
+      continue;
+    }
+    try{
+      const res=await shopify('/products.json','POST',payload);
+      // set variant cost on inventory item (REST variant cost isn't persisted on create)
+      const sell=res.product.variants.find(v=>v.sku===x.dw_sku);
+      if(sell?.inventory_item_id) await shopify(`/inventory_items/${sell.inventory_item_id}.json`,'PUT',{inventory_item:{id:sell.inventory_item_id,cost:String(x.price_trade)}}).catch(()=>{});
+      // mirror shopify id back to staging
+      execSync(`psql -h /tmp -d dw_unified -c "UPDATE anthology_catalog SET shopify_product_id='${res.product.id}' WHERE dw_sku='${x.dw_sku}'"`);
+      ok++; console.log(`  ✓ ${x.dw_sku} → product ${res.product.id} (DRAFT)`);
+    }catch(e){ fail.push([x.dw_sku,e.message]); console.log(`  ✗ ${x.dw_sku}: ${e.message}`); }
+    await sleep(400);
+  }
+  if(PUSH){ console.log(`\nCREATED ${ok}/${rows.length} DRAFT products.`); if(fail.length){console.log('FAILURES:');fail.forEach(f=>console.log('  ',f));}
+    console.log('Next (gated): run validate-before-activate, then activate DRAFT→ACTIVE.'); }
+})();
diff --git a/shopify/scripts/cadence/sdg-resolve-attrs.mjs b/shopify/scripts/cadence/sdg-resolve-attrs.mjs
new file mode 100644
index 00000000..0431e189
--- /dev/null
+++ b/shopify/scripts/cadence/sdg-resolve-attrs.mjs
@@ -0,0 +1,44 @@
+#!/usr/bin/env node
+/** Resolve Magento option codes (UOM 6247/6241, form 6543) → labels via the
+ *  session-authorized attribute API. Also dump a sample product's price/uom for
+ *  discount inference. $0 local. Steve 2026-07-22 */
+import { createRequire } from 'module';
+const require = createRequire(import.meta.url);
+let chromium;
+for (const p of ['/Users/macstudio3/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/node_modules/playwright','playwright']) { try { ({ chromium } = require(p)); break; } catch {} }
+import fs from 'node:fs';
+const OUT='/tmp/sdg';
+const B='https://trade.sandersondesigngroup.com/us/api/n';
+const log=(...a)=>console.log(new Date().toISOString().slice(11,19),...a);
+const browser=await chromium.launch({headless:true});
+const ctx=await browser.newContext({storageState:OUT+'/state.json',
+  userAgent:'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36'});
+const req=ctx.request;
+try{
+  const page=await ctx.newPage();
+  await page.goto('https://trade.sandersondesigngroup.com/us/products/wallpaper',{waitUntil:'domcontentloaded',timeout:60000}).catch(()=>{});
+  await page.waitForTimeout(2500);
+  const ids=[6247,6241,6543];
+  for(const t of ['attribute','attributeOption','option','entity']){
+    try{
+      const r=await req.get(`${B}/load?type=${t}&verbosity=2&ids=${ids.join(',')}`,{timeout:40000});
+      const j=await r.json();
+      if(j.result && j.result.length){ log(`type=${t} →`); console.log(JSON.stringify(j.result,null,1).slice(0,1500)); }
+      else log(`type=${t}: empty`);
+    }catch(e){ log(`type=${t} err`,e.message); }
+  }
+  // load the uom attribute definition (options list)
+  for(const code of ['sdb_stock_uom_refents','sdb_form_variant','sdb_stock_uom_refents_data']){
+    try{
+      const r=await req.get(`${B}/load?type=attribute&verbosity=2&ids=${code}`,{timeout:40000});
+      const j=await r.json();
+      log(`attr ${code}:`); console.log(JSON.stringify(j.result||j,null,1).slice(0,2000));
+    }catch(e){ log('attr err',e.message); }
+  }
+  // fetch a full product with verbosity 3 to see resolved uom + price block
+  const r=await req.get(`${B}/load?type=product&verbosity=3&ids=1179891`,{timeout:40000});
+  const p=(await r.json()).result?.[0]||{};
+  log('sample product verbosity3 price-related keys:');
+  for(const k of Object.keys(p)) if(/price|uom|unit|qty|pack|roll|meter|metre|yard|sample|tier/i.test(k)) console.log('  ',k,'=',JSON.stringify(p[k]).slice(0,120));
+  fs.writeFileSync(OUT+'/sample_v3.json',JSON.stringify(p,null,1));
+}catch(e){ log('FATAL',e.message);} finally{ await browser.close(); }

← 41e292c1 cadence: add Maharam launch drip script (import+activate, me  ·  back to Designer Wallcoverings  ·  auto-save: 2026-07-22T09:12:53 (2 files) — shopify/scripts/c 7440b0f2 →