← back to Filemaker Mcp
fix: consolidate multi-item orders to single line (exact total), round cents, WC PATTERN=base pattern, delete API, Slack→#new-order, error-resilient runner; recovered 32476 ($34)
7525e58d4580eb3f0ab2a8367c83037328f2d01d · 2026-06-29 12:50:45 -0700 · Steve
Files touched
M bin/shopify-sync.jsM lib/invoice.js
Diff
commit 7525e58d4580eb3f0ab2a8367c83037328f2d01d
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jun 29 12:50:45 2026 -0700
fix: consolidate multi-item orders to single line (exact total), round cents, WC PATTERN=base pattern, delete API, Slack→#new-order, error-resilient runner; recovered 32476 ($34)
---
bin/shopify-sync.js | 10 +++++++++-
lib/invoice.js | 44 +++++++++++++++++++-------------------------
2 files changed, 28 insertions(+), 26 deletions(-)
diff --git a/bin/shopify-sync.js b/bin/shopify-sync.js
index 047c2b0..e78fa80 100644
--- a/bin/shopify-sync.js
+++ b/bin/shopify-sync.js
@@ -86,7 +86,15 @@ async function main() {
state.processed = [...new Set([...(state.processed || []), num])].slice(-500);
state.maxProcessed = Math.max(state.maxProcessed || 0, num);
saveState(state);
- } catch (e) { log(`#${num} ERROR:`, e.message); }
+ } catch (e) {
+ // record the failure and mark the order seen so it neither retries forever
+ // nor silently disappears — surfaces in the ledger for manual reprocessing
+ log(`#${num} ERROR:`, e.message);
+ appendFileSync(LEDGER, JSON.stringify({ ts: new Date().toISOString(), orderNumber: num, action: 'error', error: e.message }) + '\n');
+ state.processed = [...new Set([...(state.processed || []), num])].slice(-500);
+ state.maxProcessed = Math.max(state.maxProcessed || 0, num);
+ saveState(state);
+ }
}
await reportProcessed(results);
}
diff --git a/lib/invoice.js b/lib/invoice.js
index 60a678d..fa9ea6f 100644
--- a/lib/invoice.js
+++ b/lib/invoice.js
@@ -92,31 +92,25 @@ 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 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));
- await patch(id, `RETAIL 1(${r})`, price);
- 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 || '');
- }
+ // Consolidate to ONE billable line (line rep 1) — the legacy invoice's
+ // MERCHANDISE TOTAL reliably reflects only rep 1, so a single line keeps the
+ // total exact. Same unit price -> Qty = total units × that price; mixed
+ // prices -> Qty 1 × merchandise subtotal. SKUs listed in the description.
+ const merch = r2(items.reduce((a, li) => a + (Number(li.price) || 0) * (li.quantity || 1), 0));
+ const totalQty = items.reduce((a, li) => a + (li.quantity || 1), 0);
+ const skus = items.map((li) => baseSku(li.sku));
+ const samePrice = new Set(items.map((li) => r2(li.price))).size === 1;
+ const qty = samePrice ? totalQty : 1;
+ const unitPrice = samePrice ? r2(items[0].price) : merch;
+ const desc = items.length === 1
+ ? `${skus[0]} — ${items[0].name || ''}`.trim()
+ : `${items.length} items: ${skus.join(', ')}`;
+ await patch(id, 'DETAIL 1(1)', desc.slice(0, 250));
+ await patch(id, 'Q1(1)', qty);
+ await patch(id, 'Unit 1(1)', uom(items[0] || {}));
+ await patch(id, 'RETAIL 1(1)', unitPrice);
+ await patch(id, 'NET 1(1)', unitPrice);
+ await patch(id, 'Sidemark 1(1)', 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 {}
← 1894d57 auto-save: 2026-06-29T12:49:09 (2 files) — lib/invoice.js sr
·
back to Filemaker Mcp
·
invoice: populate 'WC Pattern Number AKA DW SKU' from primar 707cde7 →