From 869bcac2873acb1786405e5a8c1259bd901ac684 Mon Sep 17 00:00:00 2001 From: justin Date: Sun, 31 May 2026 20:01:29 -0500 Subject: [PATCH] fix batch SO item_code (use erpnext_item) + notification surcharge breakdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ROOT CAUSE of orders never fulfilling: the batch Sales Order used the service SLUG as item_code (e.g. 'mcs150-update') but ERPNext items use the catalog erpnext_item codes ('MCS150-UPDATE'), so SO creation threw 'Item not found' -> no SO -> no portal -> no fulfillment. Now maps slug -> erpnext_item (falls back to COMPLIANCE-SERVICE). DOT ERPNext items were also missing — created them. Notification: show Subtotal / Discount / Card surcharge / Total so totals like $35.54 (= $34.50 + $1.04 surcharge) are transparent instead of looking wrong. Co-Authored-By: Claude Opus 4.8 (1M context) --- api/src/routes/checkout.ts | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/api/src/routes/checkout.ts b/api/src/routes/checkout.ts index 7195b9e..25d0c3a 100644 --- a/api/src/routes/checkout.ts +++ b/api/src/routes/checkout.ts @@ -1268,26 +1268,29 @@ export async function handlePaymentComplete( const customerName = (order.customer_name as string) || "Unknown"; const customerEmail = (order.customer_email as string) || ""; - let totalCents = 0; + let subtotalCents = 0; let discountCents = 0; + let surchargeCents = 0; const serviceNames: string[] = []; for (const r of rows) { - const fee = Number(r.service_fee_cents || r.total_cents || 0); - const disc = Number(r.discount_cents || 0); - const sur = Number(r.surcharge_cents || 0); - const gov = Number(r.gov_fee_cents || 0); - totalCents += fee - disc + sur + gov; - discountCents += disc; + subtotalCents += Number(r.service_fee_cents || r.total_cents || 0) + Number(r.gov_fee_cents || 0); + discountCents += Number(r.discount_cents || 0); + surchargeCents += Number(r.surcharge_cents || 0); serviceNames.push((r.service_name as string) || (r.service_slug as string) || ""); } + const totalCents = subtotalCents - discountCents + surchargeCents; const totalDollars = (totalCents / 100).toFixed(2); const serviceLine = serviceNames.length <= 1 ? `Service: ${serviceNames[0] || order_type}\n` : `Services (${serviceNames.length}):\n • ${serviceNames.join("\n • ")}\n`; + const subtotalLine = `Subtotal: $${(subtotalCents / 100).toFixed(2)}\n`; const discountLine = discountCents > 0 ? `Discount: -$${(discountCents / 100).toFixed(2)} (${order.discount_code || "promo"})\n` : ""; + const surchargeLine = surchargeCents > 0 + ? `Card surcharge: +$${(surchargeCents / 100).toFixed(2)}\n` + : ""; // Show DOT# or FRN/CORES ID depending on service type const intake = (typeof order.intake_data === "object" && order.intake_data) || {}; const dotNumber = (intake as any).dot_number || ""; @@ -1300,8 +1303,10 @@ export async function handlePaymentComplete( + `Email: ${customerEmail}\n` + idLine + serviceLine - + `Total: $${totalDollars}\n` + + subtotalLine + discountLine + + surchargeLine + + `Total: $${totalDollars}\n` + `Payment: ${paymentMethod}\n` + `Order: ${order_id}\n` + `Type: ${order_type}`; @@ -1475,12 +1480,15 @@ export async function handlePaymentComplete( } catch { /* non-fatal */ } // Build line items for ERPNext — show full price with discount on each line + const { COMPLIANCE_SERVICES } = await import("./compliance-orders.js"); const items: Record[] = []; for (const bo of batchRows.rows as any[]) { const priceCents = (bo.service_fee_cents || 0) + (bo.gov_fee_cents || 0); const discCents = bo.discount_cents || 0; + // Map slug -> ERPNext item code (items use erpnext_item codes, not slugs) + const itemCode = (COMPLIANCE_SERVICES as any)[bo.service_slug as string]?.erpnext_item || "COMPLIANCE-SERVICE"; items.push({ - item_code: bo.service_slug, + item_code: itemCode, item_name: bo.service_name || bo.service_slug, qty: 1, rate: priceCents / 100,