← back to Letsbegin

fix_x_mfr.js

75 lines

const https = require('https');
const STORE = 'designer-laboratory-sandbox.myshopify.com';
const TOKEN = process.env.SHOPIFY_PRODUCT_TOKEN;
if (!TOKEN) {
  throw new Error('SHOPIFY_PRODUCT_TOKEN environment variable is required');
}

function gql(body) {
  return new Promise((resolve, reject) => {
    const data = JSON.stringify(body);
    const req = https.request({
      hostname: STORE, path: '/admin/api/2024-10/graphql.json', method: 'POST',
      headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) },
    }, res => { let c = ''; res.on('data', d => c += d); res.on('end', () => { try { resolve(JSON.parse(c)); } catch { resolve({ error: c.slice(0,300) }); } }); });
    req.on('error', reject);
    req.setTimeout(30000, () => { req.destroy(); reject(new Error('timeout')); });
    req.write(data); req.end();
  });
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }

async function main() {
  console.log('=== X-Series MFR SKU Fixer v2 ===');
  console.log('global.manufacturer_sku → custom.manufacturer_sku\n');
  let cursor = null, page = 1, fixed = 0, noGlobal = 0, already = 0, total = 0, errors = 0;

  while (true) {
    const after = cursor ? `, after: "${cursor}"` : '';
    const r = await gql({
      query: `{ products(first: 25, query: "sku:X* AND status:active"${after}) {
        edges { cursor node { id title metafields(first: 30) { edges { node { namespace key value } } } } }
        pageInfo { hasNextPage }
      } }`
    });
    const edges = r?.data?.products?.edges || [];
    if (edges.length === 0) break;

    for (const edge of edges) {
      const p = edge.node;
      cursor = edge.cursor;
      total++;

      let globalMfr = '', globalVni = '', customMfr = '';
      for (const m of p.metafields.edges) {
        const ns = m.node.namespace, k = m.node.key, v = m.node.value || '';
        if (ns === 'global' && k === 'manufacturer_sku') globalMfr = v;
        if (ns === 'global' && k === 'vendor_name_internal') globalVni = v;
        if (ns === 'custom' && k === 'manufacturer_sku') customMfr = v;
      }

      const realMfr = globalMfr || globalVni;
      if (!realMfr) { noGlobal++; continue; }
      if (customMfr === realMfr) { already++; continue; }
      if (customMfr && /[A-Za-z]/.test(customMfr) && customMfr.length > 5) { already++; continue; }

      try {
        const result = await gql({
          query: 'mutation metafieldsSet($m: [MetafieldsSetInput!]!) { metafieldsSet(metafields: $m) { metafields { key } userErrors { message } } }',
          variables: { m: [{ ownerId: p.id, namespace: 'custom', key: 'manufacturer_sku', value: realMfr, type: 'single_line_text_field' }] }
        });
        if ((result?.data?.metafieldsSet?.userErrors || []).length === 0) fixed++;
        else errors++;
      } catch { errors++; }
      await sleep(400);
    }

    console.log(`Page ${page}: total=${total} fixed=${fixed} noGlobal=${noGlobal} already=${already}`);
    if (!r?.data?.products?.pageInfo?.hasNextPage) break;
    page++; await sleep(200);
  }

  console.log(`\n=== DONE === Fixed: ${fixed} | No global MFR: ${noGlobal} | Already correct: ${already} | Errors: ${errors}`);
}
main().catch(e => { console.error('Fatal:', e); process.exit(1); });