← back to Filemaker Mcp

scripts/backfill-grs-name-color.mjs

66 lines

// Backfill blank "Name of Pattern" / "Color of Pattern" on the recent WallQuest
// specialty-grasscloth WALLPAPER masters (Series=GRS, vid=WQ). Today's import created
// ~73 records with Width + Internal Description populated but Name/Color left blank
// (the pusher never mapped those two fields). This joins each FM record to the
// authoritative dw_unified.specialty_grasscloths_stage by mfr code (FM lowercase
// "Mfr Pattern" == stage UPPER "mfr_sku") and writes clean pattern/color.
//
// Idempotent + safe: only fills fields that are currently BLANK, only via the
// "*List Wallpapers - Full View" layout (the one that actually exposes Name/Color),
// reports any FM record with no stage match instead of guessing.
//
//   node backfill-grs-name-color.mjs            # DRY-RUN (prints plan)
//   node backfill-grs-name-color.mjs --apply    # commit
import { execFileSync } from 'node:child_process';
import * as fm from '../src/fm-client.js';

const APPLY = process.argv.includes('--apply');
const DB = 'WALLPAPER';
const LAYOUT = '*List Wallpapers - Full View';
const PSQL = process.env.PSQL_BIN || '/opt/homebrew/opt/postgresql@14/bin/psql';
const PG = 'postgresql:///dw_unified?host=/tmp&user=stevestudio2';
const sleep = ms => new Promise(r => setTimeout(r, ms));

// authoritative pattern/color keyed by UPPER(mfr_sku)
const stageRows = JSON.parse(execFileSync(PSQL, [PG, '-tAc',
  `select coalesce(json_agg(json_build_object('mfr',upper(mfr_sku),'pattern',pattern,'color',color))::text,'[]')
   from specialty_grasscloths_stage`], { encoding: 'utf8', maxBuffer: 64e6 }).trim());
const stage = new Map(stageRows.map(r => [r.mfr, r]));

async function main() {
  // the recent broken batch: GRS / WQ, Name blank, Internal Description present
  const res = await fm.findRecords(DB, LAYOUT,
    { Series: 'GRS', vid: 'WQ', 'Name of Pattern': '=', 'Internal Description': '*' },
    { limit: 500 });
  const recs = res.records || [];
  console.log(`Found ${recs.length} GRS/WQ records with blank Name of Pattern · ${APPLY ? 'APPLY' : 'DRY-RUN'}\n`);

  let ok = 0, skip = 0, nomatch = 0, fail = 0;
  const misses = [];
  for (const rec of recs) {
    const fd = rec.fieldData;
    const sku = fd['combo sku'];
    const mfr = String(fd['Mfr Pattern'] || '').trim();
    const s = stage.get(mfr.toUpperCase());
    if (!s || !s.pattern) { nomatch++; misses.push(`${sku} (mfr ${mfr || '—'})`); continue; }

    const patch = {};
    if (!String(fd['Name of Pattern'] || '').trim()) patch['Name of Pattern'] = s.pattern;
    if (!String(fd['Color of Pattern'] || '').trim() && s.color) patch['Color of Pattern'] = s.color;
    if (!Object.keys(patch).length) { skip++; continue; }

    if (!APPLY) {
      if (ok < 80) console.log(`  ${sku}  ${mfr}  Name="${patch['Name of Pattern'] ?? '(kept)'}"  Color="${patch['Color of Pattern'] ?? '(kept)'}"`);
      ok++; continue;
    }
    try {
      await fm.updateRecord(DB, LAYOUT, rec.recordId, patch, { dryRun: false });
      ok++; if (ok % 20 === 0) console.log(`  ...updated ${ok}`);
      await sleep(120);
    } catch (e) { fail++; console.error(`  FAIL ${sku}: ${e.fmCode || e.message}`); await sleep(400); }
  }
  console.log(`\n${APPLY ? 'Updated' : 'Planned'}=${ok}  alreadyFilled=${skip}  noStageMatch=${nomatch}  fail=${fail}`);
  if (misses.length) console.log(`No stage match (left untouched):\n  ${misses.join('\n  ')}`);
}
main().catch(e => { console.error(e); process.exit(1); });