← 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.`);