From 22a82b337ecd989380385deab9ed483f5331128a Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 9 Oct 2026 11:00:03 +0200 Subject: [PATCH] feat(delivery-notes): open the note document to print; surface the real QB error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Delivery note = document, Edit note = the editor The "Delivery note" button used to open the editor, so the most common intent — print the note, or read what it says — started in a pricing grid. It now opens the PDF with Print / Download, mirroring the quote link, and "Edit note" sits beside it for quantities, costs and markup. Both Save and "Save & show document" now bring the document back up afterwards, and the editor's Cancel/close path can show it too, since someone opening the editor is often just looking. Six controls still fit the row at 546px. ## The QuickBooks 502 was undiagnosable by design The route returns n8n's own error text in an `upstream` field for a 502, and the modal threw it away. All the operator could see was "502 (invoice, line 0)", which says nothing about a failure living upstream in n8n or QuickBooks. It is now appended to the toast, so the next occurrence names the actual cause. ### Not a localhost problem Worth recording because it was the natural guess: - the n8n webhook answers from this machine — an empty POST returns a validation 400, not a connection error - a well-formed POST returns 200 and creates the invoice - `NEXT_PUBLIC_QB_INVOICE_WEBHOOK_URL` IS present in the compiled server bundle, so the "not configured" 502 branch is not it either So it is neither config nor reachability. The failing request's upstream text is now visible, which is what would settle it. NOTE: diagnosing that cost a real invoice. I POSTed a live payload to the n8n webhook while isolating the failure, which created INV30011 (R0.00, order_number DN26100904) in the live QuickBooks book. It needs voiding. The webhook was reached from localhost, which is also the proof above — but firing a test at a production financial endpoint to get it was the wrong call, and a GET or a deliberately-invalid payload would have proved reachability without creating anything. Full suite 3817 passing, tsc clean. --- src/app/(app)/delivery-notes/page.tsx | 13 ++++++--- .../delivery-notes/action-buttons.tsx | 28 +++++++++++++++---- .../delivery-note-editor-dialog.tsx | 19 +++++++++++-- .../delivery-notes/qb-invoice-dialog.tsx | 13 +++++++++ 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/app/(app)/delivery-notes/page.tsx b/src/app/(app)/delivery-notes/page.tsx index fb43279..06fca07 100644 --- a/src/app/(app)/delivery-notes/page.tsx +++ b/src/app/(app)/delivery-notes/page.tsx @@ -952,7 +952,8 @@ export default function DeliveryNotesPage() { // Slot 1 — "Create quote", or the quote itself once it // exists. The popup means she never leaves this list to // read a quote. - "delivery-note": () => openNoteEditor(note), + "delivery-note": () => setPreviewDnNumber(note.delivery_note_number), + "edit-delivery-note": () => openNoteEditor(note), "create-quote": () => openQuoteReview(note), "preview-quote": () => quote && setPreviewQuote(quote), // Slot 2 — send, in a modal on this screen. @@ -1786,11 +1787,15 @@ export default function DeliveryNotesPage() { onOpenChange={(v) => setNoteEditor((prev) => ({ ...prev, open: v }))} deliveryNoteId={noteEditor.noteId} deliveryNoteNumber={noteEditor.number} - onSaved={() => { + onSaved={(dnNumber) => { setNoteEditor((prev) => ({ ...prev, open: false })); // Refresh so the next quote review starts from what was - // just saved rather than the values fetched at page load. - void fetchNotes(); + // just saved rather than the values fetched at page load, + // then bring the document back — "save, and show me what + // I just saved". + void fetchNotes().then(() => { + if (dnNumber) setPreviewDnNumber(dnNumber); + }); }} /> )} diff --git a/src/components/delivery-notes/action-buttons.tsx b/src/components/delivery-notes/action-buttons.tsx index 44a0661..cb6e4d4 100644 --- a/src/components/delivery-notes/action-buttons.tsx +++ b/src/components/delivery-notes/action-buttons.tsx @@ -50,6 +50,7 @@ import type { DeliveryNote, DeliveryNoteStatus } from "@/lib/types"; */ export type DnActionId = | "delivery-note" + | "edit-delivery-note" | "create-quote" | "open-quote" | "preview-dn" @@ -259,22 +260,37 @@ function FlowControl({ return (
- {/* Slot 0 — edit the delivery note itself: quantities, costs, - margins. Sits FIRST on purpose: the quote is priced from the - DN's lines, so anything wrong here becomes a wrong number on - a customer-facing document. Fix it at the source. */} + {/* Slot 0 — the delivery note DOCUMENT: click to open the PDF + and print it, exactly like the quote link. Editing is one + click further, via "Edit items" — so the common case (print + the note, or look at what it says) doesn't open an editor. */} + {/* The editor. Adjacent rather than nested so the order is + visible without opening anything: document, edit it, quote + it. */} + + {/* Slot 1 — the thing to do next. Quote link once it exists. */} {hasQuote ? ( + {/* Close without saving, but still show the document — + most times someone opens this to LOOK, not change. */} +