PRD: POS & kitchen order
| Module | Sale | PRD ID | PRD-KIT-001 |
| Status | Ready to dev | FEAT | KIT · BANA-1430 |
| Epic | — | Plane | BANA-1561 |
| Date | 2026-03-23 | Version | v1.0 |
| Packages | @nx/sale | URD | KIT |
| Surface | Sale · POS | ||
| Owner | Phát Nguyễn | ||
TL;DR
Sends F&B order items to the kitchen as tickets, routes them to named preparation stations, and tracks cooking in real time - no reloads, no polling. Per-item statuses auto-advance the ticket; void-and-resend means a sent item is never edited in place.
1. Context & Problem
Orders already covers cart → checkout → payment, but full-service F&B needs the back-of-house half. Today there is no kitchen-side entity: items live only on the order, so the kitchen has no work queue, no per-item cooking state, and no way to push status back to the floor. For the HKD/SME restaurants and cafés BANA targets, that gap blocks the most common F&B pattern - fire to kitchen, cook, serve - making the POS useless beyond a quick counter.
This increment builds kitchen order management (KDS) on top of the existing order lifecycle: tickets sent from an order to the kitchen, named stations to route them, per-item cooking statuses, and live status propagation via real-time updates.
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 (STA) - the station entity is built; the category-routing and per-station printer fields exist on the model but are not yet consumed by the backend (the station to fire to is chosen by the caller, and printing/auto-print is a front-end concern).
- Real-time updates to kitchen displays and dashboards on ticket create / update / complete.
- A per-item cooking-status change emits an event that drives downstream stock consumption - in the current code stock is consumed when an item reaches ready (marking an item served does not itself trigger consumption).
Non-Goals
- A dedicated kitchen display application - this delivers the back end plus the POS front end 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 handling is not part of this increment.
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Real-time freshness | Kitchen displays reflect a status change with no manual reload; real-time 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 (target; category routing is not yet wired in the backend - the firing station is chosen by the caller) |
| Service correctness | A cooking-status change reliably triggers stock consumption exactly once (consumption fires when an item reaches ready in the current code) |
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 flow: cashier fires items → ticket appears on the station's display → kitchen staff move items cooking → ready → served, ticket auto-progresses, floor sees status live. Mistakes are voided and re-fired, never 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. (The routing and printer fields exist on the station model but are not yet acted on by the backend.)
- 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 | A cooking-status change emits an event that triggers stock consumption - in the current code consumption fires when an item reaches ready, not on served | URD-KIT-009 |
| FR-10 | Create named stations per merchant (i18n) | URD-STA-001 |
| FR-11 | Route product categories to stations - data model only: the category-routing field exists but is not yet consumed by the backend (the firing station is chosen by the caller) | URD-STA-002 |
| FR-12 | Per-station printer config with auto-print - data model only: the printer/auto-print fields exist but are not yet consumed by the backend | 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 via real-time updates; the POS front end subscribes for live status |
| Idempotency | Send is keyed so retries collapse to a single ticket; no duplicate kitchen work |
| Tenancy & authz | All operations confined to the user's own merchant; permission-gated |
| 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 the POS front end, which subscribes to the real-time stream for live ticket/item status; station and routing config is managed per merchant.
9. Data & Domain
| Entity | Role |
|---|---|
| Kitchen ticket | The ticket sent to the kitchen - status, rush flag, per-order sequence, station assignment |
| Kitchen ticket line | A ticket line - references the order item, carries its own cooking status |
| Preparation station | A named preparation area (i18n); carries category-routing and printer-config fields on the model that are not yet consumed by the backend |
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 per-item cooking-status change triggers stock consumption (consumed when the item reaches ready in the current code).
- Real-time transport - 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. the POS front end) hold an active real-time 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 real-time update leaving a stale display | Displays subscribe to create / update / complete; reconnect re-syncs |
| No dedicated KDS app yet | Back end + POS front-end 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 | New kitchen entities; no data backfill |
| Launch criteria | Fire → ticket → cooking → ready/served verified end-to-end; idempotent send holds; auto-progression matches item statuses; live updates reach the POS front end; stock consumption fires on the cooking-status event (currently on ready). Automatic category-to-station routing and per-station auto-print are not yet wired |
| Monitoring | Ticket volume per merchant, send error / duplicate rate, real-time 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 the POS front end, updated live via real-time updates - no reload needed. A dedicated KDS app is Planned.
Does the cooking flow move stock? Yes - a per-item cooking-status change emits an event that Inventory acts on; in the current code stock is consumed when an item reaches ready (the served status does not itself trigger consumption). Stock consumption is 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 - URD
- Developer: @nx/sale · @nx/core · domain model