[object Object]

← back to Filemaker Mcp

test: dummy client+invoice demo scripts (999999/Testy McTestface); fix script gains RETAIL cols via HDR layout; probe scripts for line-2/tax calc behavior

2412a379db85bacfc4ef4885b20a71c1f4b4bba8 · 2026-07-01 14:35:54 -0700 · Steve Abrams

Files touched

Diff

commit 2412a379db85bacfc4ef4885b20a71c1f4b4bba8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 1 14:35:54 2026 -0700

    test: dummy client+invoice demo scripts (999999/Testy McTestface); fix script gains RETAIL cols via HDR layout; probe scripts for line-2/tax calc behavior
---
 scripts/fix-dummy-hdr.mjs      | 41 ++++++++++++++++++
 scripts/fix-dummy-tax.mjs      | 24 +++++++++++
 scripts/fix-invoice-102332.mjs |  6 +++
 scripts/probe-line2.mjs        | 27 ++++++++++++
 scripts/test-dummy-invoice.mjs | 95 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 193 insertions(+)

diff --git a/scripts/fix-dummy-hdr.mjs b/scripts/fix-dummy-hdr.mjs
new file mode 100644
index 0000000..ca0696f
--- /dev/null
+++ b/scripts/fix-dummy-hdr.mjs
@@ -0,0 +1,41 @@
+// Repair dummy invoice 999999 (recordId 64904) Otto's way, via the canonical
+// "Invoice to Client Copy" layout: RETAIL fields + tax recalc + GRAND TOTAL readback.
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
+const __dir = dirname(fileURLToPath(import.meta.url));
+for (const line of readFileSync(join(__dir, '..', '.env'), 'utf8').split('\n')) {
+  const m = line.match(/^([A-Z_]+)=(.*)$/);
+  if (m && !process.env[m[1]]) process.env[m[1]] = m[2];
+}
+const fm = await import('../src/fm-client.js');
+const HDR = 'Invoice to Client Copy';
+const ID = 64904;
+
+const steps = [
+  { 'NET 1(1)': 131.94 },
+  { 'NET 1(1)': 131.95 },
+  { 'Q1(2)': 3 },
+  { 'Q1(2)': 4 },
+];
+for (const fieldData of steps) {
+  try {
+    const r = await fm.updateRecord('invoice', HDR, ID, fieldData, { dryRun: false });
+    console.log('patched:', JSON.stringify(r.changes ?? r));
+  } catch (e) { console.log('skip', JSON.stringify(fieldData), '->', e.message); }
+}
+
+const rec = await fm.getRecord('invoice', HDR, ID);
+const f = rec.fieldData;
+console.log('READBACK via HDR:', JSON.stringify({
+  Invoice: f['Invoice'],
+  'RETAIL 1(1)': f['RETAIL 1(1)'], 'NET 1(1)': f['NET 1(1)'], 'TOTAL 1(1)': f['TOTAL 1(1)'],
+  'RETAIL 1(2)': f['RETAIL 1(2)'], 'NET 1(2)': f['NET 1(2)'], 'TOTAL 1(2)': f['TOTAL 1(2)'],
+  'merch tot2': f['merch tot2'],
+  'SALES TAX': f['SALES TAX'],
+  'SALES TAX YN': f['SALES TAX YN'],
+  'SHIPPING  HANDLING': f['SHIPPING  HANDLING'],
+  'GRAND TOTAL': f['GRAND TOTAL'],
+  'PAID ON ACCOUNT': f['PAID ON ACCOUNT'],
+  'NET DUE 2': f['NET DUE 2'],
+}, null, 1));
diff --git a/scripts/fix-dummy-tax.mjs b/scripts/fix-dummy-tax.mjs
new file mode 100644
index 0000000..5d60b72
--- /dev/null
+++ b/scripts/fix-dummy-tax.mjs
@@ -0,0 +1,24 @@
+// Flip dummy invoice 999999 (recordId 64903) to tax-exempt, re-read calcs.
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
+const __dir = dirname(fileURLToPath(import.meta.url));
+for (const line of readFileSync(join(__dir, '..', '.env'), 'utf8').split('\n')) {
+  const m = line.match(/^([A-Z_]+)=(.*)$/);
+  if (m && !process.env[m[1]]) process.env[m[1]] = m[2];
+}
+const fm = await import('../src/fm-client.js');
+
+// readback only
+
+const rec = await fm.getRecord('invoice', 'Order Detail', 64904);
+const f = rec.fieldData;
+console.log(JSON.stringify({
+  Invoice: f['Invoice'],
+  'TOTAL 1(1)': f['TOTAL 1(1)'], 'TOTAL 1(2)': f['TOTAL 1(2)'],
+  'merch tot2': f['merch tot2'],
+  'SALES TAX': f['SALES TAX'],
+  'SHIPPING  HANDLING': f['SHIPPING  HANDLING'],
+  'PAID ON ACCOUNT': f['PAID ON ACCOUNT'],
+  'NET DUE 2': f['NET DUE 2'],
+}, null, 1));
diff --git a/scripts/fix-invoice-102332.mjs b/scripts/fix-invoice-102332.mjs
index 651d1f8..a32be71 100644
--- a/scripts/fix-invoice-102332.mjs
+++ b/scripts/fix-invoice-102332.mjs
@@ -56,6 +56,12 @@ const fieldData = {
 const res = await updateRecord(DB, LAYOUT, rec.recordId, fieldData, { dryRun: !commit });
 console.log(commit ? 'COMMITTED:' : 'DRY RUN DIFF:', JSON.stringify(res, null, 1));
 
+// RETAIL columns live on the canonical header layout (Otto's convention: invoice shows RETAIL)
+const HDR = 'Invoice to Client Copy';
+const retail = { 'RETAIL 1(1)': 131.95, 'RETAIL 1(2)': -19.79 };
+const res2 = await updateRecord(DB, HDR, rec.recordId, retail, { dryRun: !commit });
+console.log(commit ? 'COMMITTED (RETAIL):' : 'DRY RUN DIFF (RETAIL):', JSON.stringify(res2.changes ?? res2, null, 1));
+
 if (commit) {
   const after = await getRecord(DB, LAYOUT, rec.recordId);
   const f = after.fieldData;
diff --git a/scripts/probe-line2.mjs b/scripts/probe-line2.mjs
new file mode 100644
index 0000000..bf18cb3
--- /dev/null
+++ b/scripts/probe-line2.mjs
@@ -0,0 +1,27 @@
+// Probe: what does "line 2 used" contain on real invoices that have a line 2?
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
+const __dir = dirname(fileURLToPath(import.meta.url));
+for (const line of readFileSync(join(__dir, '..', '.env'), 'utf8').split('\n')) {
+  const m = line.match(/^([A-Z_]+)=(.*)$/);
+  if (m && !process.env[m[1]]) process.env[m[1]] = m[2];
+}
+const fm = await import('../src/fm-client.js');
+
+const { records } = await fm.findRecords('invoice', 'Order Detail', { 'DETAIL 1(2)': '*', 'Invoice': '>=100000' }, { limit: 5, sort: [{ fieldName: 'Invoice', sortOrder: 'descend' }] });
+console.log('matches:', records.length);
+for (const r of records) {
+  const f = r.fieldData;
+  console.log(JSON.stringify({
+    Invoice: f['Invoice'],
+    'line 2 used': f['line 2 used'],
+    'Q1(2)': f['Q1(2)'], 'DETAIL 1(2)': f['DETAIL 1(2)'], 'NET 1(2)': f['NET 1(2)'], 'TOTAL 1(2)': f['TOTAL 1(2)'],
+    'MERCHANDISE TOTAL': f['MERCHANDISE TOTAL(1)'] ?? f['MERCHANDISE TOTAL'],
+    'merch tot2': f['merch tot2'],
+    'SALES TAX YN(1)': f['SALES TAX YN(1)'],
+    'NET DUE 2': f['NET DUE 2'],
+    'PAID ON ACCOUNT': f['PAID ON ACCOUNT'],
+    'SHIPPING  HANDLING': f['SHIPPING  HANDLING'],
+  }, null, 1));
+}
diff --git a/scripts/test-dummy-invoice.mjs b/scripts/test-dummy-invoice.mjs
new file mode 100644
index 0000000..867d0a9
--- /dev/null
+++ b/scripts/test-dummy-invoice.mjs
@@ -0,0 +1,95 @@
+// TEST: create a dummy client + dummy invoice so Steve can eyeball the
+// two-line retail/discount layout, then delete both by hand.
+// Usage:
+//   node scripts/test-dummy-invoice.mjs fields <db>      — list layout fields
+//   node scripts/test-dummy-invoice.mjs create           — create client + invoice, print readback
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname, join } from 'node:path';
+
+const __dir = dirname(fileURLToPath(import.meta.url));
+for (const line of readFileSync(join(__dir, '..', '.env'), 'utf8').split('\n')) {
+  const m = line.match(/^([A-Z_]+)=(.*)$/);
+  if (m && !process.env[m[1]]) process.env[m[1]] = m[2];
+}
+
+const fm = await import('../src/fm-client.js');
+const [cmd, arg] = process.argv.slice(2);
+
+if (cmd === 'fields') {
+  const meta = await fm.fieldMetadata(arg || 'Clients');
+  console.log(JSON.stringify((meta.fieldMetaData || []).map((f) => f.name), null, 1));
+  process.exit(0);
+}
+
+if (cmd !== 'create') { console.error('usage: fields <db> | create'); process.exit(2); }
+
+// ---- 1. dummy client (Clients file) ----
+const clientFields = {
+  'Account Number': '9999999',
+  'Company': 'TEST — DELETE ME (Claude layout demo)',
+  'Name': 'Testy McTestface',
+  'Address': '123 Dummy Lane',
+  'City': 'Los Angeles',
+  'State': 'CA',
+  'Zip': '90001',
+  'Phone': '+13105550000',
+  'Email address': 'test-delete-me@example.com',
+};
+// client 57431 already created on first run
+
+// ---- 2. dummy invoice (invoice file / Order Detail) ----
+const TEST_TAG = 'TEST — DELETE ME (Claude layout demo)';
+const invoiceFields = {
+  'Invoice': '999999',
+  'Date': '07/01/2026',
+  'Account #': '9999999',
+  'SOLD NAME': 'Testy McTestface',
+  'SOLD COMPANY': TEST_TAG,
+  'SOLD ADDRESS': '123 Dummy Lane',
+  'SOLD CITY': 'Houston',
+  'SOLD STATE': 'TX',
+  'SOLD ZIP': '77008',
+  'BUYER': 'Testy McTestface',
+  'SHIP COMAPNY': TEST_TAG,
+  'SHIP ADDRESS': '123 Dummy Lane',
+  'SHIP CITY': 'Houston',
+  'SHIP STATE': 'TX',
+  'SHIP ZIP': '77008',
+  'SHIP VIA': 'UPS® Ground',
+  'Cust PO': 'TEST-0000',
+  // line 1: retail
+  'Q1(1)': 4,
+  'Unit 1(1)': 'S/R',
+  'DETAIL 1(1)': 'DWTT80707 - 27" x 4.5yds (TEST)',
+  'NET 1(1)': 131.95,
+  // line 2: discount
+  'Q1(2)': 4,
+  'Unit 1(2)': 'S/R',
+  'DETAIL 1(2)': 'Discount at 15%',
+  'NET 1(2)': -19.79,
+  'SHIPPING  HANDLING': 44.5,
+  'PAID ON ACCOUNT': 493.14,
+  'check Number': 'TEST',
+  'SALES TAX YN(1)': 'N',
+  'Resale #': 'Out of State',
+  'Notes': TEST_TAG,
+};
+
+const created = await fm.createRecord('invoice', 'Order Detail', invoiceFields, { dryRun: false });
+console.log('CREATED invoice record:', JSON.stringify(created));
+const recordId = created.recordId || created.response?.recordId || created;
+
+const rec = await fm.getRecord('invoice', 'Order Detail', recordId);
+const f = rec.fieldData;
+console.log('READBACK (calcs included):', JSON.stringify({
+  recordId,
+  Invoice: f['Invoice'],
+  'SOLD NAME': f['SOLD NAME'],
+  'Q1(1)': f['Q1(1)'], 'Unit 1(1)': f['Unit 1(1)'], 'DETAIL 1(1)': f['DETAIL 1(1)'], 'NET 1(1)': f['NET 1(1)'], 'TOTAL 1(1)': f['TOTAL 1(1)'],
+  'Q1(2)': f['Q1(2)'], 'Unit 1(2)': f['Unit 1(2)'], 'DETAIL 1(2)': f['DETAIL 1(2)'], 'NET 1(2)': f['NET 1(2)'], 'TOTAL 1(2)': f['TOTAL 1(2)'],
+  'MERCHANDISE TOTAL': f['MERCHANDISE TOTAL(1)'] ?? f['MERCHANDISE TOTAL'],
+  'SHIPPING  HANDLING': f['SHIPPING  HANDLING'],
+  'PAID ON ACCOUNT': f['PAID ON ACCOUNT'],
+  'NET DUE 2': f['NET DUE 2'],
+}, null, 1));

← 7b9b08c fix: fm_find/sort JSON-string coercion (harness stringifies  ·  back to Filemaker Mcp  ·  create 3 missing DWTT Baxter WALLPAPER masters (73433 Navy/7 9bad19a →