Commit graph

253 commits

Author SHA1 Message Date
SJCoetz
010bba475c fix(buyer-tasks): pass status + email_error into SendPoModal stub
The SendPoModal renders its 'Resend' label and inline failure
banner from purchaseOrder.status / email_error, but the buyer-tasks
page stub didn't include either field. As a result, the modal
showed 'Send' and an empty banner even when the underlying PO was
mail_failed. Now the stub includes both so the operator sees the
correct affordance.

Co-authored-by: Orca <help@stably.ai>
2026-08-03 10:53:54 +02:00
SJCoetz
8616d5dbef fix(buyer-tasks): add emailError to route test fixture
The BuyerTaskGroup type gained an emailError field in the previous
commit. This pre-existing fixture constructs a BuyerTaskGroup
literal and now needs the field to compile. No behaviour change
in the test — emailError: null matches a clean draft.

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

Co-authored-by: Orca <help@stably.ai>
2026-08-03 10:52:58 +02:00
SJCoetz
c481cbeb1c feat(buyer-tasks): render mail-failed banner + Resend button
When a card's poStatus is 'mail_failed', the card now renders a
red warning row with the email_error reason and switches the
action button label to 'Resend' with the destructive colour. A
new data-po-status attribute on the button lets tests target the
state without depending on the human-readable label.

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

Co-authored-by: Orca <help@stably.ai>
2026-08-03 10:51:38 +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
0064555f22 chore(collection-confirm): cleanup nice-to-haves from final review
Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 03:04:23 +02:00
SJCoetz
d6af21193c fix(collection-confirm): scope costOverrides to current PO in dialog
Co-authored-by: Orca <help@stably.ai>
2026-08-03 02:48:17 +02:00
SJCoetz
fea67b15ba fix(collection-confirm): hoist onCostChange out of setEdit updater + preserve null rollback
Co-authored-by: Orca <help@stably.ai>
2026-08-03 02:46:28 +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
c14d07ecc0 test(collection-confirm): add confirm-invoice-dialog tests
Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 02:32:44 +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
1edaf36023 test(collection-confirm): update page tests for PoCard layout
Co-authored-by: Orca <help@stably.ai>
2026-08-03 01:33:31 +02:00
SJCoetz
f7d3032438 feat(collection-confirm): rewire page to use PoCard + remove Adjust Quantity
- Replace inline row rendering (ConfirmCard, QuantityDialog, NoteDialog,
  StockItemPickerDialog) with <PoCard> composition: PhotoStrip +
  ItemsTable + Confirm button.
- Cost edits dispatch POST /api/v1/order-items/{id}/cost with optimistic
  UI + rollback on failure.
- Confirm click opens ConfirmInvoiceDialog and POSTs to
  /api/v1/order-items/confirm with itemIds, invoice, costOverrides, actor.
- Keep AttachDialog + ReturnToPoolDialog component definitions per spec;
  render with open=false (no per-row buttons in PoCard layout).
- Switch load() to /api/v1/collection-confirm via fetch().
- Stock-source Book-to-stock, surplus chip, Return-to-pool, Adjust
  Quantity per-row actions are removed from the page body; PoCard is
  the primary grouping. Tests for those actions will fail and are
  scheduled for task 14.

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

Co-authored-by: Orca <help@stably.ai>
2026-08-03 01:25:31 +02:00
SJCoetz
0df6fb1ff2 feat(collection-confirm): add PoCard composing PhotoStrip+ItemsTable
Wraps a single PO group on the collection-confirm review page.
Renders the photo strip + items table + confirm button, with the
PO number and supplier name in the header. The cost edit/read
flow stays inside ItemsTable; the parent owns onCostChange
optimistic updates and the actual confirm mutation.

Co-authored-by: Orca <help@stably.ai>
2026-08-03 01:20:46 +02:00
SJCoetz
4310e4b211 feat(collection-confirm): add ItemsTable with inline cost edit
Co-authored-by: Orca <help@stably.ai>
2026-08-03 01:19:14 +02:00
SJCoetz
afa3876316 feat(collection-confirm): add PhotoStrip component
Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 01:16:09 +02:00
SJCoetz
7360e14c58 refactor(collection-confirm): extract ConfirmInvoiceDialog component
Co-authored-by: Orca <help@stably.ai>
2026-08-03 01:09:37 +02:00
SJCoetz
3f228808ea feat(collection-confirm): add POST /api/v1/order-items/confirm route
Co-authored-by: Orca <help@stably.ai>
2026-08-03 01:03:52 +02:00
SJCoetz
fada6f155f feat(collection-confirm): add POST /api/v1/order-items/[id]/cost route
Co-authored-by: Orca <help@stably.ai>
2026-08-03 01:01:30 +02:00
SJCoetz
e860611980 fix(collection-confirm): log server errors and return generic message to client
Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 00:59:02 +02:00
SJCoetz
bb10f1350b feat(collection-confirm): add GET /api/v1/collection-confirm route
Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-03 00:51:37 +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
06d8294c77 refactor(collection-confirm): make buildConfirmPatch take options bag with costOverride
Co-authored-by: Orca <help@stably.ai>
2026-08-02 23:59:22 +02:00
SJCoetz
7c807f3c18 fix(collection-confirm): render cost category label correctly
Co-authored-by: Orca <help@stably.ai>
2026-08-02 23:57:06 +02:00
SJCoetz
529e08eb2a fix(collection-confirm): widen NoteEntry.body and dedup attachments within row
Co-authored-by: Orca <help@stably.ai>
2026-08-02 23:52:21 +02:00
SJCoetz
7e1f22f363 feat(collection-confirm): add appendCostNote and dedupeAttachments helpers
Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-08-02 23:48:51 +02:00
SJCoetz
7136623331 feat(orders): expand rep on customers fetch for review modal
The customers getFullList in OrdersReviewModalMount now expands
both 'company' and 'rep'. The Review Order modal's customer
cascade reads selectedCustomer.expand.rep.name to populate the
Rep label and writes it to orders.rep on save.

No other consumer of the customer list reads c.expand.* — the
scout confirmed this is safe.

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

Co-authored-by: Orca <help@stably.ai>
2026-07-31 15:31:21 +02:00
SJCoetz
5af04e4b60 test(orders): review modal cascade coverage
Two new tests for the customer -> company/rep cascade:

1. Updates Company and Rep labels when the customer changes
   - renders with customer A (company 'Atlas Corp', rep 'Riya Patel')
   - clicks the customer-search mock (now picks the next non-selected
     customer from the list)
   - asserts the labels flip to B's values and A's are gone

2. Marks the modal dirty after a customer change cascades
   - asserts the Save button is disabled at baseline
   - after customer change, asserts it's enabled (proves the dirty
     check picks up the cascaded company/rep divergence)

Also updates the CustomerSearch mock to compute the next non-selected
customer id from the list (via data-next-id) so cascade tests can
drive it without hardcoding 'cust-new'.

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

Co-authored-by: Orca <help@stably.ai>
2026-07-31 15:31:07 +02:00
SJCoetz
f99ab7cf1a feat(orders): cascade company and rep from selected customer in review modal
When the operator changes the customer in the Review Order modal,
the Company and Rep labels now reactively reflect the selected
customer's expand.company.company name and expand.rep.name (with
email fallback). On save, the modal writes the new company relation
id and the new rep display name to the order — so a customer
change actually re-parents the order rather than just changing the
display.

Guards in the save body prevent two PB errors:
- company is skipped when the customer is cleared (orders.company
  is required, sending '' would 400)
- rep is skipped when the new customer has no rep, so existing
  rep values are preserved

The cascade uses useMemo derived from customerId, not a useEffect,
so the label updates in the same render as the dropdown change.

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

Co-authored-by: Orca <help@stably.ai>
2026-07-31 15:30:31 +02:00
SJCoetz
d3c3e3908d feat(orders): forward company and rep in review-save route
Destructure company and rep from the request body and forward them
through updateOrderReviewFields' headerPatch. The service layer
(Task 1) already accepts and validates these fields; the route now
just plumbs them.

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

Co-authored-by: Orca <help@stably.ai>
2026-07-31 15:29:43 +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
2cb6491654 feat(stock-orders): show SKU in line table on stock-order detail
Adds a 'SKU' column between Description and Ordered in the lines
table on /stock-orders/[id]. The column reads from
line.expand.stock_item.sku (the relation was already accessible via
the type, but the page was not expanding it on the fetch).

Falls back to an em-dash when expand is missing (e.g. an item whose
stock_item was deleted) so the row never renders an empty cell.

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

Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:20:17 +02:00
SJCoetz
e3295e3c13 chore(stock-orders): suppress set-state-in-effect lint on edit-mode seed effect
The seeding useEffect copies line qty/cost into the per-line edit
buffers when the targeted draft loads (or when the target changes).
The setState is intentional — the buffer is mirrored from the parent
draft's authoritative data — and the same suppress pattern is used
in src/app/(app)/collection-confirm/page.tsx and elsewhere.

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

Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:07:06 +02:00
SJCoetz
7b3601eab2 test(stock-orders): Edit button row coverage
Asserts the Edit button appears on draft rows only, and that clicking
it opens the modal with the right editPurchaseId. The mock stops
forwarding the click to the row's onClick via the existing
e.stopPropagation() pattern in the implementation.

Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:01:40 +02:00
SJCoetz
1d2499a17a feat(stock-orders): Edit button on draft PO rows
Adds a Pencil-icon ghost button to the trailing cell of every draft
row. Clicking it opens the QuickDraftModal in edit mode for that
purchase, scoped to that draft's lines. e.stopPropagation prevents
the row's detail-page navigation from also firing.

Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:00:03 +02:00
SJCoetz
5e91a5d7c0 test(stock-orders): QuickDraftModal edit-mode coverage
Asserts the modal hides non-targeted drafts, renders qty/cost inputs
per line, PATCHes only changed fields on Save, and that remove still
works in edit mode.

Co-authored-by: Orca <help@stably.ai>
2026-07-31 13:58:05 +02:00
SJCoetz
2303d8a29d feat(stock-orders): QuickDraftModal edit mode for draft lines
Adds an optional editPurchaseId prop. When set, the modal hides all
other drafts and renders per-line qty/cost inputs + a Save button that
PATCHes /api/v1/stock-quick-draft/line/[lineId]. Remove still works.
Search + add still works to add more lines to the same draft.

Co-authored-by: Orca <help@stably.ai>
2026-07-31 13:54:08 +02:00
SJCoetz
811b2dca1c feat(stock-quick-draft): PATCH /api/v1/stock-quick-draft/line/[lineId]
Adds a PATCH handler next to the existing DELETE handler. Validates
JSON body, maps service result reasons to HTTP status codes, and uses
admin PB to bypass user-side collection rules.

Co-authored-by: Orca <help@stably.ai>
2026-07-31 13:40:06 +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
4cf9c782e6 feat(stock-quick-draft): mount modal on /stock-orders via New Stock Item button
Co-authored-by: Orca <help@stably.ai>
2026-07-30 23:33:12 +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
dfae2d0c8b feat(stock-quick-draft): no-history supplier dropdown fallback
Co-authored-by: Orca <help@stably.ai>
2026-07-30 23:18:48 +02:00