← back to Designer Wallcoverings
Quadrille family reclassification: 3 Alan Campbell + 24 small-line lines -> Fabric; China Seas kept residential Wallcovering (reverted catalog Commercial-WC flag per Steve)
a324b3e2ec81eb4db91deead884a91c1f7d1d9e8 · 2026-06-30 09:25:33 -0700 · Steve
Files touched
A shopify/scripts/china-seas-revert-commercial.jsA shopify/scripts/quadrille-type-fix.js
Diff
commit a324b3e2ec81eb4db91deead884a91c1f7d1d9e8
Author: Steve <steve@designerwallcoverings.com>
Date: Tue Jun 30 09:25:33 2026 -0700
Quadrille family reclassification: 3 Alan Campbell + 24 small-line lines -> Fabric; China Seas kept residential Wallcovering (reverted catalog Commercial-WC flag per Steve)
---
shopify/scripts/china-seas-revert-commercial.js | 61 +++++++++++++++
shopify/scripts/quadrille-type-fix.js | 98 +++++++++++++++++++++++++
2 files changed, 159 insertions(+)
diff --git a/shopify/scripts/china-seas-revert-commercial.js b/shopify/scripts/china-seas-revert-commercial.js
new file mode 100644
index 00000000..b879d108
--- /dev/null
+++ b/shopify/scripts/china-seas-revert-commercial.js
@@ -0,0 +1,61 @@
+#!/usr/bin/env node
+/**
+ * china-seas-revert-commercial.js (one-off, 2026-06-30)
+ * Steve: China Seas is residential — Fabrics and Wallpaper only, NO Commercial Wallcovering.
+ * Revert the 52 China Seas products that were set to 'Commercial Wallcovering' back to
+ * 'Wallcovering' (wallpaper), removing the stray 'Commercial Wallcovering' tag, on
+ * live Shopify AND the dw_unified mirror.
+ *
+ * Usage: node china-seas-revert-commercial.js --dry | node china-seas-revert-commercial.js
+ */
+const { Pool } = require('pg');
+require('dotenv').config({ path: __dirname + '/../.env' });
+
+const DRY = process.argv.includes('--dry');
+const SHOP = process.env.SHOPIFY_SHOP_URL || 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN || process.env.SHOPIFY_ADMIN_API_TOKEN;
+const pool = process.env.DATABASE_URL
+ ? new Pool({ connectionString: process.env.DATABASE_URL })
+ : new Pool({ host: '/tmp', database: 'dw_unified', user: process.env.PGUSER || process.env.USER });
+
+function normalizeTags(tagStr) {
+ let tags = (tagStr || '').split(',').map(t => t.trim()).filter(Boolean);
+ tags = tags.filter(t => t !== 'Commercial Wallcovering'); // drop commercial
+ if (!tags.includes('Wallcovering')) tags.push('Wallcovering'); // ensure wallpaper tag
+ return [...new Set(tags)].join(', ');
+}
+async function api(method, path, body) {
+ const res = await fetch(`https://${SHOP}/admin/api/2024-10/${path}`, {
+ method, headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+ body: body ? JSON.stringify(body) : undefined });
+ if (!res.ok) throw new Error(`${method} ${path} -> ${res.status} ${await res.text()}`);
+ return res.json();
+}
+
+(async () => {
+ // the 52: china_seas_catalog rows flagged Commercial Wallcovering, linked to Shopify
+ const { rows } = await pool.query(`
+ SELECT DISTINCT regexp_replace(shopify_product_id,'\\D','','g') AS pid
+ FROM china_seas_catalog WHERE product_type='Commercial Wallcovering' AND shopify_product_id ~ '\\d'`);
+ console.log(`Reverting ${rows.length} China Seas products -> Wallcovering (${DRY ? 'DRY' : 'EXECUTE'})`);
+ let ok = 0, fail = 0;
+ for (const { pid } of rows) {
+ try {
+ if (DRY) { console.log(` [dry] ${pid}: type->Wallcovering, drop Commercial Wallcovering tag`); ok++; continue; }
+ const live = (await api('GET', `products/${pid}.json?fields=id,tags`)).product;
+ await api('PUT', `products/${pid}.json`, { product: { id: Number(pid), product_type: 'Wallcovering', tags: normalizeTags(live.tags) } });
+ await pool.query(`
+ UPDATE shopify_products
+ SET product_type='Wallcovering',
+ tags = array_to_string(
+ (SELECT array_agg(DISTINCT x) FROM (
+ SELECT trim(t) AS x FROM unnest(string_to_array(tags, ',')) t
+ WHERE trim(t) <> '' AND trim(t) <> 'Commercial Wallcovering'
+ UNION SELECT 'Wallcovering') s WHERE x<>''), ', ')
+ WHERE regexp_replace(shopify_id,'\\D','','g') = $1`, [pid]);
+ console.log(` ✓ ${pid}`); ok++;
+ } catch (e) { console.error(` ✗ ${pid}: ${e.message}`); fail++; }
+ }
+ console.log(`\nDone. ok=${ok} fail=${fail}`);
+ await pool.end();
+})();
diff --git a/shopify/scripts/quadrille-type-fix.js b/shopify/scripts/quadrille-type-fix.js
new file mode 100644
index 00000000..3805a9ca
--- /dev/null
+++ b/shopify/scripts/quadrille-type-fix.js
@@ -0,0 +1,98 @@
+#!/usr/bin/env node
+/**
+ * quadrille-type-fix.js (one-off, 2026-06-30)
+ * Push correct product_type + type-tag from the unified catalog truth to live Shopify
+ * for mis-typed Quadrille-family products, then sync the dw_unified mirror.
+ *
+ * - 24 small-line products (Suncloth/Quadrille/Home Couture/Plains/Charles Burger/
+ * Cloth and Paper): Wallcovering -> Fabric (catalog: quadrille_house_catalog)
+ * - 52 China Seas products: Wallcovering -> Commercial Wallcovering (catalog: china_seas_catalog)
+ *
+ * Type+tag only. All small-line products are sample-only (no unit/MOQ cascade).
+ * Re-fetches live tags per product and does a token-level swap. Idempotent.
+ *
+ * Usage: node quadrille-type-fix.js --dry # show plan, no writes
+ * node quadrille-type-fix.js # execute
+ */
+const { Pool } = require('pg');
+require('dotenv').config({ path: __dirname + '/../.env' });
+
+const DRY = process.argv.includes('--dry');
+const SHOP = process.env.SHOPIFY_SHOP_URL || 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN || process.env.SHOPIFY_ADMIN_API_TOKEN;
+const pool = process.env.DATABASE_URL
+ ? new Pool({ connectionString: process.env.DATABASE_URL })
+ : new Pool({ host: '/tmp', database: 'dw_unified', user: process.env.PGUSER || process.env.USER });
+
+// tag-token swap helper
+function swapTag(tagStr, from, to) {
+ const tags = (tagStr || '').split(',').map(t => t.trim()).filter(Boolean);
+ const out = tags.map(t => (t === from ? to : t));
+ return [...new Set(out)].join(', ');
+}
+
+async function api(method, path, body) {
+ const res = await fetch(`https://${SHOP}/admin/api/2024-10/${path}`, {
+ method,
+ headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+ body: body ? JSON.stringify(body) : undefined,
+ });
+ if (!res.ok) throw new Error(`${method} ${path} -> ${res.status} ${await res.text()}`);
+ return res.json();
+}
+
+// Build the target set from catalog truth joined to the live mirror.
+const TARGETS_SQL = `
+ WITH src AS (
+ SELECT regexp_replace(shopify_product_id,'\\D','','g') AS pid, product_type AS truth
+ FROM china_seas_catalog WHERE shopify_product_id ~ '\\d'
+ UNION ALL
+ SELECT regexp_replace(shopify_product_id,'\\D','','g'), product_type
+ FROM quadrille_house_catalog WHERE shopify_product_id ~ '\\d'
+ ),
+ m AS (SELECT regexp_replace(shopify_id,'\\D','','g') AS pid, shopify_id, vendor, product_type AS live, tags
+ FROM shopify_products)
+ SELECT m.pid, m.shopify_id, m.vendor, m.live, src.truth, m.tags
+ FROM src JOIN m USING (pid)
+ WHERE src.truth <> m.live
+ -- only the family corrections we agreed to:
+ AND ( (src.truth='Fabric' AND m.live='Wallcovering')
+ OR (src.truth='Commercial Wallcovering' AND m.live='Wallcovering') )
+ ORDER BY m.vendor;`;
+
+(async () => {
+ const { rows } = await pool.query(TARGETS_SQL);
+ console.log(`Targets: ${rows.length} products (${DRY ? 'DRY RUN' : 'EXECUTING'})`);
+ const byType = {};
+ rows.forEach(r => { const k = `${r.live} -> ${r.truth}`; byType[k] = (byType[k]||0)+1; });
+ console.log(byType);
+
+ let ok = 0, fail = 0;
+ for (const r of rows) {
+ const fromTag = 'Wallcovering';
+ const toTag = r.truth; // 'Fabric' or 'Commercial Wallcovering'
+ try {
+ if (DRY) {
+ console.log(` [dry] ${r.vendor} ${r.pid}: type->${r.truth}, tag ${fromTag}->${toTag}`);
+ ok++; continue;
+ }
+ // re-fetch live tags
+ const live = (await api('GET', `products/${r.pid}.json?fields=id,tags,product_type`)).product;
+ const newTags = swapTag(live.tags, fromTag, toTag);
+ await api('PUT', `products/${r.pid}.json`, { product: { id: Number(r.pid), product_type: r.truth, tags: newTags } });
+ // sync mirror
+ await pool.query(
+ `UPDATE shopify_products SET product_type=$1,
+ tags = regexp_replace(tags, '(^|, )Wallcovering(,|$)', '\\1' || $1 || '\\2')
+ WHERE regexp_replace(shopify_id,'\\D','','g') = $2`,
+ [r.truth, r.pid]);
+ console.log(` ✓ ${r.vendor} ${r.pid}: ${r.live} -> ${r.truth}`);
+ ok++;
+ } catch (e) {
+ console.error(` ✗ ${r.vendor} ${r.pid}: ${e.message}`);
+ fail++;
+ }
+ }
+ console.log(`\nDone. ok=${ok} fail=${fail}`);
+ await pool.end();
+})();
← 747a6821 auto-save: 2026-06-30T09:09:13 (5 files) — pending-approval/
·
back to Designer Wallcoverings
·
China Seas reconcile (DTD-A): source china_seas_catalog 106 4af735f6 →