diff --git a/src/app/(app)/invoicing/page.tsx b/src/app/(app)/invoicing/page.tsx index 75e71b8..af07ce5 100644 --- a/src/app/(app)/invoicing/page.tsx +++ b/src/app/(app)/invoicing/page.tsx @@ -23,6 +23,7 @@ import { buildDeliveryNotePrintGroups, buildTodayUnprintedDeliveryNoteGroups, buildQbInvoicePayload, + billableRows, nameSimilarity, buildOrderInvoiceCards, buildRecentInvoiceRange, @@ -2048,6 +2049,20 @@ export default function InvoicingPage() { toast.error("Pick at least one item before sending to QuickBooks."); return false; } + // Task 6 of the non-invoiceable-order-extras plan. `rows.length` + // above counts excluded rows, which ride along for the n8n + // status flip but never bill. If every row in the group is + // excluded the payload's `items` array would be empty and + // QuickBooks would receive a zero-line invoice. + // + // Both live modals disable submit on this case, so this is + // defence-in-depth for the callers that bypass them: the + // (currently unrendered) bulk modal and any legacy call that + // passes `overrides: undefined` and falls back to every row. + if (billableRows(rows).length === 0) { + toast.error("This order has no billable lines."); + return false; + } const first = rows[0]; const similarity = nameSimilarity(customer.name, first.companyName ?? ""); if (similarity < 0.35) {