[object Object]

← back to Filemaker Mcp

auto-save: 2026-06-29T12:49:09 (2 files) — lib/invoice.js src/fm-client.js

1894d579958dcc3f8e5b7e4c6cf7abdd383ae5ac · 2026-06-29 12:49:14 -0700 · Steve Abrams

Files touched

Diff

commit 1894d579958dcc3f8e5b7e4c6cf7abdd383ae5ac
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 29 12:49:14 2026 -0700

    auto-save: 2026-06-29T12:49:09 (2 files) — lib/invoice.js src/fm-client.js
---
 lib/invoice.js   | 32 +++++++++++++++++++++++++-------
 src/fm-client.js |  7 +++++++
 2 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/lib/invoice.js b/lib/invoice.js
index 6ebfab7..60a678d 100644
--- a/lib/invoice.js
+++ b/lib/invoice.js
@@ -20,7 +20,9 @@ const WCPAT_LAYOUT = '1-order detail 1 Copy3'; // exposes "wc pattern"
 const today = () => { const d = new Date(); return `${String(d.getMonth()+1).padStart(2,'0')}/${String(d.getDate()).padStart(2,'0')}/${d.getFullYear()}`; };
 const stamp = () => new Date().toLocaleString('en-US', { month:'short', day:'numeric', year:'numeric', hour:'numeric', minute:'2-digit' });
 const baseSku = (sku) => { const m = (sku||'').match(/^([A-Za-z]+-?\d+)/); return m ? m[1] : (sku||''); };
-const wcPattern = (sku) => (sku||'').replace(/-?\s*sample\s*/ig, '').trim();
+// WC PATTERN / SKU = the pattern's Shopify SKU with the -Sample/Sample variant removed.
+const wcPattern = (sku) => baseSku((sku||'').replace(/-?\s*sample\s*/ig, '').trim());
+const r2 = (n) => Math.round((Number(n)||0) * 100) / 100;
 function uom(li) {
   const t = `${li.variant_title||''} ${li.name||''} ${li.sku||''}`.toLowerCase();
   if (/per yard|\/ ?yard|\byard/.test(t)) return 'yards';
@@ -47,8 +49,9 @@ async function nextInvoiceNumber() {
 // safe single-field patch (skips calc / non-modifiable fields without aborting)
 async function patch(id, field, value) {
   try { await fm.updateRecord('invoice', HDR, id, { [field]: value }, { dryRun: false }); }
-  catch (e) { if (!['102','201','509'].includes(e.fmCode)) throw e; }
+  catch (e) { if (!['102','201','509','111'].includes(e.fmCode)) throw e; }
 }
+const MAX_LINES = 4; // DETAIL 1 / Q1 / NET 1 family caps at maxRepeat 4
 
 export async function createInvoiceForOrder(order, accountNumber, { commit = true } = {}) {
   const c = order.customer || {}, s = order.shipping_address || order.billing_address || {};
@@ -89,9 +92,12 @@ export async function createInvoiceForOrder(order, accountNumber, { commit = tru
   await patch(id, 'SALES TAX YN', orderTax > 0 ? 'Y' : 'N');
   await patch(id, 'check Number', today());
 
-  // line items -> repetitions
-  for (let i = 0; i < items.length; i++) {
-    const li = items[i], r = i + 1, price = Number(li.price || 0);
+  // line items -> repetitions 1..MAX_LINES. If more line items than slots,
+  // write the first MAX_LINES-1 individually and CONSOLIDATE the rest into the
+  // last slot (qty 1 × summed $) so the merchandise total stays exact.
+  const individual = items.length <= MAX_LINES ? items.length : MAX_LINES - 1;
+  for (let i = 0; i < individual; i++) {
+    const li = items[i], r = i + 1, price = r2(li.price);
     await patch(id, `DETAIL 1(${r})`, `${li.sku || ''} — ${li.name || ''}`.trim());
     await patch(id, `Q1(${r})`, li.quantity);
     await patch(id, `Unit 1(${r})`, uom(li));
@@ -99,13 +105,25 @@ export async function createInvoiceForOrder(order, accountNumber, { commit = tru
     await patch(id, `NET 1(${r})`, price);
     await patch(id, `Sidemark 1(${r})`, s.company || '');
   }
+  if (items.length > MAX_LINES) {
+    const rest = items.slice(individual);
+    const restTotal = r2(rest.reduce((a, li) => a + (Number(li.price)||0) * (li.quantity||1), 0));
+    const skus = rest.map((li) => baseSku(li.sku)).join(', ');
+    const r = MAX_LINES;
+    await patch(id, `DETAIL 1(${r})`, `+${rest.length} more items: ${skus}`.slice(0, 250));
+    await patch(id, `Q1(${r})`, 1);
+    await patch(id, `Unit 1(${r})`, 'lot');
+    await patch(id, `RETAIL 1(${r})`, restTotal);
+    await patch(id, `NET 1(${r})`, restTotal);
+    await patch(id, `Sidemark 1(${r})`, s.company || '');
+  }
 
   // cross-table fields (different base-table layouts; share the record id)
   try { await fm.updateRecord('invoice', SKU_LAYOUT, id, { 'JS Easy SKU #': baseSku(primarySku) }, { dryRun: false }); } catch {}
   try { await fm.updateRecord('invoice', WCPAT_LAYOUT, id, { 'wc pattern': wcPattern(primarySku) }, { dryRun: false }); } catch {}
 
-  // paid-in-full = computed grand total -> Net Due 0
-  const grand = (await fm.getRecord('invoice', HDR, id))?.fieldData?.['GRAND TOTAL'] || 0;
+  // paid-in-full = computed grand total (rounded to cents) -> Net Due 0
+  const grand = r2((await fm.getRecord('invoice', HDR, id))?.fieldData?.['GRAND TOTAL'] || 0);
   await patch(id, 'PAID ON ACCOUNT', grand);
   await patch(id, 'Notes', `Imported by Otto — ${stamp()}`);
 
diff --git a/src/fm-client.js b/src/fm-client.js
index a21cf28..deb7798 100644
--- a/src/fm-client.js
+++ b/src/fm-client.js
@@ -151,6 +151,13 @@ export async function updateRecord(db, layout, recordId, fieldData, { dryRun = t
   return { committed: true, dryRun: false, database: db, layout: lay, recordId, changes: diff };
 }
 
+export async function deleteRecord(db, layout, recordId) {
+  assertWritable(db);
+  const lay = resolveLayout(db, layout);
+  await fm(db, `/layouts/${encodeURIComponent(lay)}/records/${encodeURIComponent(recordId)}`, { method: 'DELETE' });
+  return { deleted: true, database: db, recordId };
+}
+
 export async function createRecord(db, layout, fieldData, { dryRun = true } = {}) {
   assertWritable(db);
   const lay = resolveLayout(db, layout);

← c4dc440 sync: refresh existing client's contact info from the order  ·  back to Filemaker Mcp  ·  fix: consolidate multi-item orders to single line (exact tot 7525e58 →