← 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)});