WFDASH-WEBAPP/docs/order-workflow-rules.md
SJCoetz 6c07a91693 refactor: drop order-level status, shift to item-driven workflow
This commit overhauls the order management system to use item-level status tracking instead of a single order-level status:
- Remove deprecated `orders.status` field and add `invoiced` and quote match audit fields
- Add `isOrderComplete` utility with tests to validate fully delivered orders
- Add purchase order PDF rendering support and tooling
- Add geo coordinate fields for companies/customers with map link UI
- Add REST management APIs for customers, companies, and suppliers
- Update customer/company services to use relation fields instead of free text
- Add status pill and tab filter components for item status visualization
- Add quote-to-order matching logic and unit tests
- Update all pages, components, and tests to match the new schema
- Include all required PocketBase migrations and schema updates
2026-06-29 13:06:01 +02:00

118 lines
5.7 KiB
Markdown

# Order Workflow Rules
## Quote to Order
- A quote remains in `quotes`.
- Quote items remain in `quote_items`.
- Creating an order from a quote creates one new `orders` record.
- The new order links back to the quote through `orders.quote`.
- Each quote item is copied into `order_items`.
- Each copied order item links back to its source quote item through `order_items.quote_item`.
- Creating an order from the same quote twice returns the existing order instead of duplicating it.
- Quote status is not changed by order creation.
## Order Items as Source of Truth
`order_items` is the operational source of truth after an order is created.
**No order-level `status` field exists.** Items are the only authoritative
lifecycle record; the order-level "Complete" badge in the UI is derived
client-side from the items (see "Order Completion" below).
Order items move through:
- supplier ordering / purchase order
- driver list / collection pool
- collected (back at office)
- picking
- ready for delivery
- delivered
Driver/collection fields use `drv_*` naming where possible, for example `drv_status` and `drv_id`.
## Item Status Lifecycle
`order_items.item_status` takes one of:
| Status | Meaning |
| ----------------- | -------------------------------------------------------------------- |
| `pending` | Item created, not yet sent to a supplier. |
| `processed` | Operator has reviewed/edited the line. |
| `po_generated` | PO created; in_stock flag determines next step. |
| `driver_pool` | Queued for collection (in_stock=false path). |
| `assigned` | A driver has picked it up. |
| `collected` | Back at the office (in_stock=true jumps straight here from PO). |
| `stock_verified` | Verified against stock count. |
| `picking` | Being pulled for delivery. |
| `ready_delivery` | Packed, on the delivery vehicle. |
| `delivered` | Signed for at the customer (terminal for the item). |
| `cancelled` | Cancelled (terminal for the item). |
## Orders List — Item-Status Tabs
The `/orders` page tabs are filtered by **item status**, not order status.
There are six tab groups; each one is the set of item statuses that "live"
on that tab. An order appears on tab X iff it has at least one item whose
`item_status` is in X (so an order with items in both `pending` and
`delivered` shows on both tabs).
| Tab | Item statuses in this tab |
| ---------------- | ------------------------------------------------------------------------------- |
| New | `pending` |
| Processing | `processed`, `po_generated` |
| Collection Pool | `driver_pool`, `assigned` |
| Collected | `collected`, `stock_verified`, `picking`, `ready_delivery` |
| Delivered | `delivered` |
| Cancelled | `cancelled` |
The mapping lives in `src/lib/item-status-ui.ts` (`TAB_GROUP_ITEM_STATUSES`)
and is the single source of truth for both the tab labels and the
underlying PB filter strings.
By default each row shows only items in the active tab. A "Show all items"
toggle per row expands to the full item list.
## Order Completion (derived, not stored)
A small helper `isOrderComplete(items)` in `src/lib/server/order-completion.ts`
answers the only order-level question the UI still asks: "is this order
done?". It returns true iff `items.length > 0 && every item is delivered`.
The green "Complete" badge on `/orders/[id]` and the order row header is
driven entirely by this helper — there is no `orders.status` field to
keep in sync, and therefore no flip-flop bug on refresh.
Invoiced is a separate boolean (`orders.invoiced`, set by the invoicing
action), intentionally independent from item lifecycle — an order can be
marked invoiced before, during, or after the items are all delivered.
## In-Stock Items
Each `order_items` row carries an `in_stock: bool` flag. When `true`,
the line bypasses the driver collection pool entirely after PO
generation:
- `in_stock = false` → on PO send, item transitions to `driver_pool`
and a `collection_pool` row is created.
- `in_stock = true` → on PO send, item transitions to `collected`
(skipping the pool).
Invoices and delivery notes ignore the flag — the item is still part of
the order and counted in totals.
## Driver List and Collection Pool
The old Django `driver_list` workflow is represented by the new `collection_pool` concept.
`collection_pool` tracks assignment and collection work, while `order_items` keeps the item lifecycle state.
## Future Stock Collection
Later we will add a dedicated `stock` collection.
Stock is used when:
- more items are collected than the order requires
- stock is ordered and arrives at the office
- collected items should be booked into stock instead of moving to invoicing
Until that collection exists, stock-related operational movement stays on `order_items`.
## Delivery Notes
Delivery notes should be generated from order items that are ready, loaded, or delivered.
## Repairs
Repairs will be rebuilt only where they connect to the practical order/quote/repair flow.