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