Commit graph

516 commits

Author SHA1 Message Date
Dave
dc84ce0f30 feat(quotes): resolve customer from person_hint when rep forwards email
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>
2026-08-27 10:45:51 +02:00
Dave
4d8382c87f fix(quotes): wire no-quote toggle into process-page UI gates
The server-side PDF route was already no-quote-aware — when
`quote.is_no_quote === true` it bypasses the pricing gate
(`pdf/route.ts:156, 238, 240`) and the PDF component renders "Not
quoted" cells + "—" totals (`quote-document.tsx:239, 242, 276/280/285`).
But the client side was ignoring the flag, so flipping the toggle
looked like a no-op. Five one-file fixes to close the gap:

1. `itemsReady` now short-circuits to true when no-quote is on.
   Before: `itemsReady = items.length > 0 && missingSupplier === 0 &&
   missingPricing === 0;` (always evaluated pricing). After: prepend
   `isNoQuote ||` so the Generate Quote button enables regardless of
   cost/selling prices. (Bug A)

2. `applyPricing` markup>0 guard now bypassed when no-quote is on.
   Before: toast "Markup must be greater than 0" + refuse save when
   markup ≤ 0 — blocked the operator from saving a 0/0/0 row. After:
   `if (!isNoQuote && ...)` so the operator can save the row. (Bug B)

3. `handlePricingChange` auto-derive now skipped when no-quote is on.
   The cost↔selling recompute on every keystroke (`value * (1 + markup
   / 100)` and inverse) was firing even when no-quote was set, which
   is what produced the "I set 0/0/1% markup and it shows 1 cent"
   artefact. Three branches (markup/cost/selling) now short-circuit
   to pass-through under no-quote. (Bug C)

4. Helper text under the Generate Quote PDF button now explains
   "No-quote letter — prices ignored on PDF (renders 'Not quoted'
   cells)" when the toggle is on, instead of the misleading
   "Price every item + assign a supplier before generating".

5. Second helper line under the toggle's label explaining the full
   UI flow when on: "the pricing form stays editable but isn't
   required, and the PDF renders 'Not quoted' cells with no totals."

Operator workflow now: flip toggle ON → enter description on a row
at 0/0/0 → save → click Generate Quote → PDF renders decline letter
with "Not quoted" cells and no totals. No PB migration needed
(`is_no_quote` boolean column already present per `pb-exports/pb_schema-2.json:2178`).

Build: clean. Typecheck: 0 errors. No tests added (manual flow; no
existing test exercises this toggle path).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 08:00:17 +02:00
Dave
a861de1fb6 docs(hermes): bump tool count + add 'three quote-creation tools' callout
Hermes filed WFDASH-dev-ask-quotes-create.md on 2026-08-26 claiming
`quotes.create` doesn't exist. It does — registered at
src/app/api/mcp/route.ts:1131, REST at /api/v1/quotes/create, added
2026-08-15 in Phase G gap-fill, documented as Recipe 7 in
docs/agents/workflows.md. The agent's actual failure was discovery,
not a real gap.

Two doc-only changes to keep this from recurring:
1. Tool count 125 → 141 (live count), recipe count 7 → 9 (live count)
2. New prominent "Three quote-creation tools — don't confuse them"
   section right under the intro listing all three paths
   (quotes.create / quotes.ingest_from_email /
   quotes.create_from_email) with the common failure mode explicitly
   called out. Recipe 7 already covers the walk-in workflow;
   this surfaces the contrast up front so the agent sees it during
   session-startup instead of after it has already picked the wrong
   tool.

No code change. No FEATURE-REQUESTS.md entry — the tool isn't a gap.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 07:50:17 +02:00
Dave
7986784eaf chore(tests): fix ~60 pre-existing TS errors across 11 test files
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>
2026-08-27 04:43:40 +02:00
Dave
d11a1848e8 feat(quotes): finish createQuote refactor for operator-supplied quote_number
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>
2026-08-27 04:19:51 +02:00
Dave
1717d62980 fix(approvals): 404 test coverage, exclude PDFs from outbound, drop dead pushMock
Three final-review fixes for the Approve & send from list feature:

1. The 404 branch added in 5760ef9 had no test guarding it. Add
   a test mirroring the invalid_status pattern: mock a 404 from
   review-approve, assert router.refresh() was called and
   toast.info fired with the 'no longer exists' message.

2. The list page's ensureOutboundAttachments fetched all attachments
   where direction='outbound' — including PDFs — and passed them
   straight to SendQuoteModal. The modal renders its own pdfAttachment
   above the outbound list, so an operator who re-uploaded a PDF as
   a proof would see it twice (once as always-attached, once as a
   selectable outbound). Mirror the detail page's filter
   (line 1217 of approvals/[id]/page.tsx) by adding 'kind != pdf' to
   the list page's outbound-attachments query.

3. The list page never calls router.push (it uses router.refresh
   for all branches), so the pushMock in the test scaffold was
   dead. Drop it.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 01:04:02 +02:00
Dave
5760ef9966 fix(approvals): address Task 2 code quality findings + test mock update
Four code-quality issues from review + one test-mock follow-up:

1. CRITICAL: isPlaceholderCustomer was called with a string arg but
   expects { id, customer, email } - TS error, and at runtime the
   function would always return true. Variable was dead (computed but
   never read) so remove the call, the explanatory comment, and the
   import entirely. The Option A behaviour (server handles placeholder
   via its 422) is preserved by simply not having the dead variable.

2. CRITICAL: toast(..., { type: 'info' }) doesn't compile under
   ExternalToast and silently drops the type option at runtime, so
   the invalid_status race toast renders with default styling instead
   of info (blue). Switch to toast.info(...). Updates the test
   assertion to check toast.info directly (the test mock defines
   toast.info as an independent vi.fn(), not a wrapper around toast,
   so the prior expect(toast).toHaveBeenCalledWith assertion would
   no longer match).

3. IMPORTANT: Spec asks for a dedicated 404 branch ('This quote no
   longer exists. Refreshing.') but the implementation fell through
   to the generic catch-all. Add the 404 branch between the mail-fail
   branch and the generic fallback.

4. NICE-TO-HAVE: formatCurrency was imported but never used. Drop.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 01:00:43 +02:00
Dave
911c6f7496 chore(approvals): drop dead handleApprovePartial
Task 2 collapsed the list's per-row "Approve partial" button into
the new "Approve & send" button, which routes through review-approve
on the server. The list no longer calls the legacy partial-approve
endpoint, but handleApprovePartial was left in the page file because
the plan's Step 3 only covered handleApprove replacement. Remove it.

The /api/v1/quotes/[id]/partial-approve endpoint itself is unchanged
(it's still used by other parts of the app).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 00:55:00 +02:00
Dave
a499329a4e feat(approvals): Approve & send from list rows
The pending-approvals list now offers the same one-step
approve-and-dispatch affordance as the detail page, instead of
forcing the rep through a two-step approve-then-drill-in flow.

- Replace the list's standalone 'Approve' button (direct PB write)
  and 'Approve partial' button (POST /partial-approve) with a single
  'Approve & send' button that opens SendQuoteModal.
- Lift the modal and sendApprove callback to page level, keyed by
  row id, callable per row.
- Submit posts to POST /api/v1/quotes/[id]/review-approve with
  action: 'approve' and the modal's email payload. The server
  atomically approves + dispatches.
- Same handler works for partial_approval_pending rows — server
  flips partial_approved -> sending -> emailed natively.
- Toast wording matches the detail page: 'Approved - email sent...'
  for full, 'Partial approved - email sent...' for partial, with
  invalid_status and mail-failed branches surfacing their own toasts.
- Reject path untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 00:53:45 +02:00
Dave
c0eca10b51 fix(test): drop stale triggerSend comment in approvals page test
The fix commit (27f60e1) removed the triggerSend hoisted spy and
wired the modal stub's button to call props.onSend directly, but
left a two-line comment in test 3 that still referenced the deleted
symbol. Replace the comment with one that describes the actual
mechanism so future readers don't go hunting for a triggerSend
identifier that no longer exists.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 00:48:14 +02:00
Dave
27f60e14ec fix(test): wire SendQuoteModal stub to props.onSend
The SendQuoteModal test stub had a noop triggerSend placeholder that
never called props.onSend, so the click on the modal's "Submit modal"
button would silently fail. Tests 3-7 (which assert on fetch /
refresh / toast effects from sendApprove) would all hang even after
Task 2 implements the page correctly.

Fix: call props.onSend directly from the button's onClick. Drop the
unused triggerSend hoisted spy. Tighten the stub's props type to
SendQuoteModalProps so Task 2's prop-name typos surface as TS errors.

Also widen the PB mock fallback's getList to return the full envelope
(items + totalItems + totalPages) so Task 2 reading quote_attachments
or any other collection doesn't crash on undefined totalItems.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 00:46:34 +02:00
Dave
097ada09f8 test(approvals): scaffold Approve & send from list tests
Component tests cover the row-button -> SendQuoteModal ->
review-approve submit flow including success, invalid_status,
and mail-failed error branches. All 7 fail; implementation in
follow-up commits.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 00:39:16 +02:00
Dave
5a9e0c8a70 docs(approvals): add approve-and-send-from-list implementation plan
Implementation plan for the spec committed in d87a007. Four tasks:
1. Scaffold 7 component tests (all fail initially)
2. Replace handleApprove + handleApprovePartial with lifted
   SendQuoteModal + sendApprove; new 'Approve & send' button
3. Verify partial-row test passes from task 2 wiring
4. Manual smoke test against running PB
2026-08-27 00:37:38 +02:00
Dave
d87a007be3 docs(quotes): add approve-and-send-from-list design spec
Lifts the detail-screen "Approve & send email" button behaviour onto
the pending-approvals list rows. Replaces the list's existing
two-step approve + drill-in flow with the existing combined
review-approve endpoint behind the same SendQuoteModal the detail
page already uses.

Out of scope: "approve without send" toggle in the modal, inline
markup-edit on a row.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 00:33:50 +02:00
Dave
721b0f6bf7 Merge branch 'agent/mika/6d6b5c094956' into main 2026-08-27 00:14:54 +02:00
Dave
e0d214c94f test(quotes): cover spec test cases #3 and #5 for no-quote
Closes the two IMPORTANT test-coverage gaps from the final
implementation review. Pure additions; no production code changes.

  - Spec test #3: submitQuote({partial:true}) accepts zero-priced
    items when is_no_quote=true (status flips to
    partial_approval_pending)
  - Spec test #5: strengthen the no-supplier + no-quote assertion
    to check the missing array directly, not just the failure.code

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 00:02:25 +02:00
Dave
87eacdc307 fix(quotes): persist is_no_quote through quotes.create path
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>
2026-08-26 23:58:43 +02:00
Dave
e51a405414 feat(quotes): enhance quote creation with new fields and error handling
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.
2026-08-26 23:55:30 +02:00
Dave
d4ff62c7b2 feat(mcp): expose is_no_quote on quotes.create and quotes.update
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>
2026-08-26 23:50:37 +02:00
Dave
6d9b6a8274 fix(quotes): address UI toggle code review
Addresses the 2 IMPORTANT and 4 NIT findings from the Task 9
code-quality review:

- Initial state bug: useState read 'quote' on first render when it
  was null, sealing isNoQuote as false. A previously-persisted
  is_no_quote: true quote would show the toggle as off. Replace
  with a useEffect that syncs once per loaded quote id.

- Dark mode: add dark: variants to the amber card border/background
  to match the project precedent (collection-pool/components.tsx:175).

- Label size: drop from text-sm to text-xs for consistency with
  every other <Label> in the file.

- Error surface: parse the PATCH 400 body and surface data.error
  rather than a generic toast, matching the lookupItem/generatePdf
  pattern in the same file.

- useCallback deps: stop recreating the callback on every toggle
  by stashing the current value in a ref.

- quote-notes-card id: add a comment so the wrapper div's purpose
  is clear.
2026-08-26 23:47:27 +02:00
Dave
0b6226cdf1 feat(quotes): add no-quote toggle to Quote Details card
Switch + Label inside the Quote Details card on the process page.
Optimistic update with rollback on error. When flipped on, scrolls
the quote_notes editor into view so the operator can type the
reason in the same flow.
2026-08-26 23:43:15 +02:00
Dave
6796dbea06 feat(pdf): add noQuote prop to QuoteDocument
Renders 'Not quoted' in item price cells, '—' in totals, and
changes the quote_notes callout header to 'Why we cannot quote'
when the flag is set. Visual identity stays the same (still a
QUOTATION document on letterhead).
2026-08-26 23:40:47 +02:00
Dave
a2e10652a4 fix(quotes): no-quote letters always flip to approval_pending
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.
2026-08-26 23:39:15 +02:00
Dave
cfcbbc5f10 feat(quotes): bypass PDF ready filter when is_no_quote
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.
2026-08-26 23:37:48 +02:00
Dave
1d6f7538d8 feat(quotes): skip pricing checks in diagnosePdfReadiness when is_no_quote
Keeps the 'no supplier' and 'not processed' checks active so the
function still flags legitimate data issues, but lets zero-priced
items through when the operator has marked the quote as a decline.
2026-08-26 23:31:41 +02:00
Dave
e74ef505e7 feat(quotes): skip submitQuote unpriced gate when is_no_quote
Lets a no-quote flag transition new → approval_pending even when
items have selling_price = 0. The PDF render path and email send
will relax the same way in subsequent commits.
2026-08-26 23:27:47 +02:00
Dave
bf91b601aa feat(quotes): expose is_no_quote on PATCH /api/v1/quotes/[id]
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.
2026-08-26 23:24:21 +02:00
Dave
1ef24ffe2f docs(types): correct is_no_quote JSDoc to describe decline letter
Previous wording conflated is_no_quote with the unrelated delivery-note
flow. is_no_quote is the official decline-letter flag — items render
with 'Not quoted' cells and the quote_notes callout becomes 'Why we
cannot quote'. Optional because existing rows pre-date this column.
2026-08-26 23:21:57 +02:00
Dave
403ac152e0 feat(quotes): add is_no_quote?: boolean to Quote interface
Mirrors the is_template boolean. Wires the new field through the
type system before any service-layer code touches it.
2026-08-26 23:19:45 +02:00
Dave
0f0712a1d7 feat(quotes): add is_no_quote boolean to schema
Required to mark a quote as an official decline letter. Gates relax
when set so operators can send a quote with zero prices on letterhead.
Field added to PB admin manually (UI-only change per AGENTS.md).
2026-08-26 23:17:21 +02:00
Dave
c3e9c74d61 docs(quotes): add no-quote feature implementation plan
11 tasks covering schema, type, PATCH endpoint, three service
gate relaxations, PDF route bypass, PDF component rendering, UI
toggle on Quote Details card, MCP tool exposure, and an E2E
smoke test. TDD throughout with file:line citations to the
existing service tests as templates.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-26 23:13:02 +02:00
Dave
670ed791ec docs(quotes): add no-quote feature design spec
Adds is_no_quote boolean to quotes collection so operators can send
an official decline letter on a quote the company cannot price.
Spec covers schema, service-layer gate relaxations, PDF rendering,
UI toggle placement, error edges, tests, and rollout.

Approach A (surgical flag relaxation) chosen over B/C during
brainstorming to keep v1 minimal and reversible.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-26 23:08:08 +02:00
Mika
a105c2c449 fix(delivery-notes): move 'Add item' to per-card button after each row
Step A — move the 'Add item' button from the top of the items
section to the bottom of the items list.

Step B — match the quote-form.tsx pattern: every item card is
followed by its own ghost + dashed-border 'Add another item'
button, so the operator never has to scroll back to the top
to add the next line. The dashed-border styling keeps it from
being mistaken for a submit/finish affordance.

The data-testid="dn-adhoc-add-item" selector is preserved on
the per-card buttons so any future test selectors continue to
work.

Fixes PINW-18
2026-08-26 01:32:46 +02:00
Mika
e900772faa fix(delivery-notes): move 'Add item' to per-card button after each row
Step A — move the 'Add item' button from the top of the items
section to the bottom of the items list.

Step B — match the quote-form.tsx pattern: every item card is
followed by its own ghost + dashed-border 'Add another item'
button, so the operator never has to scroll back to the top
to add the next line. The dashed-border styling keeps it from
being mistaken for a submit/finish affordance.

The data-testid="dn-adhoc-add-item" selector is preserved on
the per-card buttons so any future test selectors continue to
work.

Fixes PINW-18
2026-08-26 01:08:52 +02:00
Dave
e99850c646 chore(agent): baseline — uncommitted work from the local directory 2026-08-26 01:00:23 +02:00
Dave
720531bc0d refactor(orders, quotes): improve table layout and column widths for better readability
- Updated the Orders and Quotes detail pages to enhance table layouts, ensuring that the Description column has adequate space for multi-line text.
- Removed the Driver column from the Orders table to streamline the layout and prevent content squeezing.
- Adjusted fixed widths for various columns to fit within the viewport without horizontal scrolling, improving overall user experience.
- Ensured consistent styling and layout adjustments across both pages for a unified look and feel.
2026-08-24 07:57:15 +02:00
Dave
a0afa51600 refactor(orders): replace OrdersReviewModal with OrdersReviewModalMount and streamline attachment handling
- Updated the orders page to use `OrdersReviewModalMount` for improved order review functionality.
- Removed the inline `orderAttachementsToQuotes` function to simplify the code structure and enhance maintainability.
- Introduced a review modal in the order detail page, allowing users to trigger the review process with a dedicated button.
- Ensured consistent behavior between the orders list and detail pages regarding order review interactions.
2026-08-24 07:26:18 +02:00
Dave
139a3f3bfe feat(quotes): enhance scoring logic to include price matching
- 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.
2026-08-24 07:09:39 +02:00
Dave
07a5e810e5 feat(attachments): enhance order attachment handling and retrieval
- 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.
2026-08-21 20:47:00 +02:00
Dave
c3b452dbc4 feat(orders): enhance order handling with email_message_id deduplication and displayContactName utility
- 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.
2026-08-21 19:18:02 +02:00
Dave
fb283765fb fix(repair-details): update file handling and type definitions
- 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.
2026-08-21 01:13:44 +02:00
Dave
663c018a70 feat(quotes): add "Include older" toggle for searching full quote history
- 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.
2026-08-20 10:18:44 +02:00
Dave
88047eee33 feat(quotes): add "Completed (7 days)" tab for recent quotes
- Introduced a new tab for quotes completed within the last 7 days, enhancing the user experience by allowing quick access to recent activity.
- Updated the quotes search service to filter results based on the new "completed_recent" status, ensuring efficient retrieval of relevant quotes.
- Enhanced the quotes service to compute the cutoff date for the recent quotes dynamically, maintaining consistency across the application.
- Added tests to verify the functionality of the new filter, ensuring accurate behavior in the quotes search pipeline.
2026-08-19 13:36:41 +02:00
Dave
d194603dce feat(pdf): include VAT calculation in stock purchase document
- Added VAT calculation to the stock purchase document PDF, displaying VAT (15%) alongside the subtotal and total.
- Updated the layout to reflect the new totals structure, ensuring consistency with customer PO documents.
- Revised comments for clarity on the handling of total amounts and VAT reconciliation.
2026-08-19 11:45:10 +02:00
Dave
43bbdfc458 feat(tv-dashboard): enhance TV dashboard with driver stops and performance metrics
- Updated the TV dashboard to include a new section for driver stops, displaying active deliveries and pickups with GPS coordinates.
- Refactored the dashboard data structure to incorporate year-over-year performance metrics alongside existing monthly and weekly stats.
- Replaced the supplier map with a driver stops map for improved clarity on driver activities.
- Enhanced the user performance metrics to include detailed counts for quotes and orders processed by each representative.
- Removed the obsolete supplier map component to streamline the dashboard's functionality.
2026-08-19 10:16:49 +02:00
Dave
2f0e911563 feat(delivery-notes): standardize delivery-note letterhead across PDFs
- 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.
2026-08-18 11:53:25 +02:00
Dave
bfcddb9d82 feat(quote-form): allow empty quantity input for operator convenience
- Updated the `DraftQuoteItem` interface to allow `quantity` to be an empty string, enabling operators to type counts directly without needing to delete a default value.
- Modified the `QuoteFormItemCard` to handle empty input gracefully, falling back to 1 on submission if the field is left blank.
- Adjusted the `addItem` function in `QuoteForm` to initialize `quantity` as an empty string, enhancing user experience during item addition.
2026-08-18 07:28:25 +02:00
Dave
8ed0952364 refactor(forms): update layout and structure of form components
- Adjusted the `SheetContent` width in multiple form components to improve responsiveness.
- Enhanced the `CompanyForm` layout by reorganizing fields for better clarity and usability, including the addition of coordinates input.
- Updated the `CustomerForm` and `SupplierForm` to maintain consistency in design and structure.
- Improved the `DataTable` component's card styling for better visual appeal and user interaction.
- Refined the `QuotesPage` card styling to enhance the user experience when selecting quotes.
2026-08-17 23:34:30 +02:00
Dave
0b6171b851 feat(collection-pool): enhance stock row handling and filtering logic
- 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.
2026-08-17 22:33:47 +02:00
Dave
519fb422ae fix(pdf): adjust totals alignment in quote document
- Updated the `totals` style in the PDF generation to ensure totals are pushed to the right edge of the totals row, regardless of the presence of the operator note. This prevents shifting when the note is empty, enhancing the layout consistency.
2026-08-17 15:44:21 +02:00