TWO bugs, one of which was hiding the other.
1. "No signed copy on file" for a note that had just been signed,
scanned and uploaded. The upload was never the problem — the READ
path was. `delivery_notes.signed_copy` is a PB file field with
`maxSelect: 1`: we WRITE it as an array (`signed_copy: [file]`),
but PocketBase RETURNS A BARE STRING when exactly one file is
attached. Verified on the live record after a real upload:
typeof : string
Array.isArray : false
value : "image_ar52u2hjfq.jpg"
value[0] : "i" <-- indexing yields a CHARACTER
Every read site assumed `string[]`, and not one of them threw:
- signed-copy GET: `Array.isArray(x) ? x[0] : null` returned null
for notes that demonstrably had a copy — the reported error
- delivery-note detail tooltip: `signed_copy[0]` rendered the
first LETTER of the filename
- delivery-notes-service: treated every signed copy as absent, so
the quote went out with the UNSIGNED pdf attached
- deliveries-service: reported an empty filename
- deliveries page: `.join(", ")` on a string — this one THREW,
on a page people open daily
Adds `src/lib/file-fields.ts` (firstFileName / fileNames / hasFile)
and routes all of them through it. The type was the root of the
wrong assumptions, so it is now `string | string[]` with a comment
pointing at the helper.
2. QR 28pt -> 52pt, moved to the top-right corner. Measured by
rasterising the real PDF at 200 dpi and decoding: it now reads at
1.24 px/module, where the old size needed ~2.5 — roughly double the
distance and angle tolerance.
It could not simply go at top:40: the logo is an absolute 160x100pt
block in the same corner, so the QR landed on top of it. That is
not only ugly, it breaks the QR's quiet zone — logo ink inside the
light margin makes the code refuse to decode. The logo now steps
left (right: 112 -> 106, width 160 -> 132) to give the QR the true
corner, and the company name is width-bounded ONLY when a logo is
present. The Westscott letterhead has no matching .svg in
public/letterhead/, so in practice the name still gets the full
width and stays on one line.
Nine tests pin the file-field shapes, including one that asserts the
old `.join(", ")` call really does throw on a string, so the string
case cannot be quietly dropped again.
An account sale at the till used to produce a delivery-note-shaped slip that
was rendered entirely on the device and printed on the 80mm thermal printer.
Nothing reached the server, so the paperwork the client signed had no record
behind it. Cash / card / EFT are untouched and still get the thermal slip.
An account sale now creates a real `delivery_notes` record. The DN is raised
server-side INSIDE `checkoutSale` rather than by a follow-up call from the
till, because the offline queue (`flushQueue`) re-posts `/checkout` and
nothing else — a client-side second call would be silently dropped for every
sale taken while the network was down, which is exactly when a till is
flakiest.
`pos_sales.delivery_note` is the idempotency anchor that makes it
exactly-once: on a replay it hands back the existing DN instead of making a
second one, and a sale whose first attempt left the field blank retries. It
self-heals.
Deliberately non-fatal: if the DN can't be created the sale still succeeds.
The money and the stock movement are real by then; failing the checkout would
leave the drawer disagreeing with the stock ledger. The till is told instead.
Also in here:
- DN number collision retry. The generator reads the highest existing number
and adds one, which is not atomic, while PB holds a unique index on it. The
POS adds a second concurrent writer, so two creates could collide and 500
on a benign condition.
- GET /api/v1/pos/sales now expands customer, customer.company and
delivery_note so history.html can rebuild the DN card on a reprint. The
nested customer.company matters — customer.company is itself a relation,
so without it the card printed a raw PB id where the name belongs.
- "Print slip" is hidden for account sales; that tender produces a delivery
note, not a slip.
New GET /api/v1/pos/delivery-note/[number]/pdf streams the A4 original behind
assertPosAuth, so the till's bearer key stays its only credential. It does not
point at the CRM's unauthenticated invoicing route.
Verified end to end against a live note: DN26100401, correct company, both
lines with pricing, PDF attached, stock decremented once, and a replay of the
same client_ref returned the same DN without a duplicate.
- Added an audit trail feature that logs every create, update, and delete action on delivery notes and orders, capturing detailed changes per field.
- Introduced the `AuditTimeline` component to display the history of changes for delivery notes and orders, enhancing traceability.
- Updated the `page.tsx` files for delivery notes and orders to fetch and display audit logs, ensuring users can view the history of modifications.
- Enhanced the `package.json` with a new migration script for setting up the audit log functionality.
This feature improves accountability and transparency in the management of delivery notes and orders.
Splits the quote attachment model along the seam that actually matters:
'operator-only references' (drawings the operator consulted, supplier
emails they filed away) vs 'customer-facing files' (supplier quotes,
drawings, NDAs that need to ride along on the email).
Schema (PB): new `internal_attachments` + `outbound_attachments` fields
on the `quotes` collection, maxSelect 10 each. Existing `attachments`
field kept for back-compat but is no longer the source of truth for
outbound emails.
API:
- GET/POST/DELETE /api/v1/quotes/[id]/internal-attachments[/{filename}]
- GET/POST/DELETE /api/v1/quotes/[id]/outbound-attachments[/{filename}]
- POST /api/v1/quotes/[id]/attachments/upload kept for legacy `attachments`
MCP: `quotes.send_email` description now correctly states
`selectedAttachmentIds` are PB file tokens from `outbound_attachments`
(supplier quotes, drawings, NDAs), not IDs of the legacy `attachments`
collection.
Frontend: `ProcessOutboundAttachmentsCard` component lets the operator
upload/remove customer-facing files during the process page; approval +
process pages now read the new fields.
Outbound Send workflow (deployed as `PIEAkPYYYZLGl6Pj`):
- Old workflow (id `WF_Email_Send_SMTP`) and stale repo file
(`WFDASH — Quote Outbound Send.json`) replaced.
- New revision intersects `quotes.outbound_attachments` with
`email_outbound_attachment_ids`, downloads each from
`/api/files/quotes/<id>/<token>`, and emits
`options.attachments: [{name, content, type}]` consumed by
`Send Email (SMTP)` (PO Send pattern).
- Failure path flipped from `send_failed` to `mail_failed` so
`retryQuoteEmail` can find failed sends (it only accepts the
latter). Previously failed sends were stranded.
Email Monitor (v5) workflow fixes:
- Reply-branch auth: any authenticated request now passes through;
pre-fix bug rejected branches that the IMAP trigger hadn't seen yet.
- Reply detection: bulletproof subject regex
(/Q-?\d{4}-?\d{2}-?\d{3}/) accepts both legacy `Q-2026-09-246`
and current `Q202609246` formats; canonicalised digits-only for
PB `~` substring match.
Delivery notes: new pricing endpoint at
/api/v1/delivery-notes/items/[id]/pricing + service-layer methods.
Snapshots kept for forensics: PRE-FIX-REPLY-{BRANCH-AUTH,DETECTION}.json
and push-payload.baseline.json are the previously-deployed workflow
shapes, retained so we can diff/rollback if regression hits.
Verified:
- PUT workflow PIEAkPYYYZLGl6Pj -> 200, active, 25 nodes
- quotes-service.test.ts: 117 pass (4 pre-existing failures unchanged)
- mcp/route.test.ts: 118 pass (2 pre-existing failures unchanged)
- node --check on new Code node JS passes
Docs: docs/quotes-outbound-attachments-deploy.md covers the deploy
procedure; docs/api-mcp-layer.md description sync.
setDeliveryNoteOrder only ever wrote the `order` relation. Delivery
notes created while the fields+expand bug (see prior commit) was live
got saved with customer/company/order all blank — this setter is the
only existing entry point that touches the order relation, so it now
also fills customer/company from the linked order when the DN's own
values are blank (never overwrites an operator-set value). Applies on
both the write path and the idempotent-order-unchanged path, so
re-running the same call is enough to finish backfilling an affected
DN once the order is already linked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
`order_items.order_qty` is the SUPPLIER-facing quantity — what prints
on the PO. Five call sites read it as the customer quantity, which is a
money bug in both directions:
- supplier MOQ (customer 4, we buy 10) → invoiced 10, over-billing 6
- shorted line (customer 100, supplier covers 40) → invoiced 40,
under-billing 60 until the operator splits the remainder
The invoicing screen never fetched `quantity` at all (zero occurrences
in page.tsx), so it could not bill the customer quantity even in
principle. Added to the PB field projection, which is load-bearing:
drop it and PB returns undefined and every line silently falls back to
order_qty. A page-level test now fails if the projection drifts.
`maxSurplusFor` measured the surplus as `collected - order_qty`, which
on an MOQ line is always 0 — the overflow was mathematically unbookable
and vanished from inventory by construction, not by operator error.
Four call sites built delivery-note lines by hand; two passed
`quantity` and two forgot it, so the same DN printed the customer's 4
via the service layer and the supplier's 10 via the API routes. All
four now share `buildDeliveryNoteLine`, so they cannot diverge again.
`order_qty` had no doc comment at all next to an 11-line one on
`quantity` — that absence is why five subsystems guessed differently.
Now documented, with a new `src/lib/order-item-qty.ts` holding
`customerQty` / `supplierQty` as the single definition.
driver-stops.ts and collection-confirm/po-card.tsx read `order_qty`
CORRECTLY (they check what the supplier delivered against what we
ordered from them) and carry comments saying so, so the next cleanup
doesn't "fix" them into breaking.
23 new tests. Full suite: no regressions — the 15 failures are
identical before and after (verified by stashing and re-running).
Audit: docs/superpowers/specs/2026-08-30-quantity-handling-audit.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Added support for deduplication of orders based on the `email_message_id`, preventing duplicate entries during email intake.
- Introduced the `displayContactName` utility to sanitize and format contact names, improving the display of order details by removing extraneous whitespace.
- Updated relevant order-related components to utilize the new utility for consistent contact name rendering.
- Enhanced tests to cover the new deduplication logic and the functionality of the `displayContactName` utility, ensuring robust behavior in various scenarios.
- Introduced a centralized `deliveryNoteLetterheadCode` function to ensure all delivery-note PDFs consistently use the Westcott Group letterhead, regardless of the subsidiary.
- Updated relevant API routes and services to utilize the new function, removing previous hardcoded fallbacks.
- Enhanced the `DriverStop` interface to include timestamps for better reporting capabilities.
- Added new fields to track creation timestamps for driver stops, improving data accuracy for outstanding reporting.
- Introduced a new `DeliveryNoteRow` type to track delivery notes alongside items, improving the organization of delivery data.
- Updated the `DeliveryListResponse` type to include delivery notes, allowing for better integration with the existing delivery management system.
- Enhanced the `groupByCustomerOrder` function to accommodate delivery notes, ensuring they are grouped correctly with their respective orders.
- Implemented state management for delivery notes in the `DeliveriesPage` component, including loading and error handling.
- Added search functionality for delivery notes, enabling operators to filter by delivery note number, customer PO, and order details.
- Updated the UI to display delivery notes alongside items, improving the overall user experience in the deliveries management interface.
- Enhanced tests to cover new delivery notes functionality and ensure correct behavior across various scenarios.
- Added a new service function `markDeliveryNoteCompletedByInvoice` to auto-close delivery notes after successful QuickBooks invoice creation, updating the status to "completed" and appending an audit banner to the notes.
- Enhanced the delivery note detail page with a modal for creating invoices via QuickBooks, including customer selection and billing company options.
- Implemented state management for the invoice modal, including loading states and error handling for customer fetching.
- Updated tests to cover the new auto-close functionality and ensure correct behavior across various scenarios.
- Improved documentation for the delivery notes service to reflect the new functionality and its usage.
- Fix resolveInvoiceLineDefaults to prioritize customer order quantity over supplier-collected quantity, ensuring invoices reflect what the customer actually ordered
- Add surplusQty utility function to calculate supplier MOQ overflow that should be booked to stock via /api/v1/stock/surplus-book
- Add surplus stock indicators in order and delivery note modals with descriptive tooltips explaining the surplus amount
- Expand supported letterhead codes to include WESTSCOTT GROUP, and configure all delivery notes to use the Westcott Group letterhead globally per business branding rules
- Improve error handling and logging in the delivery notes API POST route to include PocketBase error details and request context for easier debugging
- Add comprehensive unit tests for the updated invoice line logic and new surplusQty function to prevent regressions
- Fix delivery note PDF signature section overlapping page contents by removing fixed absolute positioning
- Add client-side picker for duplicate order numbers when merging delivery notes
- Add orderId parameter to combine delivery notes API for direct order lookup
- Update resolveDeliveryNoteLineQty to prioritize operator-edited quantity first
- Refactor combine delivery notes service and API to handle order lookup errors and candidate selection
Add inline quantity editing for delivery note items:
- New PATCH API endpoint and service function to update item quantities
- Optimistic UI updates on the detail page with error and loading states
- Input validation and status gating for draft/sent delivery notes
Add bulk delivery note combine functionality:
- New POST API endpoint and service to merge multiple draft/sent notes
- Row selection support in DataTable across pagination
- Confirmation modal with selected notes preview and optional order linking
- Automatic audit trail updates for merged and cancelled records
Extend OrderItem TypeScript interface with optional order expand field
Fix minor type casting issues in createAdHocDeliveryNote utility
Add new POST API endpoint at /api/v1/delivery-notes/[id]/regenerate-pdf to re-render and save delivery note PDFs from current line items. Add a "Regenerate PDF" button to the delivery note detail page with loading state and single-flight protection to prevent duplicate requests. Update ad-hoc delivery note creation to automatically attach PDFs to order items so print and download buttons work immediately after creation. Add helper utilities for resolving relation names and calculating line item quantities for PDF rendering.
Update the cancel delivery note dialog description to correctly state items are marked as cancelled
Fix PocketBase expand field whitelisting for delivery items to avoid missing related entity data
Change order item status to "cancelled" instead of "completed" when cancelling a delivery note, with updated JSDoc explaining the choice of status
Update the buyer tasks PO sent filter and its regression test to cover all current ItemStatus values
Add backend API routes and service functions for delivery note status updates with strict validation rules
Add UI action buttons and cancellation confirmation modal to the delivery note detail page
Update table layouts and styling across delivery notes and invoicing pages to improve responsiveness
Fix text overflow issues by adding whitespace-nowrap and break-words classes
Refactor order items table to display status inline and reduce redundant layout code
This commit adds several key features, improvements, and cleanups across the codebase:
- Add `order_items.confirm_collected` MCP/REST endpoint to mark order items as collected without requiring driver assignment
- Add standalone order creation and order split APIs for flexible order management
- Update order list sort to oldest-first to align with user workflow of working through oldest orders first
- Improve order search to show cross-tab matches and display source tab indicators on search results
- Add customer name similarity check to warn about mismatched QuickBooks customers during invoice creation
- Remove supplier name column from delivery note PDFs and clean up related data passing across delivery note routes
- Add validation and debug logging to ItemIntelPopover for missing item ID on apply actions
- Remove unused OrderItemContextPopoverTrigger component from order detail page
- Add delivery note regeneration script and update test suites for new and existing functionality
- Update documentation and add test coverage for the new collection confirm tool