Commit graph

94 commits

Author SHA1 Message Date
SJCoetz
a35e715b21 polish(stocktake-pdf): zebra rows, drop Location, Target→Max, prominent Status, wider Name
Visual layout cleanup on the Stocktake PDF:
- Zebra-stripe alternating rows (subtle slate-50 on even rows,
  white on odd) so adjacent rows scan easily.
- Drop the Location column — one less thing to fit on the page,
  and the operator has the /stock detail page for that.
- Rename the Target column header to 'Max' (user-facing naming).
- Move the Status column to the right edge and make it bold +
  colour-coded using STOCK_STATUS_STYLES[status].hex, matching
  the on-screen badge palette.
- Widen the Name column (18%→24%) so full stock-item names read
  in one line for most rows.
- Redistribute remaining widths so the table still totals 100%
  of the landscape A4 page width.

Column widths now: SKU 10 / Name 24 / Supplier 18 / Current 9 /
Min 8 / Max 9 / Status 16 / spacer 6 (= 100%).

Co-authored-by: Orca <help@stably.ai>
2026-08-03 18:23:11 +02:00
SJCoetz
6033aba18d fix(stocktake-pdf): match /stock columns + add button on /stock page
The original Stocktake PDF columns (Name, SKU, Location, Unit,
Current qty, blank Actual, Supplier) didn't match what the
operator sees on /stock. They pointed at the wrong list — they
worked for a blank-column walk-through stocktake form, but the
user wants to print the /stock list as it appears on screen.

This rewrite makes the Stocktake PDF mirror the /stock table
column-for-column:
- SKU, Name, Supplier, Current, Min, Target, Location, Status
- Status derived via getStockStatus + STOCK_STATUS_STYLES so
  it matches the on-screen badge label
- Current qty now renders with the unit suffix (matching the
  on-screen cell)
- Target renders '—' when null/undefined/0, matching /stock

Also:
- Switched to landscape A4 to give 8 columns room (was
  portrait).
- Added a copy of the 'Download Stocktake PDF' button on the
  /stock page next to 'New stock item' — both /stock and
  /stock-orders now expose the same endpoint. Button uses the
  same plain <a target='_blank' rel='noopener'> pattern as
  the existing stock-orders buttons, with aria-hidden on the
  Download icon.
- Added a test on /stock for the new link (href, target,
  rel). 33/33 tests pass.

Co-authored-by: Orca <help@stably.ai>
2026-08-03 18:18:58 +02:00
SJCoetz
91d8363380 fix(stock-purchase-send): render auto-PDF in-process instead of HTTP self-call
The send-email route's auto-PDF block did an internal
\`fetch(/api/v1/stock-purchases/[id]/pdf)\` to render a PDF when
the operator hit Send on a stock PO without one. Cloudflare's
loopback filter rejects that self-call with a network-level
error before Next.js sees it, surfacing as 'fetch failed' in the
modal (status rolled to draft, route returned 500 with code
'pdf_failed').

Extract the PDF generation into \`generateStockPurchasePdf\` on
the service layer so the send-email route can render the PDF
in-process. Same pattern as the customer-PO flow's
\`generatePurchaseOrderPdf\`. The route stays a thin shim that
maps \`StockPurchasePdfValidationError\` to 422.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 16:51:07 +02:00
SJCoetz
907a7414b4 fix(pdf): repeat table headers on every page in list reports
Move the thead <View> out of the table and onto the page itself
with the @react-pdf/renderer 'fixed' prop, so reports that span
multiple A4 pages still label their columns. Without this, page 2+
of a long stock-orders report or stocktake would show data without
column headers — making the right-hand 'Actual' column on the
stocktake and the value columns on the orders list unreadable.

Co-authored-by: Orca <help@stably.ai>
2026-08-03 16:23:12 +02:00
SJCoetz
45f1ec6bb7 feat(pdf): list-pdf documents for stock orders + stocktake
Two new @react-pdf/renderer documents backed by renderStockPurchaseListPdf
and renderStockItemStocktakePdf in render.tsx. The Orders PDF mirrors
the on-screen table; the Stocktake PDF adds a blank 'Actual' column
for the operator to fill in during the walk-through. Both inherit
the toString() → Buffer pattern so they run under the same jsdom
test setup as the other PDF renderers.

Co-authored-by: Orca <help@stably.ai>
2026-08-03 16:13:14 +02:00
SJCoetz
a5567f41cf test(stock-items): assert expand: supplier on stocktake calls
The mock's getFullList is now a vi.fn so the call args can be inspected. Each test asserts expand: 'supplier' was passed, so a regression that drops the expand (e.g. a copy-paste from the dashboard read) fails fast instead of silently shipping rows without supplier names.

Co-authored-by: Orca <help@stably.ai>
2026-08-03 16:08:03 +02:00
SJCoetz
88678cea49 feat(stock-items): listStockItemsForStocktake service helper
Returns active stock items sorted by name with supplier expanded,
for the new Stocktake PDF download endpoint. Distinct from the
dashboard's low-stock query: stocktake wants every SKU
alphabetically, not the under-stocked ones first.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 16:04:46 +02:00
SJCoetz
e75f0d6157 fix(stock-purchase-send): stop auto-flipping stock_purchase_items on success
Same operator-flow fix we made for customer POs in 79b9309: sending a
stock PO no longer auto-flips the linked stock_purchase_items from
`collection_pool` to `assigned_to_driver`. Items now stay in
`collection_pool` after the supplier email goes out; the operator
marks them ready-to-collect from the stock-order page only after the
supplier confirms, and that's the action that promotes them to
`assigned_to_driver` (so the driver-app sees them as ready).

Without this, the stock-order "PO Sent" state was invisible — the
items jumped straight to driver-ready the moment the email left,
before any supplier confirmation. Now the PO Sent → Ready to Collect
→ Assign Driver flow is observable end-to-end.

3 new tests, full suite 1210/1210 passing.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 16:04:21 +02:00
SJCoetz
690b8a2c26 fix(po-send): reject sends for POs without a generated PDF
The dashboard's send-email route fires the n8n webhook unconditionally
once pre-send validation passes. If a PO has no generated PDF (legacy
state — `documents` empty + `documents_history: [null]`), n8n tries to
download from PB with an empty filename, gets a 404, and the webhook
returns HTTP 500. The operator sees "Webhook returned HTTP 500. Status
reverted to mail_failed." with no indication of the actual cause.

Add a PDF-existence guard in `sendPurchaseOrderEmail` using the same
`documents` → `documents_history` fallback chain the route uses to
compute `latestFilename`. When both are empty / unusable, return a
`no_pdf` failure that flips the PO to `mail_failed` with a clear
operator-facing message BEFORE the webhook fires.

While here, rename the misleading `code: "no_pdf"` on the line-items
check to `code: "no_line_items"` — the old code name was wrong (it
checked for linked items, not PDFs) and would have collided with the
new `no_pdf` guard.

The new guard also filters null entries out of `documents_history`
before calling `.find()` / `.file` — the legacy `[null]` shape was
making the old route code throw.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 15:47:01 +02:00
SJCoetz
92febfb51c feat(stock-purchases): aggregate ordered/received value on list rows
Add ordered_value and received_value to StockPurchaseListRow. Each is
Σ line.{quantity|quantity_received} × line.cost_price, rounded to 2dp.
Replaces countItemsForPurchase with summarizeItemsForPurchase so the
per-row N+1 returns all three numbers in one fetch (no extra round
trip).

Co-authored-by: Orca <help@stably.ai>
2026-08-03 14:24:30 +02:00
SJCoetz
79b93096ec fix(po-send): stop auto-flipping order_items.item_status to collection_pool
Previously, when recordPurchaseOrderSendOutcome received a "success"
outcome from the n8n workflow, it auto-flipped every linked
order_items.item_status from "po_generated" to "collection_pool".

This dropped items off the buyer-tasks PO Sent tab immediately on
send — before the supplier had even confirmed receipt. The operator
had no chance to mark items ready-to-collect manually.

New flow: send mail → items stay "po_generated" and remain visible
in buyer-tasks PO Sent → operator manually marks items ready →
items drop off buyer-tasks.

The back-link sync (purchase_orders.order_item) still runs and now
reads all items with purchase_order = poId regardless of status, so
the array stays accurate as items progress through their lifecycle.

Adds src/lib/server/purchase-orders-service.test.ts with 4 cases:
- success does NOT auto-flip linked items
- success flips PO to sent + stamps email_message_id
- success keeps the PO's order_item back-link in sync
- failure flips PO to mail_failed + stamps error (also no item writes)

Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 14:10:34 +02:00
SJCoetz
5959d2a1aa fix(buyer-tasks): keep mail_failed POs visible in the PO Draft tab
The po_draft filter previously required po.status === 'draft', so
a failed send (status: 'mail_failed') caused the group to vanish
from buyer-tasks entirely. The only recovery path was the per-PO
Resend button on the order detail page, which is friction. Now
mail_failed groups stay in place so the operator can re-open the
SendPoModal and retry from the same list. po_sent is unchanged— mail_failed must not appear there.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 10:46:32 +02:00
SJCoetz
964b3d51b2 feat(buyer-tasks): surface email_error on buyer task groups
Adds an emailError field on BuyerTaskGroup so the UI can render
the send-pipeline failure reason. The value is truncated to 200
chars at the service boundary to keep the card layout bounded.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 10:31:33 +02:00
SJCoetz
4444842ef5 feat: collection-map updates, todays-stops, quote/approval rework, buyer-tasks, e2e
- Add todays-stops test and component to collection-map
- Expand collection-map sidebar and pin-model
- Rework orders/[id]/process page
- Add quotes/[id]/page and process pages
- Add quotes/approvals/[id] page
- Add buyer-tasks generate-po route
- Add quotes partial-approve and pdf routes
- Add purchase-orders [id]/pdf route
- Add e2e create-flows spec
- Add superpowers plan doc
- Update pb_schema-2.json
- Update form components (company, customer, order, quote, supplier)
- Update UI components (button, command, dialog)
- Add collection-pool test components

Co-authored-by: Orca <help@stably.ai>
2026-08-03 09:33:45 +02:00
SJCoetz
e2896264f4 fix(collection-confirm): add route validation + server-side actor derivation
Co-authored-by: Orca <help@stably.ai>
2026-08-03 02:35:58 +02:00
SJCoetz
dbf02e2511 fix(collection-confirm): restore Book to stock for stock rows + cost guard
Restores the 'Book to stock' action that the page rewrite in f7d3032 dropped from collection-confirm/page.tsx, and routes the cost pencil away from stock rows so the existing cost-edit endpoint (which queries order_items) can't 404 on a stock_purchase_items id.

- New POST /api/v1/collection-confirm/book-stock endpoint + route tests.
- New bookStockRows(rowIds, { actor }) service that mirrors the pre-rewire bookStockLineForRow flow per-row: ledger → cache increment → line update → auto-derive parent send→received.
- PoCard footer now switches on the row mix: stock rows → 'Book to stock'; customer rows → 'Confirm for invoicing'; mixed → both.
- ItemsTable hides the cost pencil when row.source === 'stock' (cost cell stays readable).
- Page-level handleBookStock wired through to the new endpoint with a reload on success.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 02:29:20 +02:00
SJCoetz
c856db1617 fix(collection-confirm): thread null cost through appendCostNote to preserve audit on null→0
Co-authored-by: Orca <help@stably.ai>
2026-08-03 00:46:50 +02:00
SJCoetz
47eff9a6fe feat(collection-confirm): implement confirmOrderItemsForInvoicing with createBatch
Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 00:40:29 +02:00
SJCoetz
77d2c32df4 fix(collection-confirm): add cost_price to ConfirmRow, rename body to args
Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 00:29:56 +02:00
SJCoetz
2997b8dcba feat(collection-confirm): implement updateOrderItemCost service
Co-authored-by: Orca <help@stably.ai>
2026-08-03 00:23:37 +02:00
SJCoetz
8b6b83583d fix(collection-confirm): use getOrderItemsCollectionName + populate stock supplier
Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 00:17:56 +02:00
SJCoetz
15f6ec638d fix(collection-confirm): make listCollectedItems stock leg fail-soft
Co-authored-by: Orca <help@stably.ai>
2026-08-03 00:12:02 +02:00
SJCoetz
efe10b9474 feat(collection-confirm): add service skeleton with listCollectedItems
Co-authored-by: Orca <help@stably.ai>
2026-08-03 00:08:57 +02:00
SJCoetz
f7b5f3677e feat(orders): persist company and rep from review-modal cascade
Extends updateOrderReviewFields' patch type to accept company
(relation id) and rep (free-text snapshot) in addition to the
existing order_number and customer. The modal cascade writes these
when the operator changes the customer; the service now carries
them through to the PB update.

The order_number collision check is unchanged — it only runs for
order_number changes, so the new fields are unaffected.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-07-31 15:29:10 +02:00
SJCoetz
da0a689507 feat(stock-quick-draft): add updateQuickDraftLine service
Adds a TDD'd updateQuickDraftLine function that PATCHes
quantity and/or cost_price on a stock_purchase_items row.
Returns the same discriminated-union shape as its sibling
add/remove functions. No schema changes.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-07-31 13:38:59 +02:00
SJCoetz
f9a0d08dab fix(tests): mock scaffold destructure + collection-pool + pocketbase startup
- orders-service.test.ts: destructure capture-group bug on regex match
  (was assigning m[0] to 'field', so filter clauses never matched).
  Two pre-seed tests were masked by the bug.
- quotes-service.test.ts: createQuoteFromEmail now returns 0
  attachments; the n8n flow uploads them in a follow-up step. Update
  the assertion that still expected the old embedded-base64 count.
- collection-pool/page.test.tsx: tests assume ONE supplier group with
  multiple rows; supply that shape and scope the selectors. Also wait
  for data load in the chevron test and gather list items across all
  <ul> elements in the sort control helper.
- pocketbase.ts: wrap authStore.onChange registration defensively so
  the test mock (no authStore) doesn't throw at startup.
- stock-orders/page.tsx: drop the re-export of
  STOCK_PURCHASE_STATUS_VALUES on the page module — Next.js page
  route files only allow the default export.
- collection-confirm/view-model.test.ts: buildConfirmNote input is
  Pick<InvoiceDetails, 'date' | 'number'>; drop the extra setBy fields.

Co-authored-by: Orca <help@stably.ai>
2026-07-31 00:01:24 +02:00
SJCoetz
6329a100e7 fix(stock-quick-draft): isolate new-SKU suppliers, fix empty-string pick, service layer
Co-authored-by: Orca <help@stably.ai>
2026-07-30 23:31:21 +02:00
SJCoetz
98e1f25fd5 fix(stock-quick-draft): supplier-name schema + remove dead resolvePb + pagination cap
Co-authored-by: Orca <help@stably.ai>
2026-07-30 23:14:11 +02:00
SJCoetz
8acb6dcf95 fix(stock-quick-draft): dedicated /drafts endpoint with nested shape, browser-auth
Co-authored-by: Orca <help@stably.ai>
2026-07-30 23:11:27 +02:00
SJCoetz
249a78a7ef fix(stock-quick-draft): add trailing newlines
Co-authored-by: Orca <help@stably.ai>
2026-07-30 22:06:24 +02:00
SJCoetz
f7de9d44fd feat(stock-quick-draft): removeQuickDraftLine
Co-authored-by: Orca <help@stably.ai>
2026-07-30 22:04:37 +02:00
SJCoetz
0ed89aac10 fix(stock-quick-draft): escape filter strings + handle findOrCreateOpenDraft failures
Co-authored-by: Orca <help@stably.ai>
2026-07-30 22:02:39 +02:00
SJCoetz
99374599d7 feat(stock-quick-draft): addQuickDraftLine with find-or-create-open-draft
Co-authored-by: Orca <help@stably.ai>
2026-07-30 21:55:02 +02:00
SJCoetz
3eef60f34c feat(stock-quick-draft): getStockItemSupplierHistory
Co-authored-by: Orca <help@stably.ai>
2026-07-30 21:50:04 +02:00
SJCoetz
eb64f03db6 feat(stock-quick-draft): types + searchStockItemsForQuickDraft
Co-authored-by: Orca <help@stably.ai>
2026-07-30 21:38:58 +02:00
SJCoetz
4535a20eee fix(buyer-tasks): mark-ready puts item into collection pool, not collected
When the operator clicks "Mark as ready" on a buyer-tasks po_sent card
and fills in the note, the item should land in the collection pool
(`item_status: "collection_pool"` + `ready_for_collection: true`) so
a driver can be assigned. Previously the service set
`item_status: "ready_delivery"`, which was rendered as the word
"Collected" on /orders and on /orders tabs — operators reasonably
read that as "driver already picked it up", and the item never
appeared on /collection-pool where they expected to assign a driver.

Now:
- markGroupReady writes a single PB update with the same shape as
  /api/v1/collection-map/items/[id]/assign (collection_pool +
  ready_for_collection: true + notes)
- po_sent buyer-tasks filter excludes collection_pool and assigned,
  so marked-ready items drop out of buyer-tasks and surface on
  /collection-pool instead
- ITEM_STATUS_VISUAL.ready_delivery is labelled "Ready for
  Collection" (kept distinct from the actual `collected` state) for
  any other flow that legitimately sets this status (e.g. stock-out)
- new item-status-ui.test.ts locks down the label/tab mapping so
  the regression can't return

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-07-30 15:25:33 +02:00
SJCoetz
172d562f06 fix(buyer-tasks): generate PO succeeds + supplier stripe + PO badge
generatePoForGroup was creating purchase_orders records without
`po_number`, which is required by the PB schema. PB rejected the
create with 500 ("Failed to create record.") and the operator never
got a PO. Fix:

- New src/lib/server/po-number.ts — server-side generatePONumber()
  that mirrors the client version in pb-helpers.ts (PO-YYYY-MM-NNN
  format) but uses getAdminPocketBase(). Per the data-access rules,
  don't extend pb-helpers.ts; server business logic lives under
  lib/server/.
- generatePoForGroup now generates the PO number, includes
  total_amount (sum of costPrice * quantity) and order_item
  (back-link to the items) in the create payload — mirrors the
  canonical /orders/[id]/process PO-creation flow so the PB admin
  row shows related lines without an extra getList call.
- Test updated to assert the new payload (po_number regex,
  total_amount computed, order_item back-link).

UI while here:
- BuyerTaskGroupCard now renders a deterministic supplier colour
  stripe on the left edge. New src/lib/supplier-color.ts hashes the
  supplierId into a small Tailwind 600 palette (18 colours) so the
  same supplier always shows the same colour, and the supplier name
  gets tinted with the same colour for quick scanning.
- PO number is now shown prominently at the top of the card (in a
  warm-toned badge that matches the AI Insights theme) when one
  exists — po_draft and po_sent tabs. Needs PO tab omits it.

Co-authored-by: Orca <help@stably.ai>
2026-07-30 15:00:18 +02:00
SJCoetz
6fa658df62 feat(buyer-tasks): require audit note when marking ready
The Mark ready action on the buyer-tasks page now mirrors the
collection-pool flow: the operator is asked for a free-text note
(name of who they spoke to, collection number, etc.) before the
items transition to ready_delivery. The note is required, is trimmed,
and is written to order_items.notes alongside the status change so
there's an audit trail.

- Service: markGroupReady now takes a required note param, trims it,
  rejects empty values, and persists the trimmed text to notes.
- Route: /api/v1/buyer-tasks/mark-ready returns 400 when note is
  missing or whitespace-only, and forwards the value to the service.
- Page: BuyerTasksPage swaps the no-input MarkReadyModal for the
  shared MarkReadyDialog, threads the note through the POST body,
  and surfaces server-side validation errors back into the dialog
  rather than only as a toast.
- The old MarkReadyModal is removed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-07-29 22:38:00 +02:00
SJCoetz
76f1d79082 fix(buyer-tasks): prefill SendPoModal fields from buyer-tasks page
Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:45:12 +02:00
SJCoetz
05242f09f4 feat(buyer-tasks): markGroupReady with cross-order check
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:00:45 +02:00
SJCoetz
98a0bc4143 feat(buyer-tasks): generatePoForGroup creates PO and updates items
Co-authored-by: Orca <help@stably.ai>
2026-07-29 19:58:07 +02:00
SJCoetz
b4c9f2d2ce fix(buyer-tasks): use getFullList and tighten item_status filter for po_draft/po_sent
- Switch from getList(1, 500) to getFullList so candidate sets
  aren't silently truncated at 500 rows (matches orders-service
  and quotes-service conventions).
- Tighten the po_draft filter to item_status = po_generated so
  items in downstream statuses (collection_pool, delivered, etc.)
  don't get pulled over the wire.
- Tighten the po_sent filter to exclude item_status in
  {ready_delivery, invoiced, delivered, cancelled} for the same
  reason.
- Drop the redundant ready_delivery post-filter on po_sent now
  that the PB layer handles it.
- Tests capture the filter string passed to getFullList and assert
  its clauses so regressions are caught.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-07-29 19:56:53 +02:00
SJCoetz
49acc04fd1 fix(buyer-tasks): type-safe expand traversal for customer/company
Co-authored-by: Orca <help@stably.ai>
2026-07-29 19:53:09 +02:00
SJCoetz
463917d755 feat(buyer-tasks): listBuyerTasks service with grouping
Co-authored-by: Orca <help@stably.ai>
2026-07-29 19:52:41 +02:00
SJCoetz
74c73e9f17 feat(orders): totals + add/remove items in review modal
Two related enhancements to the orders review modal:

* Tier 1 — header totals: the "Order items" header now shows the
  item count plus the cost and selling totals (qty*price summed
  across the live draft state). Uses formatCurrency from
  pb-helpers.ts for the ZAR formatting.

* Tier 2 — add/remove items: the edit-mode items list now has an
  "Add item" button (Plus icon) and a per-row Trash icon. New rows
  get a sentinel id like "__new_1" and a left-edge accent stripe;
  rows flagged for removal flip to a strikethrough + reduced opacity
  with disabled inputs (toggle button flips to an Undo2 icon).

  On save, the route posts to /api/v1/orders/[id]/review-save with
  new itemsToAdd and itemsToRemove arrays. The service layer adds
  createOrderItems + softFlagOrderItems handlers (set
  item_status="removed" — operator confirmed the value is in the
  PB admin schema). The modal re-keys the new drafts with the
  returned PB ids so a follow-up save treats them as existing
  items.

  Partial-failure UI now has three sections (updates / adds /
  removes) sharing a labelled FailureSection helper. Response
  envelope now includes itemsAdded and itemsRemoved alongside the
  existing items block, plus a flat partialFailures array for
  the toast summary.

  Type cleanup: "removed" added to ItemStatus and the
  ITEM_STATUS_VISUAL map.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-07-29 18:54:49 +02:00
SJCoetz
c9e5362cf1 fix(types): restore ItemHistorySupplier + ItemHistoryOtherQuote (dropped during merge resolution)
The merge of feature/order-sprint-item-history into main concatenated
src/lib/types.ts by appending the last 50 lines of the feature branch's
version. The two helper interfaces ItemHistorySupplier and
ItemHistoryOtherQuote were defined earlier in the file (lines 1416-1433)
and were dropped, breaking the references in ItemHistory and the import
in item-history-service.ts. This commit restores them.

Co-authored-by: Orca <help@stably.ai>
2026-07-29 17:45:15 +02:00
SJCoetz
43011d4d5e feat(item-history): wire description-fuzzy matcher into getItemHistory
Co-authored-by: Orca <help@stably.ai>
2026-07-29 17:43:59 +02:00
SJCoetz
7801b602f5 feat(orders): add edit-mode confirmation for items table
Items in the orders review modal are read-only by default. A new
'Edit items' button (Pencil icon, top right of the items list)
opens a confirmation dialog before the inputs become editable.

- State: editingItems (default false), confirmingEdit (default false).
- Both reset to false whenever the modal re-opens (useEffect on
  [open, order.id]) so re-opening always starts read-only.
- All 4 per-row inputs (qty, cost, markup, selling) carry
  disabled={!editingItems || saving}.
- Visual hint: when editing, the items list gets a subtle warn
  ring + tint (ring-1 ring-warn/40 bg-warn/5).
- data-testid hooks: review-edit-items-btn on the button,
  review-edit-confirm-dialog on the dialog, review-items-list on
  the ul with data-editing attribute.
- 4 new tests cover: read-only default, button opens dialog,
  confirm enables editing, cancel keeps read-only.

Also adds order_attachements?: string[] to the Order type — the
PB file field on the orders collection used by the modal's source-
of-truth panel (separate commit wires the data).

Co-authored-by: Orca <help@stably.ai>
2026-07-29 17:41:21 +02:00
SJCoetz
22df511293 merge
Co-authored-by: Orca <help@stably.ai>
2026-07-29 16:49:37 +02:00
SJCoetz
96e071330a test(orders): reconcile duplicate test file state
A post-commit re-edit of the test file introduced a duplicate
updateOrderItemsPrices describe block at the tail. All tests still
pass (the duplicates run independently). Keep the file aligned
with HEAD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-07-29 16:24:03 +02:00