← back to Dwjs Consolidation 2026 04 23

qty_coverage_audit.js

87 lines

#!/usr/bin/env node
// Verify qty metafields (global.v_prods_quantity_order_min/units = 2) on DWJS + DWQW
const https = require('https');
const fs = require('fs');
const STORE='designer-laboratory-sandbox.myshopify.com';
const TOKEN=(process.env.SHOPIFY_ADMIN_TOKEN || '');

function gql(b, retry=0){return new Promise((res,rej)=>{const d=JSON.stringify(b);const r=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(d)}},R=>{let c='';R.on('data',x=>c+=x);R.on('end',()=>{try{res(JSON.parse(c))}catch{if(retry<3)setTimeout(()=>res(gql(b,retry+1)),2000);else res({error:c.slice(0,300)})}})});r.on('error',err=>{if(retry<3)setTimeout(()=>res(gql(b,retry+1)),2000);else rej(err)});r.setTimeout(60000,()=>{r.destroy();if(retry<3)res(gql(b,retry+1));else rej(new Error('t'))});r.write(d);r.end()});}

async function bulkCheck(filter){
  const q=`{ products(query:"${filter}") { edges { node { id
    m1: metafield(namespace:"global", key:"v_prods_quantity_order_min") { value }
    m2: metafield(namespace:"global", key:"v_prods_quantity_order_units") { value }
    variants { edges { node { id sku title
      v1: metafield(namespace:"custom", key:"v_prod_quantity_order_min") { value }
      v2: metafield(namespace:"custom", key:"v_prods_quantity_order_units") { value }
    } } }
  } } } }`;
  // clear prior bulk
  for(let i=0;i<10;i++){const r=await gql({query:'{ currentBulkOperation { status } }'});const st=r?.data?.currentBulkOperation?.status;if(!st||['COMPLETED','FAILED','CANCELED','EXPIRED'].includes(st))break;await new Promise(r=>setTimeout(r,3000));}
  await gql({query:`mutation{ bulkOperationRunQuery(query: """${q}""") { bulkOperation{id} userErrors{message} } }`});
  for(;;){
    await new Promise(r=>setTimeout(r,4000));
    const p=await gql({query:'{ currentBulkOperation { status objectCount url } }'});
    const op=p?.data?.currentBulkOperation;
    console.log(`  bulk ${op?.status} objects=${op?.objectCount||0}`);
    if(op?.status==='COMPLETED') return op.url;
    if(['FAILED','CANCELED','EXPIRED'].includes(op?.status)) throw new Error(op.status);
  }
}

function dl(url,file){return new Promise((r,j)=>{const o=fs.createWriteStream(file);https.get(url,res=>{res.pipe(o);o.on('finish',()=>o.close(()=>r(file)))}).on('error',j)})}

(async()=>{
  const filters = [
    { name: 'DWJS (all)', filter: 'sku:DWJS*' },
    { name: 'DWQW (all)', filter: 'sku:DWQW*' },
  ];
  for (const f of filters) {
    console.log(`\n=== ${f.name} — ${f.filter} ===`);
    const url = await bulkCheck(f.filter);
    const file = `/tmp/qty_audit_${f.name.replace(/\W/g,'')}.jsonl`;
    await dl(url, file);
    const products = new Map();
    for (const line of fs.readFileSync(file,'utf8').split('\n').filter(Boolean)) {
      const o = JSON.parse(line);
      if (o.id?.includes('/Product/')) products.set(o.id, {
        m1: o.m1?.value || null,
        m2: o.m2?.value || null,
        variants: [],
      });
      else if (o.id?.includes('/ProductVariant/') && o.__parentId) {
        const p = products.get(o.__parentId);
        if (p) p.variants.push({
          sku: o.sku || '',
          title: o.title || '',
          v1: o.v1?.value || null,
          v2: o.v2?.value || null,
        });
      }
    }
    const total = products.size;
    let productLvlOk = 0, missingM1 = 0, missingM2 = 0;
    let variantTotal = 0, variantOk = 0, sampleVOk = 0, boltVOk = 0;
    let missingVariants = [];
    for (const [pid, p] of products) {
      if (p.m1 === '2' && p.m2 === '2') productLvlOk++;
      else { if (p.m1 !== '2') missingM1++; if (p.m2 !== '2') missingM2++; }
      for (const v of p.variants) {
        variantTotal++;
        const isSample = /sample/i.test(v.sku + v.title);
        const want = isSample ? '1' : '2';
        if (v.v1 === want && v.v2 === want) { variantOk++; if (isSample) sampleVOk++; else boltVOk++; }
        else if (missingVariants.length < 20) {
          missingVariants.push({pid, sku: v.sku, v1: v.v1, v2: v.v2, want});
        }
      }
    }
    console.log(`  products: ${total}, product-metafield 2/2 ok: ${productLvlOk} (missing min=${missingM1}, missing units=${missingM2})`);
    console.log(`  variants: ${variantTotal}, variant-metafield ok: ${variantOk}`);
    if (missingVariants.length) {
      console.log(`  first variant misses:`);
      for (const m of missingVariants.slice(0,5)) console.log(`    ${m.sku} v1=${m.v1} v2=${m.v2} want=${m.want}`);
    }
  }
})().catch(e=>{console.error(e);process.exit(1)});