← back to Filemaker Mcp
scripts/drain-flagged-skus.mjs
72 lines
// Drain the flagged-SKU queue (data/flagged-skus.jsonl) written by lib/notify.js
// flagSku() whenever an invoice line was created with a blank VID + Detail 1 Mfr
// Number because ensureWallpaper() couldn't find a mfr number for the SKU.
//
// For each UNRESOLVED row it re-runs ensureWallpaper (the WALLPAPER master may now
// exist / dw_unified may now carry the mfr), and — reusing the exact copy-lookup
// write from scripts/ensure-wallpaper-for-order.mjs — writes VID + Detail 1 Mfr
// Number onto every matching invoice line for that SKU's Cust PO. Rows that now
// resolve are marked resolved:true; rows that still can't resolve stay open.
//
// node scripts/drain-flagged-skus.mjs # drain + rewrite the queue
// node scripts/drain-flagged-skus.mjs --dry-run # report only, no writes
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
for (const l of readFileSync(join(ROOT, '.env'), 'utf8').split('\n')) {
const m = l.match(/^([A-Z0-9_]+)=(.*)$/); if (m && !process.env[m[1]]) process.env[m[1]] = m[2].replace(/^['"]|['"]$/g, '');
}
const DRY = process.argv.includes('--dry-run');
const QUEUE = join(ROOT, 'data', 'flagged-skus.jsonl');
const ISHIP = 'Order Detail - Main - Shipping';
if (!existsSync(QUEUE)) { console.log('no flagged-skus.jsonl — nothing to drain.'); process.exit(0); }
const fm = await import('../src/fm-client.js');
const { ensureWallpaper } = await import('../lib/wallpaper.js');
const rows = readFileSync(QUEUE, 'utf8').split('\n').filter(Boolean)
.map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
const open = rows.filter((r) => !r.resolved);
console.log(`queue: ${rows.length} row(s), ${open.length} unresolved`);
if (!open.length) process.exit(0);
// group unresolved by Cust PO so we pull each invoice's lines once
const byPO = new Map();
for (const r of open) {
const po = r.custPO || String(r.orderName || '').replace(/^#/, '');
if (!byPO.has(po)) byPO.set(po, []);
byPO.get(po).push(r);
}
const resolvedKeys = new Set();
for (const [po, group] of byPO) {
if (!po) { console.log('skip row with no Cust PO'); continue; }
const { records } = await fm.findRecords('invoice', ISHIP, { 'Cust PO': '==' + po }, { limit: 30, sort: [{ fieldName: 'Invoice', sortOrder: 'ascend' }] }).catch(() => ({ records: [] }));
const skuOf = (rf) => (rf['WC Pattern Number AKA DW SKU(1)'] || rf['WC Pattern Number AKA DW SKU'] || '').trim();
const combos = [...new Set(group.map((r) => r.dwSku).filter(Boolean))];
console.log(`Cust PO ${po}: ${combos.length} flagged SKU(s): ${combos.join(', ')}`);
for (const combo of combos) {
const wp = await ensureWallpaper(combo);
if (!wp.ok) { console.log(` ${combo}: STILL FLAGGED — ${wp.reason}`); continue; }
if (!DRY) {
for (const rec of records) {
if (skuOf(rec.fieldData) !== combo) continue;
try { await fm.updateRecord('invoice', ISHIP, rec.recordId, { 'VID': wp.vid, 'Detail 1 Mfr Number': wp.mfr }, { dryRun: false }); }
catch (e) { console.log(` ${combo} invoice line err ${e.fmCode}`); }
}
}
console.log(` ${combo}: ${DRY ? 'WOULD RESOLVE' : 'RESOLVED'} mfr=${wp.mfr} vid=${wp.vid} [${wp.src}]`);
for (const r of group) if (r.dwSku === combo) resolvedKeys.add(r.key);
}
}
if (DRY) { console.log(`\n[dry-run] would resolve ${resolvedKeys.size} row(s). queue unchanged.`); process.exit(0); }
// rewrite the queue with resolved rows flagged (keep them for audit trail)
const out = rows.map((r) => resolvedKeys.has(r.key) ? { ...r, resolved: true, resolvedAt: new Date().toISOString() } : r);
writeFileSync(QUEUE, out.map((r) => JSON.stringify(r)).join('\n') + '\n');
console.log(`\nresolved ${resolvedKeys.size} row(s); ${out.filter((r) => !r.resolved).length} still open.`);