← back to Sample Followup Sweep
reconciled drafter: window 8/6-8/10 (fill-in since last send), Sent-verified addrs + processor, no Ref# on emails; draft into info@
5df1ebe0c8b44c0eb349d54e0035437f0b2a1777 · 2026-08-20 11:12:31 -0700 · Steve
Files touched
A data/outstanding-snapshot.jsonA scripts/send-reconciled.mjs
Diff
commit 5df1ebe0c8b44c0eb349d54e0035437f0b2a1777
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Aug 20 11:12:31 2026 -0700
reconciled drafter: window 8/6-8/10 (fill-in since last send), Sent-verified addrs + processor, no Ref# on emails; draft into info@
---
data/outstanding-snapshot.json | 127 +++++++++++++++++++++++++++++++++++++++++
scripts/send-reconciled.mjs | 72 +++++++++++++++++++++++
2 files changed, 199 insertions(+)
diff --git a/data/outstanding-snapshot.json b/data/outstanding-snapshot.json
new file mode 100644
index 0000000..47e5863
--- /dev/null
+++ b/data/outstanding-snapshot.json
@@ -0,0 +1,127 @@
+{
+ "GREEN": [
+ {
+ "mfr": "BX10040",
+ "sku": "PWV10040",
+ "req": "08/06/2026"
+ },
+ {
+ "mfr": "FD280-10",
+ "sku": "GRS800010",
+ "req": "08/10/2026"
+ }
+ ],
+ "ROMO": [
+ {
+ "mfr": "W971/01",
+ "sku": "ROP74578",
+ "req": "08/07/2026"
+ }
+ ],
+ "BRE": [
+ {
+ "mfr": "82674",
+ "sku": "DWJS90108",
+ "req": "08/07/2026"
+ },
+ {
+ "mfr": "82674",
+ "sku": "DWJS90108",
+ "req": "08/10/2026"
+ }
+ ],
+ "BM": [
+ {
+ "mfr": "FD280-05 Driftwood Camel",
+ "sku": "HLW73095",
+ "req": "08/10/2026"
+ },
+ {
+ "mfr": "SN101038P",
+ "sku": "GRS20323",
+ "req": "08/10/2026"
+ },
+ {
+ "mfr": "Roofweave_v60_4",
+ "sku": "HLW73145",
+ "req": "08/10/2026"
+ }
+ ],
+ "OSB": [
+ {
+ "mfr": "PDG1146-07-Porcelaine de Chine Flora Birds",
+ "sku": "EUR90475",
+ "req": "08/10/2026"
+ },
+ {
+ "mfr": "NCW4390-02",
+ "sku": "EUR80387",
+ "req": "08/10/2026"
+ }
+ ],
+ "YOR": [
+ {
+ "mfr": "LC7137",
+ "sku": "PRC84025",
+ "req": "08/10/2026"
+ },
+ {
+ "mfr": "4177-27719",
+ "sku": "DWC322184",
+ "req": "08/10/2026"
+ },
+ {
+ "mfr": "MF4735",
+ "sku": "DWC322186",
+ "req": "08/10/2026"
+ },
+ {
+ "mfr": "AB4920-200",
+ "sku": "DWJS91574",
+ "req": "08/10/2026"
+ },
+ {
+ "mfr": "CV4455",
+ "sku": "DWJS17387",
+ "req": "08/10/2026"
+ },
+ {
+ "mfr": "FH4068",
+ "sku": "DWJS16618",
+ "req": "08/10/2026"
+ }
+ ],
+ "KRA": [
+ {
+ "mfr": "BR-81035.606.0",
+ "sku": "DWKK101625",
+ "req": "08/10/2026"
+ },
+ {
+ "mfr": "W4159-84",
+ "sku": "DWKK130319",
+ "req": "08/10/2026"
+ }
+ ],
+ "SANDB": [
+ {
+ "mfr": "P804-99",
+ "sku": "DWSS72675",
+ "req": "08/10/2026"
+ }
+ ],
+ "WQ": [
+ {
+ "mfr": "FC60808",
+ "sku": "DWQW60039",
+ "req": "08/10/2026"
+ }
+ ],
+ "THIB": [
+ {
+ "mfr": "T16217",
+ "sku": "DWTT70093",
+ "req": "08/10/2026"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/scripts/send-reconciled.mjs b/scripts/send-reconciled.mjs
new file mode 100644
index 0000000..6d41f5a
--- /dev/null
+++ b/scripts/send-reconciled.mjs
@@ -0,0 +1,72 @@
+#!/usr/bin/env node
+// Reconciled sample-follow-up DRAFTER (Steve 8/20).
+// Pulls outstanding memos in the "fill-in-since-last-send / 10-day-floor" window and DRAFTS one
+// letter per vendor into info@ Drafts (NEVER auto-sends). Steve reviews + sends from Drafts.
+// Letter = intro + "Attn / processed by: <vendor processor>" + Ref#·Date Ordered·Manufacturer# table.
+// WIN env overrides the FileMaker date range (default = today's fill-in window).
+import { readFileSync, writeFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { join } from 'node:path'; import http from 'node:http';
+const ROOT = join(homedir(), 'Projects/sample-followup-sweep');
+const WIN = process.env.WIN || '08/06/2026...08/10/2026'; // entered 8/6..8/10 (since 8/15 send, 10-day floor)
+
+// --- FileMaker: pull outstanding (Date WP Sample Sent empty) in WIN, group by vid ---
+const cfg = JSON.parse(readFileSync(join(homedir(), '.claude.json'), 'utf8'));
+const fenv = cfg?.mcpServers?.filemaker?.env || {};
+for (const k of ['FM_CLOUD_HOST', 'FM_CLARIS_EMAIL', 'FM_CLARIS_PASSWORD']) process.env[k] = fenv[k];
+process.env.FM_READONLY = '1';
+const fm = await import('/Users/macstudio3/Projects/filemaker-mcp/src/fm-client.js');
+const A = (await fm.findRecords('WALLPAPER', 'REPORT ON SAMPLES ORDERED',
+ { 'Date WP Sample Sent': '=', 'today for client': WIN }, { limit: 1200 }).catch(() => ({ records: [] }))).records;
+const snap = {};
+for (const r of A) { const d = r.fieldData; const v = String(d.vid || '').toUpperCase(); if (!v) continue;
+ (snap[v] = snap[v] || []).push({ mfr: (d['Mfr Pattern'] || '').trim(), sku: d['combo sku'] || '', req: d['today for client'] || '' }); }
+writeFileSync(join(ROOT, 'data/outstanding-snapshot.json'), JSON.stringify(snap, null, 2));
+
+const esc = s => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
+const SHIP = { name: 'Designer Wallcoverings', line1: '15442 Ventura Blvd. #102', csz: 'Sherman Oaks, CA 91403', phone: '1-888-373-4564' };
+// Sent-verified addresses + processor (person at vendor who handled the order), per vendor
+const V = {
+ YOR: { name: 'York', to: 'RiveraH@yorkwall.com, orders@yorkwall.com', acct: '34328', proc: 'York Sample Dept (order-thru-website desk)' },
+ BRE: { name: 'Brewster', to: 'brewsterorders@brewp.com', acct: '6003784', proc: 'Brewster Orders desk' },
+ BMGREEN:{ name: 'Greenland / bmwallpaper', to: 'sales@bmwallpaper.com', acct: 'On File', proc: 'Nora (NORA & GREENLAND)' },
+ OSB: { name: 'Osborne & Little', to: 'Polancol@oalusa.com', acct: '1433803', proc: 'Leandra Polanco' },
+ KRA: { name: 'Kravet', to: 'matt.schoffman@kravet.com', acct: '10087117', proc: 'Matt Schoffman' },
+ ROMO: { name: 'Romo', to: 'fawn.bowen@romo.com', acct: '223641', proc: 'Fawn Bowen (Sampling Coordinator)' },
+ SANDB: { name: 'Sandberg (via Gimmersta)', to: 'support@gimmersta.com', acct: 'On File', proc: 'Alva' },
+ WQ: { name: 'WallQuest / Malibu', to: 'orders@wallquest.com', acct: '651317', proc: 'WallQuest Orders desk' },
+ THIB: { name: 'Thibaut', to: 'cathy.dy@thibautdesign.com', acct: '0108751', proc: 'Cathy Dy' },
+ ARTE: { name: 'Arte (@ Egg & Dart)', to: 'Therese@egg-and-dart.com', acct: 'On File', proc: 'Therese Stachowiak' },
+};
+// merge BM + GREEN under one bmwallpaper letter
+const items = {};
+for (const [vid, arr] of Object.entries(snap)) { const key = (vid === 'BM' || vid === 'GREEN') ? 'BMGREEN' : vid; (items[key] = items[key] || []).push(...arr); }
+
+function letter(v, rows) {
+ // Steve 8/20: do NOT show our internal Ref # (DW SKU) to the vendor — Date Ordered + Mfr # only.
+ const tbl = rows.map(r => `<tr style="border-bottom:1px solid #eee"><td style="padding:5px 18px 5px 0;white-space:nowrap">${esc(r.req || '—')}</td><td style="padding:5px 0">${esc(r.mfr)}</td></tr>`).join('\n');
+ return `<div style="font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#222;line-height:1.5">
+<p>We are following up on memo samples we have not yet received (ordered ~10 days ago).</p>
+<p><strong>Our account number is ${esc(v.acct)}</strong></p>
+<p><strong>Attn / processed by:</strong> ${esc(v.proc)} — please route these to whoever handled the original order.</p>
+<table style="border-collapse:collapse;font-size:14px;margin:8px 0">
+<tr style="text-align:left;border-bottom:2px solid #333"><th style="padding:5px 18px 5px 0">Date Ordered</th><th style="padding:5px 0">Manufacturer #</th></tr>
+${tbl}
+</table>
+<p>Ship to:<br>${esc(SHIP.name)}<br>${esc(SHIP.line1)}<br>${esc(SHIP.csz)}<br>${esc(SHIP.phone)}</p>
+<p><strong>Sidemark: Samples ASAP</strong></p>
+<p>Thank you!<br>Showroom Manager</p></div>`;
+}
+// George draft (account=info, /api/drafts — no send-approval token)
+const genv = k => { for (const f of ['/Users/macstudio3/Projects/Designer-Wallcoverings/DW-MCP/.env', '/Users/macstudio3/Projects/george-gmail/.env']) { try { const m = readFileSync(f, 'utf8').match(new RegExp('^' + k + '=(.+)$', 'm')); if (m) return m[1].trim(); } catch {} } return ''; };
+let auth = genv('GEORGE_BASIC_AUTH'); if (!auth.includes(':')) auth = 'admin:' + genv('GEORGE_BASIC_AUTH_PASS');
+auth = 'Basic ' + Buffer.from(auth).toString('base64');
+function draft(to, subject, body) { return new Promise(res => { const b = JSON.stringify({ account: 'info', to, subject, body }); const rq = http.request({ host: '127.0.0.1', port: 9850, path: '/api/drafts', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(b), Authorization: auth } }, r => { let d = ''; r.on('data', x => d += x); r.on('end', () => { try { const j = JSON.parse(d); res(j.success ? 'ok' : d.slice(0, 80)); } catch { res(d.slice(0, 80)); } }); }); rq.on('error', e => res(e.message)); rq.write(b); rq.end(); }); }
+
+const DO = process.argv.includes('--draft');
+console.log(`Reconciled follow-up — window ${WIN} — ${DO ? 'DRAFT into info@' : 'DRY-RUN (add --draft to create)'}\n`);
+for (const [key, rows] of Object.entries(items)) {
+ const v = V[key]; if (!v) { console.log(` ? no vendor map for ${key} (${rows.length})`); continue; }
+ const subj = `Sample Follow-Up — Outstanding Memos (Acct ${v.acct}) — Designer Wallcoverings`;
+ if (!DO) { console.log(` · ${v.name.padEnd(26)} → ${v.to.padEnd(40)} (${rows.length}) refs: ${rows.map(r => r.sku).join(', ')}`); continue; }
+ const r = await draft(v.to, subj, letter(v, rows));
+ console.log(` ${r === 'ok' ? '✎' : '✗'} ${v.name.padEnd(26)} → ${v.to.padEnd(40)} (${rows.length}) ${r === 'ok' ? '' : r}`);
+}
← aacc67a sample-followup: recently-contacted suppression — skip vendo
·
back to Sample Followup Sweep
·
watch-sent-stamp: fast Sent-poller stamps FileMaker 2nd Requ 9f0c85d →