Commit graph

163 commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
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
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
921ea4e63d fix(stock-quick-draft): parseInt limit + exhaustive status map
Co-authored-by: Orca <help@stably.ai>
2026-07-30 22:11:55 +02:00
SJCoetz
0515b1ce7c feat(stock-quick-draft): API routes (search, history, add-line, delete-line)
Co-authored-by: Orca <help@stably.ai>
2026-07-30 22:07:37 +02:00
SJCoetz
3b8a299b15 fix(orders): use text-warn (not text-warn-foreground) for icon buttons
text-warn-foreground is the dark contrasting text colour meant to
sit ON TOP of a warn-tinted background (e.g. an alert panel).
Used as the icon colour on a plain card it renders nearly black.

text-warn is the actual amber/orange hue — what we want for the
Review + Open icons on the order list.

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

Co-authored-by: Orca <help@stably.ai>
2026-07-30 15:34:12 +02:00
SJCoetz
f17f4d08b2 style(orders): tint Open button warn-amber to match Review
Both right-hand action buttons on the order list card now share the
warn-amber treatment, so they pop against the muted card background
and the operator can spot them at a glance. The icons (AlertTriangle
for Review, ExternalLink for Open) keep the actions distinguishable.

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

Co-authored-by: Orca <help@stably.ai>
2026-07-30 15:32:33 +02:00
SJCoetz
d3f8242e4f fix(orders-sprint): transition pending→processed, merge Unknown tabs, polish layout
Three issues on /orders/sprint, fixed together:

1. 'Process' was a no-op for the buyer queue. The modal saved
   supplier + cost + selling + markup + notes, but never wrote
   item_status or po_description, so items stayed 'pending' and
   reappeared on the next refresh — never reaching buyer-tasks.
   Now mirrors the canonical /orders/[id]/process saveItem gate:
   pending → processed only when supplier + cost + po_description
   are all set, and items already past 'processed' (stock_booked,
   po_generated, etc.) are never downgraded.

2. Two 'Unknown' tabs side-by-side: a hardcoded 'Unknown' tab for
   blank categories and a PB-managed 'Unknown / Other' tab for
   category id zgr50fji47r36wu. They had the same meaning and
   different colours. Now resolved by slug ('cat_unknown') and
   bucketing both into a single tab, matching how /sprint already
   tries to.

3. Layout polish: dark-mode-safe category pills (withAlpha helper),
   hidden redundant Category column when filtered to one tab,
   empty <th> given an aria-label, supplier-less cells no longer
   render an empty Badge box, and client-side bucketing so tab
   switches no longer trigger a full refetch + layout shift.

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

Co-authored-by: Orca <help@stably.ai>
2026-07-29 23:06:11 +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
6259ed8407 fix(orders): transition pending → processed on save when priced
The buyer-tasks Needs PO tab filters by
  item_status = 'processed' && purchase_order = ''
but the order process page's saveItem() never updated item_status,
so priced items stayed pending and never surfaced on the buyer
board.

Save now promotes pending → processed when the operator has filled
supplier + cost_price + po_description. Items already past processed
(stock_booked, po_generated, ready_delivery, …) are never downgraded.
The stock-fulfil branch writes processed too as a no-op for the
case where the subsequent stock/fulfil call fails — it will be
overwritten with stock_booked on success.

Co-authored-by: Orca <help@stably.ai>
2026-07-29 22:02:33 +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
3cfb2ac00c feat(buyer-tasks): extract send-po modal to shared component
Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:40:50 +02:00
SJCoetz
1486e10bbe feat(buyer-tasks): page route + sidebar entry
Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:19:33 +02:00
SJCoetz
bcda00ac46 feat(buyer-tasks): POST mark-ready route
Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:04:32 +02:00
SJCoetz
4f0be21732 feat(buyer-tasks): POST generate-po route
Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:03:26 +02:00
SJCoetz
275a47e28f feat(buyer-tasks): GET /api/v1/buyer-tasks route
Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:02:04 +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
70eb59e17a feat(orders): add Order Sprint page + process modal + sidebar entry
Wires a new /orders/sprint surface that lists pending order items
(purchase_order = '' && item_status = 'pending') grouped by category,
mirrors the quote-sprint workflow. Click a row to open
OrderSprintProcessModal for assigning a supplier, cost, and emitting
a purchase order.

Sidebar gets an "Order Sprint" entry under the Orders group.

Notes:
- No automated tests yet (manual smoke test recommended).
- OrderSprintItem type is defined locally in both page.tsx and the
  modal — duplicated but consistent; extract to types.ts in a follow-up.
- Page + modal use client-side getPocketBase (Pb is filtered to the
  user); the counts route is server-side.

Co-authored-by: Orca <help@stably.ai>
2026-07-29 17:50:21 +02:00
SJCoetz
d6570dca71 fix(orders): source attachments from order_attachements field
The orders review modal's source-of-truth panel now reads from the
order_attachements multi-file field on the orders collection
(pbc_3527180448) instead of the quote_attachments collection rows
on the parent quote. Older orders were migrated on 2026-07-29; the
order-level field is authoritative going forward.

- Added orderAttachementsToQuotes() helper in
  src/app/(app)/orders/page.tsx that synthesises the
  QuoteAttachment[] shape from the order's file-field array, deriving
  kind (pdf/image/doc) and content_type from the filename extension.
  PB file fields don't carry a content-type from the wire, so we
  infer from extension (png/jpg/gif/webp/svg/bmp/pdf/docx/txt).
- Replaced the quote_attachments collection query in
  OrdersReviewModalMount with the helper call.
- Added orders + pbc_3527180448 to the ALLOWED_COLLECTIONS whitelist
  in the file proxy route so the review modal's PDF/image preview
  iframes can stream through /api/v1/files/... .

Co-authored-by: Orca <help@stably.ai>
2026-07-29 17:41:29 +02:00
SJCoetz
22df511293 merge
Co-authored-by: Orca <help@stably.ai>
2026-07-29 16:49:37 +02:00
SJCoetz
064f13a7ca fix(orders): use client PB for customer list in modal mount
getAdminPocketBase reads PB_ADMIN_EMAIL/PB_ADMIN_PASSWORD which are
server-only env vars. The modal mount is a client component, so the
admin client would fail to authenticate at runtime. The users PB
session already has read access; switch to getPocketBase.

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

Co-authored-by: Orca <help@stably.ai>
2026-07-29 16:19:40 +02:00
SJCoetz
8b6b10d64b fix(orders): remove assertAgentAuth from review-save route (browser-callable)
This route is invoked from the in-app review modal, not from an external
agent integration. The browser session is already authenticated via PB;
calling assertAgentAuth here caused every modal save to 401.

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

Co-authored-by: Orca <help@stably.ai>
2026-07-29 16:19:27 +02:00
SJCoetz
a80721c6e0 fix(orders): review modal mount review fixes (customer fallback, dead ai wiring)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-07-29 16:11:11 +02:00
SJCoetz
de87eb312e feat(orders): warn-amber review button + modal mount on orders list
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

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