Refine the handling of order_number values in the MCP API. Custom free-text order_numbers are now allowed to repeat across orders without triggering collision checks, while only values formatted as ORD-YYYY-MM-NNN will be subject to collision verification. Update tests to reflect this new behavior and ensure proper functionality.
The route accepted ?skip=N but always called getList(1, ...) — every
chunk re-processed the FIRST limit orders, leaving offsets > limit
unprocessed. Convert skip to page (floor(skip/limit) + 1) and add a
regression test asserting getList is called with the right page.
The backfill is N+1-bounded (each order = 1 + N candidate queries),
so a 5,300-order table takes ~3 hours at ~25 orders/min on a busy
PB instance. Splitting into 200-order chunks keeps each call under
Next.js's maxDuration (10 min) and gives the operator a clear
progress signal: every chunk returns nextSkip + done.
`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>
Task 9 of the non-invoiceable-order-extras plan.
order_items.create MCP tool:
- Input schema gains an optional boolean 'exclude_from_invoice'.
- Tool description rewritten to explain the supplier-side component
use case, the PO + driver + collection flow, the silent drop from
the customer invoice and delivery note, and the force-zero selling
price.
- operationData hint / nextActions block updated — the prior text
documented a '409 order_locked' refusal that no longer fires
(Task 2 removed the pending gate).
- Handler forwards the boolean to createOrderItemForOrder (which
itself forces selling_price=0 when the flag is set — Task 2).
Also fixed a TypeScript strict-mode complaint in the T7 test that
the previous commit left behind (vi.fn mock-call tuple type
needed an explicit parameter so mock.calls[0] is typed).
Co-Authored-By: Claude <noreply@anthropic.com>
Task 8 of the non-invoiceable-order-extras plan.
Two regression tests, no production code change. The picking-slip
feature relies on a property of the existing print pipeline:
excluded rows DO receive item_status = 'invoiced' (Task 7 / n8n
batch flip) and DO carry the invoice number / DN number, so the
print queries pick them up automatically. The picker must still
pull the washers even though they never reach the customer.
- print-bundle: explicitly verifies the picking slip renderer is
called with both billable and excluded rows (mocked
PickingSlipPdf → assert lines array has both descriptions).
- delivery-note-print: explicitly verifies the PB filter does NOT
carry an exclude_from_invoice clause — adding one would
silently filter the picker slip and break Task 7's design.
Co-Authored-By: Claude <noreply@anthropic.com>
Task 7 of the non-invoiceable-order-extras plan.
The status flip + audit stamp + delivery_note header create all
run on EVERY row so the order closes normally — excluded rows are
real operational lines from the supplier's POV. The PDF line
list is built from billable.items only, so the customer never
sees excluded lines.
Added exclude_from_invoice to the getFullList projection (load-
bearing — omit it and the billable filter silently passes
undefined, every extra lands on the customer delivery note).
New 400 guard 'No billable lines for a delivery note' when every
row in the request is excluded. UI guard in Task 6 prevents the
operator from reaching this path, but the route has its own
defence so the picker doesn't get a guaranteed-to-fail submit
silently.
2 tests + 1 mutation-test prove the split:
- excluded rows still get status flip + DN number + PDF upload,
but the renderer is called with ONLY billable items
- all-excluded returns 400 before any PATCH fires
- mutation (PDF built from items): the mixed test fails because
the renderer sees 3 lines instead of 2
Co-Authored-By: Claude <noreply@anthropic.com>
Task 3 of the non-invoiceable-order-extras plan.
- New POST /api/v1/orders/{id}/items route wraps
createOrderItemForOrder. Same-origin browser auth (PB session
cookie), mirrors the posture of /quote after the order-quote
migration.
- AddItemSheet component on the process page: right-side Sheet
(mirrors PriceEditSheet's layout) with description / quantity /
cost / selling / supplier / notes + the load-bearing
'exclude_from_invoice' checkbox. When ticked, the selling-price
input is forced to 0 and disabled, a 'Not invoiced' badge replaces
the margin chip so a -100% red chip on every extra is noise.
- Add Item button below the item card stack — always available,
not gated on item_status. The empty-state copy above ('No line
items on this order. Add items before processing.') now points at
a real action.
- Existing excluded rows render with a muted 'Not invoiced' badge
in the card stack so the operator can spot them at a glance
without opening the sheet.
- The server (Task 2) forces selling_price=0 from the flag
regardless of caller input — the client forcing here is for UX
consistency.
Co-Authored-By: Claude <noreply@anthropic.com>
Thin wrapper around suggestDeliveryNoteForOrder(id). The route
mirrors the quote matcher's /quote-match endpoint for the case
where an order's items were edited after intake, or the order
predates the automatic intake check.
Co-Authored-By: Claude <noreply@anthropic.com>
Closes the same vanishing-buyer-notes bug B12 fixed on the process
page, this time on the order detail page that operators see first.
- Inline row indicators (MessageSquare icon for a buyer note,
Paperclip + count for per-line attachments) inside the Description
TableCell. Hidden when the line has no signal. Click toggles a
QuoteLineContext panel below the row.
- QuoteLineContext panel wrapped in a Fragment-keyed extra row,
colSpan=8 (matches the eight TableHead columns: Description, Qty,
Selling, Supplier, Cost, Total, Status, Edit). The plan called
for colSpan=9, but the Driver column was removed 2026-08-24 and
the live count is 8.
- quoteAttachments intentionally empty in the detail-page panel —
order-level attachments have their own card below the table.
Migration off pb-helpers (AGENTS.md §'data access layer discipline'):
- /api/v1/orders/{id}/quote route now treats empty quote_id as the
unlink path (clears quote + potential_quote + matching fields),
and dropped assertAgentAuth so the page can call it from a
browser session (admin auth happens via the PB session cookie at
the PocketBase layer; the MCP forwarder still works without that
gate because the MCP route gates at its own boundary).
- /api/v1/orders/{id}/quote-match is a thin wrapper around
suggestQuoteForOrder (returns the new SuggestQuoteResult shape —
best.quoteId / best.quoteNumber, not the old best.quote.id /
best.quote.quote_number).
- Page migrated four pb-helpers calls: findMatchingQuotesForOrder →
POST quote-match, applyOrderQuoteMatch → merge into quote-match +
QuoteMatchCard accepts the result via SuggestionCard from the
fetched order, clearOrderQuoteMatch → POST quote with empty
quote_id, setOrderQuoteManually → POST quote with the picked id.
- runMatch signature simplified — accept-vs-suggest was the old
split; the new endpoint only ever suggests. 'Accept' now goes
through linkQuoteManually via POST /quote.
- Stale matcher block (4 functions + 3 helpers + re-exports +
imports) deleted from pb-helpers.ts. The pure scoring helpers now
import directly from @/lib/server/order-quote-match.
Known follow-up: QuoteMatchCard still reads from matchResult (a
QuoteMatchResult shape). Clearing matchResult after runMatch means
the SuggestionCard renders empty until the next fetchOrder lands.
Reads from order.expand.potential_quote after that. A future task
can rewrite the card to drop the in-memory result entirely.
Co-Authored-By: Claude <noreply@anthropic.com>
The order detail page and process page need the pairing, not the
matcher — until now the process page was doing an exact/substring
guess at render time and silently hiding buyer notes when the PO's
wording differed from the quote's.
- matched-attachments response gains linePairs (keyed by order_item_id,
carrying quote_item_id, confidence, tier). pairOrderLines is called
defensively — a pairing failure cannot kill the attachments
response (catch returns null → empty linePairs object, the rest of
the payload still serves).
- POST /api/v1/orders/[id]/quote-line writes the operator's confirm
decision (or unlink with empty quote_item_id) onto
order_items.quote_item. Guards against patching a line that
belongs to a different order (404 otherwise).
Both routes are additive — the existing matched-attachments fields are
unchanged, the MCP orders.list_attachments consumer keeps working.
Co-Authored-By: Claude <noreply@anthropic.com>
Hermes (n8n) drops the AGENT_API_KEY on direct REST calls, so anything
n8n needs to run must exist as an MCP tool. This forwards the agent's
JSON-RPC call to POST /api/v1/orders/quote-match/sweep, mirroring the
existing orders.set_quote forwarding pattern.
Updated the tools/list expected-list test to include the new tool so
the catalog stays self-documenting.
Co-Authored-By: Claude <noreply@anthropic.com>
The matcher only runs on orders created from now on. This sweeps the
historical backlog plus any order whose lines changed after creation.
- sweepUnmatchedOrders in order-quote-link-service.ts walks
quote_matching_attempted = false && quote = '' (the codebase's
pattern for an empty relation, verified against orders-service.ts
usage) in batches, projecting only id (the wider order fields are
re-fetched inside suggestQuoteForOrder).
- bucketAgeGaps exposes the histogram that lets FUZZY_WINDOW_DAYS be
re-tuned from real data instead of the 90-day estimate.
- POST /api/v1/orders/quote-match/sweep is the agent-auth surface
with maxDuration=300 for the 500-order worst case.
Tests cover the filter shape, the field projection (load-bearing —
over-projection wastes wire bytes), the no-op on no-match path, the
fault tolerance (one row throwing doesn't kill the batch), and all
six histogram bucket boundaries including the ≤ N vs < N edges.
Co-Authored-By: Claude <noreply@anthropic.com>
When a mine puts a spot out to tender, several engineering companies
each ask us to price the same item list. Before pricing a new one the
operator wants to see what the earlier quote went out at.
The per-line answer already existed: populateItemIntel powers the ⓘ
popover on each row and returns recent_quote (prior quote number,
company, selling price, days ago). This adds no new matching logic —
it runs that same lookup across every line of a quote and rolls the
results up, so the operator gets one panel instead of clicking ⓘ on
each row.
What the roll-up adds over the per-line popover:
- "N of M lines have been quoted before" at a glance
- relatedQuotes ranked by HOW MANY of this quote's lines they also
carried; a prior quote covering 5 of 5 is very likely the same
tender from a different bidder, one covering 1 of 5 is a shared
commodity line
Two honesty details, both driven by what the first live run showed:
- match confidence is surfaced as close/likely/loose. The underlying
matcher accepts anything above 0.5, and a live check returned a
R150 hit against a magnetic base drill — a loose row must not look
as trustworthy as a close one.
- an unpriced prior quote renders "unpriced", never R0.00, which
would read as "we quoted it at zero".
Read-only: passes write:false so viewing the panel doesn't persist
intel onto rows the operator only looked at. On-demand rather than
auto-loading, since it costs one intel computation per line
(~400-650ms each, batched 5 at a time, capped at 40 lines).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The archive's search fetched the entire recency-windowed matching set
(thousands of rows once "Include older" is on) and substring-filtered
it in memory, taking several seconds per keystroke; it now delegates
to the same ranked pushdown search service the main /quotes list uses.
Also fixes pagination being hidden while searching (results past page
1 were unreachable) and the match count showing the current page's
size instead of the true total.
Adds the chevron item-preview toggle to archive rows (extracted into
a shared QuoteItemsExpanded component, backed by a new GET
/api/v1/quotes/[id]/items route), removes a duplicate "Customer or
Company" label in the new-quote form, and drops the stray
warning-colored search box styling that leaked from DataTable onto
every list page that uses it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Updated the search input handling to improve responsiveness by integrating a debounced search mechanism, reducing latency from 550 ms to approximately 300 ms.
- Added keyboard shortcut support for focusing the search box, enhancing accessibility and usability for frequent users.
- Improved the search placeholder to reflect all searchable fields, providing clearer guidance to operators.
- Introduced a clear-search button to streamline user interactions, allowing quick resets of the search input.
- Enhanced the display of search results with a live result count badge, providing immediate feedback on search outcomes.
These changes collectively enhance the search experience for operators, making it more intuitive and efficient.
- Added `email_sender`, `email_subject`, and `email_body` fields to the order creation input schema for improved traceability of inbound emails.
- Updated logic to include these fields in the order creation process, allowing operators to track the source of orders without needing follow-up updates.
- Enhanced documentation to reflect the new optional fields and their purpose in the order management workflow.
This update improves the audit trail for orders and enhances user experience by linking orders directly to their source emails.
- Introduced `order_number` field in the order creation input schema, allowing users to provide a custom identifier (e.g., PO/reference).
- Implemented logic to handle empty or whitespace values, defaulting to auto-generated order numbers.
- Added error handling for order_number collisions, returning structured error responses for better client-side handling.
- Updated documentation to reflect the new order_number functionality and its implications.
This enhancement improves order management flexibility and user experience.
Gary (and any future rep) forwards inbound RFQs to a shared mailbox.
Today the customer linked to the resulting quote is the rep (or the
Unknown Sender sentinel), because the only resolution path is exact
email match — the from_email resolves to Gary, not the actual customer.
Add person_hint to EmailIntakePayload. The AI extraction layer reads
the subject / body / signature for the customer being quoted FOR and
fills person_hint. findOrCreateCustomer now walks three resolution
passes in order:
1. Exact email match (existing behaviour — direct RFQs unchanged)
2. person_hint resolution: case-insensitive substring on
customer.customer, with a companyHint tie-break when multiple
candidates exist
3. Existing sender-based create-new path (Gary wins, quoter
reassigns in the WFDASH UI)
MCP schema for quotes.create_from_email surfaces the new field with
doc explaining the rep-forwarded case. /api/v1/quotes/from-email
doc updated. AI Agent system prompt in the n8n workflow doc now
extracts person_hint with worked examples.
Also extends the mock filter parser to accept PB's bare ~ operator
(production code uses ~; the mock previously only accepted =~).
5 new unit tests + 2 integration tests for the new path.
Co-Authored-By: Claude <noreply@anthropic.com>
Typecheck cleanup after the deploy blocker push — `tsc --noEmit` was
silent on `d11a184` baseline but ~60 errors hid across test files
that only the compiler surfaces (the build was clean because test
files are excluded from the prod bundle). Touched:
- src/app/api/mcp/route.test.ts (16) — generic on `vi.fn()` for
tuple inference; `as unknown as` two-step cast on record literals
- src/app/api/mcp/po-agent-workflow.test.ts (1) — spread instead of
duplicate key
- src/components/buyer-tasks/buyer-task-group-card.test.tsx (5) —
`PurchaseOrderEmailPayload` now requires `subject`/`body`
- src/components/collection-confirm/confirm-stock-dialog.test.tsx (1)
— removed shadowed `id` from `makeRow` overrides
- src/components/item-intel-popover.test.tsx (2) — missing row
fields + boolean→object for `verified`
- src/components/quotes/process-item-card.test.tsx (2) — full
QuoteItem stub; generic on `vi.fn()`
- src/components/sprint-item-pricing-modal.test.tsx (3) — full
SprintItem/QuoteItemAttachment/QuoteAttachment stubs
- src/lib/server/orders-service-review.test.ts (~60) — annotated
`getOne` return type to break union widening on the expand option
- src/lib/server/orders-service.test.ts (2) — removed bogus
`includeArchivedCounts` param; `rfq_number` now whitelisted on Order
- src/lib/server/quotes-service.test.ts (3) — two-step cast on
Quote header; `unit?` now on QuoteItem
- src/lib/server/stock-purchase-items-service.test.ts (~10) — typed
`pbMock` via `StockPurchaseItemsPb` so `*WithPb(pb,...)` checks
Also added two optional fields to `src/lib/types.ts`:
- `QuoteItem.unit?` — pre-2026 rows have no unit, so it's optional
- `Order.rfq_number?` — populated by the n8n email agent, inherited
verbatim by `splitOrder`
Build: clean. Typecheck: 0 errors. Tests: 14 pre-existing runtime
failures + 1 pre-existing unhandled error in `supplier-form.test.tsx`
remain — confirmed they exist on `d11a184` baseline, so they are
outside the cleanup scope and unchanged by this commit.
Co-Authored-By: Claude <noreply@anthropic.com>
The /api/v1/quotes/create route was wired to accept operator-supplied
quote_number + rfq_number + quote_notes + notes + status, but the
service-layer side never matched: QuoteNumberExistsError didn't exist
and CreateQuoteInput ignored the new fields. Production deploy was
failing on `Module has no exported member 'QuoteNumberExistsError'`.
Service changes:
- New QuoteNumberExistsError class (mirrors OrderNumberCollisionError):
carries existingId + attemptedNumber, mapped to 409 by the route
- CreateQuoteInput extended with quote_number, rfq_number, quote_notes,
notes, status; imported QuoteStatus from @/lib/types
- createQuote now uses input.quote_number verbatim when supplied, else
falls back to the existing atomic-counter generator
- createQuote's pb.create call is wrapped in try/catch: PB's UNIQUE
constraint violation on quote_number is re-thrown as the typed error
with the colliding row's id resolved via a follow-up getList
New route test file (9 cases) covers happy path with supplied
quote_number, the 409 collision branch, notes/quote_notes fallback
merge, auth-gate failures, missing customer/items validation, and
generic 500 fallback. Uses importOriginal so the real
QuoteNumberExistsError class survives the service-layer mock.
Co-Authored-By: Claude <noreply@anthropic.com>
Closes the gap that the MCP quotes.create tool advertised is_no_quote
but the upstream POST /api/v1/quotes/create chain silently dropped the
flag. Three additions:
- CreateQuoteInput interface gains the field
- createQuote() forwards it to pb.collection.create(...)
- POST handler forwards body.is_no_quote to createQuote()
Spec test case 10 ("quotes.create with is_no_quote: true persists
flag and returns quote") now passes end-to-end.
Co-Authored-By: Claude <noreply@anthropic.com>
Added support for additional fields in the quote creation process, including quote_number, rfq_number, quote_notes, and status. Updated the error handling to return a specific 409 response when a quote_number conflict occurs, improving the API's feedback for clients. Enhanced documentation to reflect these changes.
Lets Hermes and other external agents drive the no-quote flow without
a UI step. Hints document the gate-bypass and reversibility.
Five touch points in src/app/api/mcp/route.ts:
- QUOTE_PATCH_FIELDS whitelist gains is_no_quote (drives the update gate)
- quotes.create input shape declares the boolean
- quotes.update input shape declares the boolean
- quotes.update description text lists is_no_quote in the whitelist
- quotes.create handler forwards args.is_no_quote to the upstream body
The quotes.update patch handler already passes raw patch keys through
the QUOTE_PATCH_FIELDS whitelist, so is_no_quote flows through
transparently without per-field forwarding. quotes.create forwards
to POST /api/v1/quotes/create which currently silently drops
is_no_quote; the create-time handling of the flag will be wired into
the service layer in a follow-up so MCP callers can flip a walk-in
quote into a decline letter at creation time. The schema hint is
live today.
Smoke verification deferred to Task 11 (no-quote E2E). Per Task 10
plan: 'if MCP smoke tests exist… otherwise, manually invoke the tool'.
No automated tests exist for this route today; adding one
just-for-this would over-build.
Co-Authored-By: Claude <noreply@anthropic.com>
The downstream status-flip logic uses ready.length === allItems.length
to choose between approval_pending and partial_approval_pending. For
an is_no_quote quote with all-unpriced items, the unfiltered bypass
in Task 7 made ready.length === 0 while allItems.length > 0, flipping
to partial_approval_pending instead of approval_pending — contradicting
the spec ('new → approval_pending → sending → emailed' for no-quote
letters).
Add a guard so is_no_quote quotes always flip to approval_pending.
The partial-flip path remains unchanged for priced quotes.
Lets the PDF render with all active items (regardless of price)
when the operator has marked the quote as a decline letter. The
existing PDF diagnostic still flags data issues for priced quotes.
Also adds the (unused) noQuote prop to QuoteDocumentProps so the
route compiles. Task 8 will wire the rendering.
Adds the new field to the PATCHABLE_FIELDS whitelist, validates
type as boolean, and dispatches to a new setIsNoQuote service helper.
Tested via mocked route handler.
- Updated the `scoreQuote` function to incorporate price scoring, allowing quotes to be evaluated based on selling and cost prices when the query is numeric.
- Introduced new weights for item selling and cost prices, ensuring they contribute to the overall score only when relevant.
- Enhanced the test suite to cover new price matching scenarios, ensuring accurate scoring behavior for quotes based on price inputs.
- Refactored related functions to support the new price scoring logic, maintaining consistency across the scoring process.
- Updated the `orders.add_attachment` and `orders.list_attachments` tools to utilize the order's own `order_attachements` field, allowing immediate visibility of attachments without requiring a matched quote.
- Enhanced the `POST` route for uploading attachments to support multiple files and improved response structure to include `collectionId` and `collectionName`.
- Merged attachment sources in the `GET` route for matched attachments, providing a unified view of order-level and quote-level files, with a new `source` field to indicate the origin of each attachment.
- Added utility functions for inferring MIME types and attachment kinds based on file extensions, ensuring consistent handling across the application.
- Improved documentation for clarity on the attachment handling process and updated response formats to reflect the new structure.
- 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.
- Modified the file handling in the repair details page to use the correct repair ID and simplified the key assignment for photo files.
- Updated the API route to cast the status parameter to the new RepairStatus type for improved type safety.
- Expanded the RepairFileField type to include "delivery_note" for better file management.
- Removed unused helper functions to clean up the codebase and improve maintainability.
- Introduced a new toggle in the quotes and archive pages to allow users to include older quotes beyond the default 90-day cutoff.
- Updated the search parameters in the quotes and archive APIs to handle the new `includeOlder` flag, enabling full history searches.
- Enhanced the user interface to reflect the current search scope, providing clear feedback on whether the search is limited to recent quotes or includes the full archive.
- Adjusted the quotes search service to support the new functionality, ensuring efficient retrieval of older quotes when requested.
- Added relevant tests to verify the correct behavior of the new search feature and its integration with existing functionality.
- 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.
- Added `drv_id` to the stock row data structure to ensure proper assignment tracking.
- Implemented a new `isReadyForCollection` function to streamline the logic for determining if stock rows are ready for collection.
- Updated the `buildVisibleRows` function to utilize the new readiness check, improving the accuracy of row filtering based on the ready status.
- Added a test case to verify that stock rows are correctly included in the ready filter, ensuring expected behavior in the collection pool view.
- Enhanced coordinate parsing logic to handle various input formats, improving robustness in the driver stops API.
- Introduced a new `quote_notes` field to store operator-facing free-text notes, distinct from the existing `notes` field used for rejection audit logs.
- Updated the quote processing page to include a `QuoteNoteCard` for editing and displaying the operator notes, ensuring seamless integration into the PDF generation flow.
- Enhanced the PDF generation logic to render the operator notes in a blue callout below the totals, improving visibility for end-users.
- Implemented a new service function `setQuoteNotes` to handle the saving of notes, including trimming whitespace and handling non-string inputs defensively.
- Added unit tests for the `setQuoteNotes` function to ensure correct behavior and data integrity when updating notes.
- Updated relevant types and documentation to reflect the new functionality and its intended use cases.
- Implemented a new split mechanism for order items, allowing operators to split quantities based on in-stock and non-in-stock statuses.
- Updated the payload for creating split order items to ensure it adheres to the PocketBase schema, avoiding empty strings for relation fields and removing deprecated fields.
- Enhanced error handling in the split route to log unexpected errors for better debugging.
- Improved the UI to conditionally render split buttons based on item availability, ensuring a clearer user experience.
- Added unit tests to validate the new splitting logic and payload structure, ensuring robustness against schema changes.
- Introduced a new `ConfirmStockDialog` to handle stock collection confirmations, allowing operators to review per-line quantities and any short lines before finalizing the action.
- Updated the `CollectionConfirmPage` to manage the new confirmation state and trigger the dialog upon clicking the "Confirm collection" button.
- Enhanced tests to cover the new dialog functionality, ensuring correct behavior for both full and short quantity lines during the confirmation process.
- Improved documentation to clarify the flow and user interactions with the new confirmation dialog.
- Introduced a new `handleQtyChange` function to manage per-row quantity updates for both customer and stock items, ensuring correct data submission to the appropriate endpoints.
- Enhanced the `dedupeAttachments` function to handle additional document sources, including parent purchase orders, and skip empty filenames to prevent 404 errors.
- Updated the `ItemsTable` and `PoCard` components to support quantity editing, allowing users to modify collected quantities directly in the UI.
- Added tests to validate the new quantity handling logic and attachment deduplication, ensuring robust functionality across various scenarios.
- 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.
- Updated the `GET /api/v1/orders` endpoint to support additional query parameters including `customer`, `createdAfter`, `createdBefore`, and `status`, allowing for more granular order filtering.
- Implemented logic to resolve customer IDs from names and aggregate order items by their statuses for improved data retrieval.
- Introduced new helper functions in `orders-service.ts` for fetching order statistics and daily summaries, enhancing reporting capabilities.
- Added comprehensive documentation for the new query parameters and their usage in the API documentation.
- Improved error handling for invalid customer and status inputs, ensuring clearer feedback for API consumers.