Commit graph

237 commits

Author SHA1 Message Date
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
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
29ffcbdc8d feat(stock-quick-draft): cards region with remove + footer buttons
Co-authored-by: Orca <help@stably.ai>
2026-07-30 23:04:43 +02:00
SJCoetz
1b7552ffd2 fix(stock-quick-draft): new-SKU supplier picker reachable, closure guard, submit test
Co-authored-by: Orca <help@stably.ai>
2026-07-30 22:57:17 +02:00
SJCoetz
75342ed88c feat(stock-quick-draft): qty/cost row + Add & next + new-SKU toggle
Co-Authored-By: Claude <noreply@anthropic.com>

Co-authored-by: Orca <help@stably.ai>
2026-07-30 22:43:17 +02:00
SJCoetz
275d17b76a fix(stock-quick-draft): race-guard search, surface API errors, deterministic test timing
Co-authored-by: Orca <help@stably.ai>
2026-07-30 22:35:35 +02:00
SJCoetz
b099e2b42e feat(stock-quick-draft): modal search + supplier-history regions
Co-authored-by: Orca <help@stably.ai>
2026-07-30 22:21:10 +02:00
SJCoetz
cdbf91526f refactor(stock-quick-draft): useState lazy init, sr-only title, trim mock stubs
Co-authored-by: Orca <help@stably.ai>
2026-07-30 22:19:21 +02:00
SJCoetz
2d4d0df6d1 feat(stock-quick-draft): modal skeleton + Stock Order Reference header
Co-authored-by: Orca <help@stably.ai>
2026-07-30 22:15:51 +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
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
14badb8aee docs(plans): stock quick-draft modal implementation plan
12-task TDD plan covering the service module, four API routes,
the modal component (built incrementally across 4 tasks), and the
page-hook. Each task ends with a commit; spec gaps surfaced during
self-review already merged into task content.

Co-authored-by: Orca <help@stably.ai>
2026-07-30 21:31:59 +02:00
SJCoetz
23e8e94d7c docs(specs): stock quick-draft modal design
Adds spec for the new item-first quick-draft modal on /stock-orders:
item-first stock-order entry, accumulates per supplier into draft POs,
reuses existing Send/Receive flow. No schema changes.

Co-authored-by: Orca <help@stably.ai>
2026-07-30 21:26:47 +02:00
SJCoetz
c400b4c69b feat(process-modal): show resolved supplier on Recent Orders rows
The Process Order Item modal already fetched order_items history with
the supplier id, but the rendered row only showed description, order
number, cost, selling, and markup — leaving the operator to guess
which supplier fulfilled each past order.

Now each Recent Orders row also shows the resolved supplier name,
tinted with the same deterministic supplier colour used by the
buyer-tasks cards (djb2 → 18-swatch palette). The id is looked up
locally against the modal's existing `suppliers` prop — no extra PB
expand needed.

Unknown supplier ids (e.g. supplier deleted since the past order) are
silently omitted rather than surfaced as a stale id. Two new tests
cover the resolved-name and missing-id cases.

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

Co-authored-by: Orca <help@stably.ai>
2026-07-30 15:45:49 +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
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
20b57ce2bb feat(sprint-modal): show markup + order desc hint + explicit Show/Hide
Three small additions to the process modal:

- Markup cell added to the pricing grid (4 cols now: PO Qty, Cost,
  Selling, Markup). Read-only and dashed like Selling — derived from
  selling/cost so the operator sees the margin they're pricing at.
  Green when selling ≥ cost, destructive when selling < cost.

- Above the PO description textarea, the order's original item
  description is now shown as a read-only reference hint. Lets the
  operator see what the customer asked for vs what they're sending
  to the supplier without having to scroll back to the modal header.

- AI Insights toggle now has an explicit "Show" / "Hide" label next
  to the chevron so the affordance is obvious. The chevron direction
  was the only signal before; the label makes the toggle
  self-documenting.

Co-authored-by: Orca <help@stably.ai>
2026-07-30 14:54:02 +02:00
SJCoetz
b9dfec8d28 style(modal): re-tint AI Insights section from info (blue) to warn (yellow)
The AI Insights section was info-blue while the modal's outer border
and the popover's AI card are warn-yellow. The mixed cool/warm
palette felt disjointed. Switching the section to warm warn tones
makes the yellow border feel intentional rather than decorative.

- border-info/20 → border-warn/40 (heavier, with shadow-warn/10 so
  the section has a soft glow)
- bg-info/5 → bg-warn/5
- text-info → text-warn-foreground (Sparkles icon, "AI Insights"
  label, chevrons, suggestion-available pill, Use AI button)
- Use AI button: border-info/30 → border-warn/40, hover bg-info/10
  → hover bg-warn/10

Co-authored-by: Orca <help@stably.ai>
2026-07-30 14:14:45 +02:00
SJCoetz
0007408f14 style(popover,modal): warn-toned AI card + warning-bordered modal
Popover (the ⓘ AI snapshot card):
- Switch from raw amber-200/amber-50 to the project's warn theme
  tokens so it matches the rest of the design system and respects
  dark mode.
- Subtle gradient backgrounds (from-warn/10 to-warn/5) so each card
  reads as a distinct section without screaming for attention.
- Icons now sit in coloured chips (rounded-md bg-warn/20 etc.) for
  better visual hierarchy.
- Freshness badges are pill-shaped with an inset ring instead of a
  flat square — softer.
- Fuzzy-match warning box uses warn tokens too so it doesn't clash
  with the parent card.
- Supplier / quote rows get a faint hover background and tighter
  spacing for easier scanning.

Process modal:
- Outer Card border switched from border-border/60 to
  border-2 border-warn/40 with a warm shadow-warn/15. The whole
  modal now pops on the dimmed background — fits the "stand out
  orange/yellow" ask.

Co-authored-by: Orca <help@stably.ai>
2026-07-30 14:12:59 +02:00
SJCoetz
5f039ea8cd feat(sprint-modal): lock selling, add PO qty, wire AI suggestion
The OrderSprintProcessModal was trying to do too much and made
operators do too much. The buying flow on this screen is now:

- Selling comes from the order and is fixed. Rendered as a read-only
  dashed-bordered cell with a "From order · not editable" hint — the
  price the customer was quoted is still visible so the operator can
  see the margin they're pricing at.
- Markup field removed (it only existed to drive the cost↔selling
  recalc). Markup is now computed from selling/cost at save time so
  downstream margin reports stay consistent.
- New PO Qty input (defaults to order_qty, falls back to quantity)
  written to order_qty. Lets buyers override for partial fills.
- "Use AI suggestion" button next to the AI Insights header reads
  ai_item_insights.top_historical_matches[0] and applies supplier_id
  (resolved to the actual supplier name in the dropdown) + cost_price.
  Disabled when no AI matches; warns if the AI's supplier_id isn't in
  the suppliers list instead of silently dropping it.

Visual refresh while here: backdrop-blur overlay, info-tinted AI
section so it reads as distinct from the deterministic history, soft
borders (border-border/60), success-coloured Process button with
shadow, gradient header, hover transitions, sticky footer with
status hint.

Co-authored-by: Orca <help@stably.ai>
2026-07-30 14:07:58 +02:00
SJCoetz
6093a749eb feat(ai-history): render item-history blob shape-aware
The AI agent returns `procurement_summary` + `top_historical_matches`
arrays but the popover was dumping both as raw JSON. Now:

- AiInsightsCard promotes the first *_summary/*_description key (or any
  long string) into a prose headline above the structured sections.
- isNamedEntry recognises matched_description / match_description /
  suppliername, so the renderer turns n8n's historical-match rows into
  structured list rows instead of an object-dump.
- AiCard inside the popover delegates body rendering to AiInsightsCard;
  the amber framing + freshness badge + raw-data fallback are kept.

Tests cover headline promotion, named-list rendering, empty blob,
description-keyed entries, and the popover's end-to-end shape.

Co-authored-by: Orca <help@stably.ai>
2026-07-30 13:56:39 +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
bca6a32c0d feat(buyer-tasks): page body with tabs, cards, mark-ready flow
Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:18:57 +02:00
SJCoetz
9d40109b81 feat(buyer-tasks): mark-ready confirmation modal
Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:14:11 +02:00
SJCoetz
10dd4aff81 feat(buyer-tasks): group card with color-coded action button
Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:11:39 +02:00
SJCoetz
6fa44c55ff feat(buyer-tasks): collapsible items-list component
Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:06:10 +02:00
SJCoetz
5a08d3651e feat(buyer-tasks): tab strip component
Co-authored-by: Orca <help@stably.ai>
2026-07-29 20:05:17 +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
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
fe1af217e0 docs: buyer tasks page design spec
Co-authored-by: Orca <help@stably.ai>
2026-07-29 19:35:44 +02:00