PRD: POS & kitchen order
| Module | Orders (CORE-07) | PRD ID | PRD-KIT-001 |
| Status | Shipped | Owner | Orders squad |
| Date | 2026-03-23 | Version | v1.0 |
| Packages | @nx/sale · @nx/core · apps/sale-renderer | URD | KIT · STA · POS |
TL;DR
Lets a full-service F&B venue push order items to the kitchen as tickets and watch them move through cooking in real time, without anyone reloading a screen. Tickets route to named preparation stations, progress through per-item cooking statuses that auto-advance the ticket, and use void-and-resend so a sent item is never edited in place. The result: the back-of-house gets a live, accurate work queue and the floor sees status the moment the kitchen touches it.
1. Context & Problem
Orders already covers the cart-to-checkout-to-payment lifecycle, but a full-service F&B venue needs the back-of-house half: order items must reach the kitchen, progress through cooking, and update preparation displays without anyone reloading a screen. Today there is no kitchen-side entity at all - items live only on the sale order, so the kitchen has no work queue, no per-item cooking state, and no way to push status back to the floor. For HKD/SME restaurants and cafés that KICKO targets, that gap blocks the most common F&B service pattern (fire to kitchen, cook, serve), making the POS unusable beyond a quick-counter till.
This increment builds kitchen order management (KDS) on top of the existing sale order lifecycle: tickets sent from a sale order to the kitchen, named stations to route them, per-item cooking statuses, and live status propagation over WebSocket.
2. Goals & Non-Goals
Goals
- Send order items to the kitchen as a ticket with line items, with an idempotent send so a duplicate fire never produces a second ticket (KIT).
- A full ticket lifecycle (
PENDING → PROCESSING → READY → COMPLETED / VOIDED) and a per-item cooking lifecycle (PENDING → COOKING → READY → SERVED / VOIDED). - Auto-progression - the ticket status advances automatically from its item statuses.
- Void-and-resend semantics - a sent item is voided and re-fired, never edited in place.
- Named kitchen stations per merchant with product-category routing and per-station printer config (STA).
- Real-time updates to kitchen displays and dashboards on ticket create / update / complete.
- Marking an item served triggers downstream stock consumption.
Non-Goals
- A dedicated kitchen display application - this delivers the backend plus the
apps/sale-renderersurface only (Planned). - Refund / return flow and stock-mutation internals - owned by Inventory.
- Table-layout / seating management (Planned).
- POS shift sessions (POS) - listed for area completeness; session schema/services are not part of this increment.
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Real-time freshness | Kitchen displays reflect a status change with no manual reload; WebSocket fan-out on every create / update / complete |
| Send integrity | Zero duplicate tickets for a repeated fire (idempotent send holds under retries) |
| Auto-progression accuracy | Ticket status always matches the rollup of its item statuses |
| Routing coverage | Items land at the station mapped to their product category |
| Service correctness | Marking an item served reliably triggers stock consumption exactly once |
4. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Cashier | Fire order items to the kitchen and see when they are ready to serve |
| Kitchen staff | Work a live ticket queue, progress items cooking → ready → served, void mistakes |
| Manager / Owner | Configure stations, category routing, and per-station printers |
Core scenarios: a cashier fires items to the kitchen → a ticket appears on the routed station's display → kitchen staff move items cooking → ready → served, the ticket auto-progresses, and the floor sees status live; a mistaken item is voided and re-fired rather than edited.
5. User Stories
- As a cashier, I want to send order items to the kitchen in one action, so the back-of-house gets a work ticket immediately.
- As a cashier, I want a repeated fire to be idempotent, so a double-tap never duplicates a ticket.
- As kitchen staff, I want each item to carry its own cooking status, so I can track a multi-item ticket precisely.
- As kitchen staff, I want the ticket to auto-progress from its items, so I don't maintain a separate ticket state by hand.
- As kitchen staff, I want to void and resend a sent item, so corrections never silently mutate an in-flight ticket.
- As a manager, I want to route product categories to named stations with their own printer config, so each station only sees and prints its own work.
- As a cashier, I want kitchen status to update displays in real time, so the floor knows when to serve without asking.
6. Functional Requirements
| # | Requirement | URD ref |
|---|---|---|
| FR-1 | Send order items to the kitchen - creates a ticket with item lines | URD-KIT-001 |
| FR-2 | Idempotent send - a duplicate send produces the same ticket | URD-KIT-002 |
| FR-3 | Ticket lifecycle PENDING → PROCESSING → READY → COMPLETED / VOIDED | URD-KIT-003 |
| FR-4 | Per-item cooking lifecycle PENDING → COOKING → READY → SERVED / VOIDED | URD-KIT-004 |
| FR-5 | Ticket status auto-progresses from its item statuses | URD-KIT-005 |
| FR-6 | Changes use void-and-resend - sent items are not edited in place | URD-KIT-006 |
| FR-7 | Rush flag and per-order sequence for ticket ordering | URD-KIT-007 |
| FR-8 | Real-time updates to kitchen displays and dashboards | URD-KIT-008 |
| FR-9 | Marking an item served triggers stock consumption | URD-KIT-009 |
| FR-10 | Create named stations per merchant (i18n) | URD-STA-001 |
| FR-11 | Route product categories to stations | URD-STA-002 |
| FR-12 | Per-station printer config with auto-print | URD-STA-003 |
Full requirement text and acceptance criteria live in the Orders URD. This PRD references them rather than restating them.
7. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Real-time | Ticket create / update / complete fan out over WebSocket; apps/sale-renderer subscribes for live status |
| Idempotency | Send is keyed so retries collapse to a single ticket; no duplicate kitchen work |
| Tenancy & authz | All operations scoped per merchant (x-merchant-id); gated by sale/kitchen permissions |
| Data integrity | Ticket status is always a deterministic rollup of item statuses; corrections are void-and-resend, never in-place edits |
| Consistency | Send and lifecycle transitions are transactional; partial failures don't leave orphan tickets |
| i18n | Station names and user-facing statuses are bilingual ({ en, vi }) |
8. UX & Flows
Key surface: the kitchen-ticket views live in apps/sale-renderer, which subscribes to the WebSocket stream for live ticket/item status; station and routing config is managed per merchant.
9. Data & Domain
| Entity | Role |
|---|---|
KitchenTicket | The ticket sent to the kitchen - status, rush flag, per-order sequence, station assignment |
KitchenTicketItem | A ticket line - references the order item, carries its own cooking status |
KitchenStation | A named preparation area (i18n) with category routing and printer config |
Conceptual only - full schema and invariants in the sale domain model.
10. Dependencies & Assumptions
Depends on
- Sale Order (URD-ORD) - tickets are fired from order items.
- Product categories (Product) - station routing maps categories to stations.
- Inventory (Inventory) - a served item triggers stock consumption.
- Real-time transport (
@nx/saleWebSocket) - for live status fan-out.
Assumptions
- The merchant runs a full-service F&B flow (fire-to-kitchen), not counter-only.
- Stations and category routing are configured before items are fired.
- Clients (e.g.
apps/sale-renderer) hold an active WebSocket subscription to receive live updates.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| Duplicate fire creating two tickets | Send is idempotent via key; retries collapse to one ticket |
| Ticket status drifting from item statuses | Status is a deterministic auto-rollup, not hand-maintained |
| Editing a sent item in place | Disallowed by design - void-and-resend is the only correction path |
| Missed WebSocket update leaving a stale display | Displays subscribe to create / update / complete; reconnect re-syncs |
| No dedicated KDS app yet | Backend + apps/sale-renderer surface ships now; standalone KDS app is Planned |
12. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P2 - see Orders feature catalog (KIT, STA Built) |
| Rollout | All merchants; no feature flag |
| Migration | Sale-schema migrations for the new kitchen entities; no data backfill |
| Launch criteria | Fire → ticket → station routing → cooking → served verified end-to-end; idempotent send holds; auto-progression matches item statuses; live updates reach apps/sale-renderer |
| Monitoring | Ticket volume per merchant, send error / duplicate rate, WebSocket delivery health, served-to-stock consumption consistency |
13. FAQ
Can a sent kitchen item be edited? No - sent items are never edited in place. Use void-and-resend: void the item and fire a new one.
What happens if a fire is sent twice? Nothing duplicates - send is idempotent by key, so a repeated fire resolves to the same ticket.
How does the ticket status change? It auto-progresses from its item statuses; there is no separate hand-maintained ticket state.
Where do tickets show up? On the routed station's display in apps/sale-renderer, updated live over WebSocket - no reload needed. A dedicated KDS app is Planned.
Does serving an item move stock? Yes - marking an item served triggers downstream stock consumption (owned by Inventory).
Is this the POS shift session feature? No - POS sessions (URD-POS) belong to the same feature line but are a separate increment; this PRD covers kitchen tickets and stations.
References
- URD: Orders - Kitchen Tickets · Kitchen Stations · POS Sessions
- Builds on: Sale Order
- Module: Orders - overview + traceability
- Developer: @nx/sale · @nx/core · domain model