[object Object]

← back to Designer Wallcoverings

WallQuest onboard: gated Phillipe Romano draft-push (per-yard, settlement-filtered)

d4b939ce4e84cd56bd0dd0bfa5524542ae4ac5af · 2026-07-09 23:31:37 -0700 · Steve

push-drafts-book.cjs creates DRAFT products (status:draft): beach-city title, per-yard
pricing (min/step 8, 8-yd bolt metafields), Sample+Per-Yard variants, leak-safe LOCAL
swatch+patch-quilt-room images, mfr/private-label/spec metafields. SKIPS settlement-flagged
rows (Daisy 7 florals stay DRAFT). Idempotent (shopify_product_id), --dry-run validated:
LA 135 + Daisy 45 = 180 clean, 0 leaks. GATED live write — Steve runs via !.

Files touched

Diff

commit d4b939ce4e84cd56bd0dd0bfa5524542ae4ac5af
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jul 9 23:31:37 2026 -0700

    WallQuest onboard: gated Phillipe Romano draft-push (per-yard, settlement-filtered)
    
    push-drafts-book.cjs creates DRAFT products (status:draft): beach-city title, per-yard
    pricing (min/step 8, 8-yd bolt metafields), Sample+Per-Yard variants, leak-safe LOCAL
    swatch+patch-quilt-room images, mfr/private-label/spec metafields. SKIPS settlement-flagged
    rows (Daisy 7 florals stay DRAFT). Idempotent (shopify_product_id), --dry-run validated:
    LA 135 + Daisy 45 = 180 clean, 0 leaks. GATED live write — Steve runs via !.
---
 scripts/wallquest-refresh/push-drafts-book.cjs | 88 ++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)

diff --git a/scripts/wallquest-refresh/push-drafts-book.cjs b/scripts/wallquest-refresh/push-drafts-book.cjs
new file mode 100644
index 00000000..975210a5
--- /dev/null
+++ b/scripts/wallquest-refresh/push-drafts-book.cjs
@@ -0,0 +1,88 @@
+// Create Phillipe Romano DRAFT products for a WallQuest real-textile book. Per-yard pricing,
+// sample variant, beach-city title, leak-safe LOCAL images (swatch + patch-quilt room), metafields.
+// Settlement-flagged rows are SKIPPED. Idempotent (skips rows with shopify_product_id). Status=draft.
+// GATED live write — Steve runs via `!`. Use --dry-run first (no token, no network).
+//   TABLE=lillian_august_catalog VIEW=<dir> node push-drafts-book.cjs --dry-run
+const fs = require('fs'), https = require('https'), { execSync } = require('child_process');
+const DRY = process.argv.includes('--dry-run');
+const STORE = 'designer-laboratory-sandbox.myshopify.com', API = '2024-10';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const TABLE = process.env.TABLE, VIEW = process.env.VIEW;
+const CONN = process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/dw_unified';
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+if (!TABLE || !VIEW) { console.error('need TABLE + VIEW'); process.exit(1); }
+if (!DRY && !TOKEN) { console.error('no SHOPIFY_ADMIN_TOKEN (use --dry-run)'); process.exit(1); }
+const psql = sql => execSync(`psql "${CONN}" -tAF'\u00a7' -c "${sql.replace(/"/g,'\\"')}"`, { encoding: 'utf8', maxBuffer: 1e9 });
+const clean = s => (s||'').replace(/\bWallpapers?\b/gi,'Wallcovering');
+const FORBID = /wallquest|lillian\s*august|carl\s*robinson|daisy\s*bennett|chesapeake|brewster/i;
+function rest(method, path, body) {
+  return new Promise((res, rej) => {
+    const data = body ? JSON.stringify(body) : null;
+    const req = https.request({ hostname: STORE, path: `/admin/api/${API}${path}`, method,
+      headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}) }, timeout: 60000 },
+      r => { let d = ''; r.on('data', c => d += c); r.on('end', () => { try { res({ status: r.statusCode, j: d ? JSON.parse(d) : {} }); } catch { res({ status: r.statusCode, raw: d }); } }); });
+    req.on('error', rej); req.on('timeout', () => { req.destroy(); rej(new Error('timeout')); });
+    if (data) req.write(data); req.end();
+  });
+}
+const b64 = p => { try { return fs.readFileSync(p).toString('base64'); } catch { return null; } };
+
+const rows = psql(`SELECT dw_sku, pattern_name, color_name, material, our_price, COALESCE(ai_description,''),
+  COALESCE((local_image_path::jsonb)->>0,''), COALESCE((room_setting_images::jsonb)->>0,''),
+  mfr_sku, COALESCE(width,''), COALESCE(repeat_v,''), COALESCE(match_type,''),
+  COALESCE(ai_styles::text,'[]'), COALESCE(color_hex,''), COALESCE(shopify_product_id::text,''), COALESCE(settlement_flag,'')
+  FROM ${TABLE} ORDER BY dw_sku`).trim().split('\n').filter(Boolean).map(l => {
+    const [dw_sku,pattern,color,material,price,desc,img,room,mfr,width,repeat,match,styles,hex,pid,flag] = l.split('\u00a7');
+    return { dw_sku,pattern,color,material,price,desc,img,room,mfr,width,repeat,match,styles,hex,pid,flag };
+  });
+
+(async () => {
+  let created = 0, skip = 0, flagged = 0, already = 0, err = 0, leak = 0;
+  console.log(`${DRY?'[DRY-RUN] ':''}${TABLE}: ${rows.length} rows`);
+  for (const r of rows) {
+    if (r.flag) { flagged++; continue; }                              // settlement — DRAFT, don't push
+    if (r.pid) { already++; continue; }                                // idempotent
+    const title = clean(`${r.pattern}${r.color?' '+r.color:''} Wallcovering | Phillipe Romano`);
+    if (FORBID.test(`${title} ${r.desc}`)) { leak++; console.log('  LEAK', r.dw_sku); continue; }
+    const swatch = r.img ? b64(`${VIEW}/${r.img}`) : null;
+    const roomImg = r.room ? b64(`${VIEW}/${r.room}`) : null;
+    if (!swatch) { skip++; console.log('  no swatch', r.dw_sku); continue; }
+    let styles = []; try { styles = JSON.parse(r.styles); } catch {}
+    const product = {
+      title, body_html: clean(r.desc), vendor: 'Phillipe Romano', product_type: 'Wallcovering', status: 'draft',
+      tags: [r.pattern.split(' ')[0], r.material, 'Natural Texture', 'Grasscloth & Naturals', 'Phillipe Romano', ...styles].filter(Boolean).join(', '),
+      options: [{ name: 'Size' }],
+      variants: [
+        { option1: 'Sample', sku: `${r.dw_sku}-Sample`, price: '4.25', inventory_management: null, inventory_policy: 'deny' },
+        { option1: 'Per Yard', sku: r.dw_sku, price: String(r.price), inventory_quantity: 999, inventory_policy: 'continue' },
+      ],
+      images: [ { attachment: swatch, alt: title }, ...(roomImg ? [{ attachment: roomImg, alt: `${title} room setting`, position: 2 }] : []) ],
+      metafields: [
+        { namespace: 'custom', key: 'manufacturer_sku', value: r.mfr, type: 'single_line_text_field' },
+        { namespace: 'dwc', key: 'manufacturer_sku', value: r.mfr, type: 'single_line_text_field' },
+        { namespace: 'private_label', key: 'real_vendor_name', value: 'WallQuest', type: 'single_line_text_field' },
+        { namespace: 'global', key: 'width', value: r.width, type: 'single_line_text_field' },
+        { namespace: 'global', key: 'unit_of_measure', value: 'Priced Per Yard', type: 'single_line_text_field' },
+        { namespace: 'global', key: 'v_prods_quantity_order_min', value: '8', type: 'single_line_text_field' },
+        { namespace: 'global', key: 'v_prods_quantity_order_units', value: '8', type: 'single_line_text_field' },
+        { namespace: 'global', key: 'packaged', value: '8-Yard Bolt (sold per yard)', type: 'single_line_text_field' },
+        { namespace: 'specs', key: 'material', value: r.material, type: 'single_line_text_field' },
+        { namespace: 'specs', key: 'repeat_v', value: r.repeat, type: 'single_line_text_field' },
+        { namespace: 'custom', key: 'color', value: r.color, type: 'single_line_text_field' },
+        { namespace: 'custom', key: 'color_hex', value: r.hex, type: 'single_line_text_field' },
+      ].filter(m => m.value),
+    };
+    if (DRY) { created++; if (created <= 3) console.log(`  would create: ${title} | $${r.price}/yd | ${product.images.length} imgs | ${product.metafields.length} mf`); continue; }
+    try {
+      const u = await rest('POST', '/products.json', { product });
+      if (u.status >= 200 && u.status < 300 && u.j.product) {
+        const pid = u.j.product.id;
+        psql(`UPDATE ${TABLE} SET shopify_product_id=${pid} WHERE dw_sku='${r.dw_sku}'`);
+        created++; console.log(`  ✓ ${r.dw_sku} ${title}`);
+      } else { err++; console.log(`  ✗ ${r.dw_sku} HTTP ${u.status} ${(u.raw||JSON.stringify(u.j)).slice(0,120)}`); }
+      await sleep(600);
+    } catch (e) { err++; console.log(`  ✗ ${r.dw_sku} ${String(e).slice(0,70)}`); }
+  }
+  console.log(`\n${DRY?'[DRY-RUN] ':''}${TABLE} DONE: created=${created} already=${already} settlement-skipped=${flagged} no-swatch=${skip} leaks=${leak} errors=${err}`);
+  if (DRY) console.log('live: SHOPIFY_ADMIN_TOKEN=… TABLE=… VIEW=… node push-drafts-book.cjs  (Steve via !)');
+})();

← 3252281c catalog-push: HARDEN sync bucket to missing/empty tables ONL  ·  back to Designer Wallcoverings  ·  auto-save: 2026-07-09T23:42:48 (4 files) — pending-approval/ a4a39262 →