URD: Orders
| Module | CORE-07 | Version | v0.6 |
|---|---|---|---|
| Status | In-progress | Date | 2026-06-04 |
Business documentation. This URD is Orders' feature list - each feature below is one Functional Area (
<AREA>). The same<AREA>keys the feature's PRDs (PRD-<AREA>-NNN) and tests (TC-<AREA>-NNN), and each feature is listed in the Delivery feature catalog. See the Feature Spine convention.
1. Purpose
Define user-facing requirements for order management - the sale order lifecycle from cart to checkout to payment, plus the F&B extensions a full-service venue needs: check splitting, kitchen tickets, POS shift sessions, table reservations, and loyalty point earning.
2. Scope
| Included | Excluded |
|---|---|
| Sale order lifecycle (DRAFT → PROCESSING → PARTIAL → COMPLETED / CANCELLED) | Refund / return flow (Planned) |
| Order items (product, combo fan-out, custom) | Stock mutation (Inventory) |
| Checkout and revert | Payment provider integration (Payment) |
| Order split and merge | Tax engine & e-invoice issuance |
| Check splitting (split the bill) | Delivery order tracking (Planned) |
| Kitchen tickets and stations (F&B) | Table-layout / seating management (Planned) |
| POS sessions (shifts) with X/Z reports | Dedicated kitchen display application (Planned) |
| Reservations | Order templates (Planned) |
| Loyalty point earning | Technical API specifications (see developer docs) |
| Real-time updates to kitchen & dashboards |
3. Definitions
| Term | Definition |
|---|---|
| Sale Order | A transaction containing items being sold; one entity serves as both cart (DRAFT) and committed order. |
| Order Item | A line item - a product variant (PRODUCT mode) or a manual entry (CUSTOM mode). |
| Combo | A product that fans out into priced child lines when added to the cart. |
| Sale Check | An independently-payable group within an order, used to split the bill. |
| Kitchen Ticket | A ticket sent to the kitchen for F&B preparation; contains items from a sale order. |
| Kitchen Station | A named preparation area (e.g., grill, bar) that receives tickets. |
| POS Session | A cashier's shift on a specific device; tracks opening / closing cash and generates reports. |
| X Report | Interim shift summary - can be generated multiple times. |
| Z Report | Final closing report - only one per session. |
| Reservation | A guest booking for a future date / time with a party size. |
4. Conceptual Model
Conceptual only - full schema lives in the developer domain model.
5. Feature Catalog
The feature list of this module. Each row is one feature (a Functional Area). Detail in §6. Mirrored in the Delivery feature catalog.
| Feature ID | Feature | Phase | Status | Priority |
|---|---|---|---|---|
ORD | Sale Order | P1 | Built | High |
CHK | Check Splitting | P2 | Built | Medium |
KIT | Kitchen Tickets | P2 | Built | High |
STA | Kitchen Stations | P2 | Built | Medium |
POS | POS Sessions | P2 | Built | High |
RSV | Reservations | P2 | Built | Medium |
PNT | Loyalty Points | P2 | Built | Medium |
PCK | Product Picker | P2 | Planned | High |
SLF | QR Self-order | P2 | Planned | High |
SHF | Multi-employee shifts | P2 | In-progress | High |
ENT | Entitlements | P1 | Built | High |
Status: live from Plane where mapped, otherwise registry-declared. Vocabulary mirrors Plane (state-group / phase).
6. Features
One sub-section per feature, in catalog order. Each feature keeps its description, requirements, and acceptance together. Priority = MoSCoW (Must / Should / Could / Won't).
ORD - Sale Order Built
Feature ID: orders/ORD · Phase: P1 · PRDs: PRD-ORD-001 · PRD-ORD-002 · Dev: @nx/sale
What it does for users: cashiers build an order as a cart, add products (including combos), and move it through checkout to payment - with split/merge and a customer link while still in draft.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-ORD-001 | M | Create draft orders (cart) with items |
| URD-ORD-002 | M | Add, update, remove items only while in DRAFT |
| URD-ORD-003 | M | Adding the same product merges quantity onto the existing line |
| URD-ORD-004 | M | Setting an item quantity to zero removes the line |
| URD-ORD-005 | M | Add a combo item that fans out into priced child lines |
| URD-ORD-006 | M | Checkout: DRAFT → PROCESSING (validates items, locks prices) |
| URD-ORD-007 | M | Checkout validates: non-empty cart, prices ≥ 0, quantity ≥ 1 |
| URD-ORD-008 | M | Revert: PROCESSING → DRAFT |
| URD-ORD-009 | M | Cancel from DRAFT, PROCESSING, or PARTIAL (with optional reason) |
| URD-ORD-010 | M | Partial payment → PARTIAL status |
| URD-ORD-011 | M | Full payment → COMPLETED status |
| URD-ORD-012 | S | Split one order into multiple (DRAFT only) |
| URD-ORD-013 | S | Merge multiple orders (DRAFT only) |
| URD-ORD-014 | S | Attach a customer to the order |
| URD-ORD-015 | S | Multi-currency with exchange rate (default VND) |
| URD-ORD-016 | C | Enforce a maximum number of items per order |
| URD-ORD-017 | M | Split assigns specific items / quantities into new draft orders (optional name / customer each); a fully-assigned line moves whole, a partial line splits leaving the remainder on the source; an empty source is cancelled (FULL_SPLIT), a partial source is repriced; non-positive quantity, unknown item, or over-allocation is refused |
| URD-ORD-018 | M | A combo's lead and all its children must move together, at full quantity, into the same target group; partial or orphaning combo moves are refused |
| URD-ORD-019 | M | Merge moves every item of the source draft orders into the target draft order and cancels the sources (MERGED_INTO_<target>); only orders of the same merchant and sale channel can merge |
| URD-ORD-020 | M | Every transferred item records an append-only transfer-history entry (source, target, time, quantity) forming the split / merge lineage |
| URD-ORD-021 | S | A merge can be rolled back while the target is still draft and was merged; items return to their original source orders (restored to draft) via the lineage; quantity added after the merge stays on the target |
| URD-ORD-022 | M | Split, merge and rollback are atomic; allocation usages clone (split) / move (merge) / cancel (rollback) in lock-step and affected orders are repriced |
| URD-ORD-023 | M | An order with active checks cannot be split, merged, or rolled back |
Acceptance
AC-ORD-01: Order lifecycle
| Given | When | Then |
|---|---|---|
| DRAFT order | Checkout | PROCESSING (prices locked) |
| PROCESSING | Full payment | COMPLETED |
| PROCESSING | Partial payment | PARTIAL |
| Any non-terminal status | Cancel | CANCELLED |
| Empty cart | Checkout | Rejected |
AC-ORD-02: Combo fan-out
| Given | When | Then |
|---|---|---|
| A combo product | Add to cart | One lead line + zero-priced child lines created |
| The same combo already in order | Add again | Rejected (already in order) |
AC-ORD-03: Split a draft
| Given | When | Then |
|---|---|---|
| A DRAFT order with items | Split into groups, taking a line's full quantity | The line moves whole to the new draft, stamped with a transfer entry |
| A DRAFT order with a line of qty 5 | Split assigning 2 to a new draft | The line splits - 2 on the new draft, 3 left on the source; both repriced |
| A split that empties the source | - | The source is cancelled (FULL_SPLIT) and its reserved stock cancelled |
| A split assigning more than a line's available quantity | - | Refused (over-allocation) |
AC-ORD-04: Merge & rollback
| Given | When | Then |
|---|---|---|
| Two DRAFT orders of the same merchant + channel | Merge into a target | All items move to the target, sources cancelled (MERGED_INTO_<target>), reserved stock moved |
| A merged target still in DRAFT | Rollback | Every item returns to its original source via the lineage; sources restored to DRAFT |
| A line whose quantity was increased after the merge | Rollback | The surplus stays on the target; only the originally-transferred quantity returns |
| A target that was never merged | Rollback | Refused (not merged) |
AC-ORD-05: Combo atomicity & active checks
| Given | When | Then |
|---|---|---|
| A combo (lead + children) on a draft | Split assigning only the lead, or a child's partial quantity | Refused (combo must move together at full quantity) |
| An order with active checks | Split, merge, or rollback | Refused - roll back the checks first |
CHK - Check Splitting Built
Feature ID: orders/CHK · Phase: P2 · PRDs: - · Dev: @nx/sale
What it does for users: a table splits one order into several independently-payable checks, allocating specific items and quantities (and even a different customer) to each, completing the order only when all checks are paid.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-CHK-001 | S | Split an order into multiple payment checks |
| URD-CHK-002 | S | Allocate specific items and quantities to each check |
| URD-CHK-003 | S | Different customer per check |
| URD-CHK-004 | S | Order completes when all its checks complete |
| URD-CHK-005 | C | Roll back a split while no check is paid |
Acceptance
AC-CHK-01: Bill split
| Given | When | Then |
|---|---|---|
| An order with items | Split into multiple checks with allocated items | Each check is independently payable |
| All checks of the order complete | - | The order completes |
KIT - Kitchen Tickets Built
Feature ID: orders/KIT · Phase: P2 · PRDs: - · Dev: @nx/sale
What it does for users: order items are sent to the kitchen as tickets that progress through cooking statuses, update displays in real time, and trigger stock consumption when served.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-KIT-001 | M | Send order items to kitchen (creates ticket + items) |
| URD-KIT-002 | M | Idempotent send - a duplicate send produces the same ticket |
| URD-KIT-003 | M | Ticket lifecycle: PENDING → PROCESSING → READY → COMPLETED / VOIDED |
| URD-KIT-004 | M | Item lifecycle: PENDING → COOKING → READY → SERVED / VOIDED |
| URD-KIT-005 | M | Ticket status auto-progresses from its item statuses |
| URD-KIT-006 | M | Changes use void-and-resend (sent items are not edited in place) |
| URD-KIT-007 | S | Rush flag and per-order sequence for ticket ordering |
| URD-KIT-008 | M | Real-time updates to kitchen displays and dashboards |
| URD-KIT-009 | S | Marking an item served triggers stock consumption |
Acceptance
AC-KIT-01: Kitchen
| Given | When | Then |
|---|---|---|
| Order items | Send to kitchen | Ticket created |
| Same idempotency key | Duplicate send | No duplicate ticket |
| All items READY/SERVED | - | Ticket auto-progresses |
| Item marked served | - | Stock consumption triggered |
STA - Kitchen Stations Built
Feature ID: orders/STA · Phase: P2 · PRDs: - · Dev: @nx/sale
What it does for users: merchants define named preparation stations and route product categories to them, with per-station printer configuration and auto-print.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-STA-001 | S | Create named stations per merchant (i18n) |
| URD-STA-002 | S | Route product categories to stations |
| URD-STA-003 | S | Per-station printer config with auto-print |
Acceptance
AC-STA-01: Station routing
| Given | When | Then |
|---|---|---|
| A station with a routed product category | An item in that category is sent to kitchen | The ticket is routed to that station |
POS - POS Sessions Built
Feature ID: orders/POS · Phase: P2 · PRDs: - · Dev: @nx/sale
What it does for users: a cashier opens a shift on a device with an opening cash float, attaches orders to it, and closes it with a cash count that produces a Z report and an interim-X summary.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-POS-001 | M | Open a session on a device with an opening cash float |
| URD-POS-002 | M | Only one open session per device |
| URD-POS-003 | M | Close a session: count cash, record discrepancy |
| URD-POS-004 | M | Z report (closing) - one per session |
| URD-POS-005 | S | X report (interim) - repeatable |
| URD-POS-006 | S | Orders created during a shift attach to the session |
| URD-POS-007 | S | Track expected vs. actual for non-cash methods |
| URD-POS-008 | C | Recount support on close |
Acceptance
AC-POS-01: Session
| Given | When | Then |
|---|---|---|
| No open session on device | Open | Session created |
| Existing open session on device | Open | Rejected |
| Open session | Close with cash count | Discrepancy computed, Z report generated |
RSV - Reservations Built
Feature ID: orders/RSV · Phase: P2 · PRDs: - · Dev: @nx/sale
What it does for users: hosts take table bookings with guest details, party size and source, track them through a lifecycle, and spawn a linked sale order on check-in.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-RSV-001 | S | Create a reservation: guest name, phone, party size, date / time |
| URD-RSV-002 | S | Lifecycle: PENDING → CONFIRMED → CHECKED_IN / CANCELLED |
| URD-RSV-003 | S | Source tracking: phone, walk-in, online, other |
| URD-RSV-004 | S | Occasion tagging: birthday, anniversary, business, etc. |
| URD-RSV-005 | S | On check-in, spawn and link a sale order |
Acceptance
AC-RSV-01: Reservation
| Given | When | Then |
|---|---|---|
| Guest info | Create | Status PENDING, table held |
| Confirmed reservation | Check in | Status CHECKED_IN, sale order spawned & linked |
PNT - Loyalty Points Built
Feature ID: orders/PNT · Phase: P2 · PRDs: - · Dev: @nx/sale
What it does for users: customers earn loyalty points on completed orders, awarded once per order, with a balance tracked per customer per merchant.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-PNT-001 | S | Earn points on completed orders |
| URD-PNT-002 | S | Point award is idempotent per order |
| URD-PNT-003 | S | Point balance tracked per customer per merchant |
Acceptance
AC-PNT-01: Point earning
| Given | When | Then |
|---|---|---|
| A customer-linked order | The order completes | Points are earned and added to the customer's balance |
| The same completed order replayed | - | No duplicate award (idempotent per order) |
PCK - Product Picker Planned
Feature ID: sale/PCK · Phase: P2 (Jun) · PRDs: PRD-PCK-001 · Dev: @nx/sale · @nx/commerce
What it does for users: a cashier picks any sellable item in at most 3 taps. Products with Options resolve to exactly one Variant through option choices; single-variant items skip the chooser entirely.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-PCK-001 | M | Cashier picks any sellable item in ≤3 taps |
| URD-PCK-002 | M | A product with Options resolves to exactly one Variant via option choices |
| URD-PCK-003 | M | Single-variant products skip the chooser (one tap adds to cart) |
| URD-PCK-004 | M | Price updates live as options are chosen |
| URD-PCK-005 | S | Invalid option combinations cannot be selected |
| URD-PCK-006 | S | Default options are pre-selected for the fastest path |
| URD-PCK-007 | S | Barcode scan bypasses the picker (scan = item in cart) |
Acceptance
AC-PCK-01: Option resolution
| Given | When | Then |
|---|---|---|
| A product with size + ice options | Cashier picks size then ice | Exactly one variant is added, correct price shown - ≤3 taps total |
| A product with one variant | Cashier taps it | It lands in the cart immediately, no chooser |
SLF - QR Self-order Planned
Feature ID: sale/SLF · Phase: P2 (Jul-Aug) · PRDs: PRD-SLF-001 · Dev: @nx/sale · @nx/commerce
What it does for users: a guest at a table scans a QR code and orders from their own phone - no app install. The order lands in the POS tied to that table; staff confirm and it flows to the kitchen like any other order.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-SLF-001 | M | Guest scans a table QR and gets the menu on their phone (no app) |
| URD-SLF-002 | M | The menu served is policy-based (time/channel) from the merchant's Menu |
| URD-SLF-003 | M | Guest builds a cart and submits an order for that table |
| URD-SLF-004 | M | The order lands in the POS tied to the table; staff confirm or reject |
| URD-SLF-005 | M | A confirmed order flows to the kitchen like a staff-entered order |
| URD-SLF-006 | S | Guest sees order status updates |
| URD-SLF-007 | S | Sold-out items are hidden / not orderable |
| URD-SLF-008 | C | Guest can re-order within the same session |
Acceptance
AC-SLF-01: Guest order reaches the kitchen
| Given | When | Then |
|---|---|---|
| A guest at table 5 scans its QR | They submit an order of 2 items | POS shows the order on table 5 pending confirmation; on confirm, kitchen receives the ticket |
| An item is sold out | Guest browses the menu | The item is not orderable |
SHF - Multi-employee Shifts In-progress
Feature ID: sale/SHF · Phase: P2 (Jul) · PRDs: PRD-SHF-001 · Dev: @nx/sale
What it does for users: a multi-staff store runs sales shifts: open/close on a device, multiple staff enrolled in one shift, X/Z reports at close, and the till reconciled - counted cash vs expected, discrepancy recorded. (As-built note: the multi-employee backend is on develop; the POS still runs the old single-user session - the increment wires the front end over.)
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-SHF-001 | M | A staff member opens and closes a shift on a device |
| URD-SHF-002 | M | Multiple staff can enrol in one shift (multi-employee) |
| URD-SHF-003 | M | An X report is available mid-shift; a Z report at close |
| URD-SHF-004 | M | Cash is reconciled at close: counted vs expected, discrepancy recorded |
| URD-SHF-005 | M | Sales during the shift attribute to the acting staff member |
| URD-SHF-006 | S | Cash movements (pay-in/pay-out/safe-drop) are recorded against the drawer |
| URD-SHF-007 | S | Owners see revenue by shift and by period |
| URD-SHF-008 | M | Closing with a non-zero discrepancy requires a reason note |
| URD-SHF-009 | S | The opening float is locked once the shift opens |
Acceptance
AC-SHF-01: A full shift
| Given | When | Then |
|---|---|---|
| Two staff enrolled in an open shift | They sell through the day and close | Z report is correct; expectedCash = openingFloat + cash sales − refunds + pay-ins − pay-outs; discrepancy = counted − expected |
| A shift is open on a device | A second device tries to open another | Behaviour follows the shift policy (shared vs per-device) |
ENT - Entitlements Built
Feature ID: sale/ENT · Phase: P2 · PRDs: PRD-ENT-001 · Dev: @nx/sale · @nx/core
What it does for users: a merchant sells something the customer draws down over time - a drinks bundle, a class pass, a membership window, a block of metered minutes. A policy defines what is sold (quota + validity + scope); selling its variant mints a per-customer grant that freezes a snapshot of the policy so later catalogue edits never alter a sold pass; each use writes an append-only redemption that draws the balance down, validates the item against the frozen scope, and can be reversed.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-ENT-001 | M | A policy binds 1:1 to a sellable variant (the variant sold as the entitlement) |
| URD-ENT-002 | M | A policy declares its limit dimensions: COUNT (discrete uses), TIME_WINDOW (valid window, no usage cap), METERED (continuous units); these may combine |
| URD-ENT-003 | M | A policy carries an optional quota (amount + unit) and an optional validity duration; either axis may be absent |
| URD-ENT-004 | M | A policy declares an activation mode - on purchase, on first use, or scheduled - governing when validity starts counting |
| URD-ENT-005 | S | A policy may require an attached customer (named entitlement) or allow a bearer entitlement |
| URD-ENT-006 | M | A policy's redemption scope is defined by targets; an empty target set means open access |
| URD-ENT-007 | M | A target binds a policy to a specific item (variant by default) with an ordering sequence; unique per (policy, item) |
| URD-ENT-008 | M | Selling the entitlement variant mints a grant with a unique code, a quota balance, and a validity window resolved from policy + activation mode |
| URD-ENT-009 | M | The grant freezes a snapshot of the policy and its targets at sale time, isolating it from later catalogue edits |
| URD-ENT-010 | M | A grant tracks quota total vs used; the used counter is the source of truth and available = total − used |
| URD-ENT-011 | M | A grant moves through PENDING → ACTIVE → EXHAUSTED / EXPIRED / SUSPENDED / CANCELLED |
| URD-ENT-012 | M | A redemption draws the used counter down for a quantity, recorded against its consuming order / item |
| URD-ENT-013 | S | A redemption quantity in a different unit from the grant's quota unit is converted via the unit ratio |
| URD-ENT-014 | M | A redemption validates the redeemed item against the grant's frozen target scope (open when no targets) |
| URD-ENT-015 | M | A reversal redemption returns quota to the grant; the ledger is append-only (corrections are new entries, never edits) |
| URD-ENT-016 | S | Redemptions can be linked to the sale order / order item that consumed them |
| URD-ENT-017 | S | Every policy, target, grant, and redemption is merchant-scoped and soft-deleted |
Acceptance
AC-ENT-01: Sell a grant
| Given | When | Then |
|---|---|---|
| A configured policy on a variant | The variant is sold | A grant is minted with a unique code, the policy's quota balance, and a validity window; status ACTIVE (or PENDING per activation mode) |
| A sold grant | The policy is later edited | The grant's terms are unchanged - read from its frozen snapshot |
AC-ENT-02: Redeem and exhaust
| Given | When | Then |
|---|---|---|
| An ACTIVE grant with balance remaining | A use is redeemed for a quantity | A redemption is appended and the used counter rises; available = total − used |
| The used counter reaches the quota total | - | The grant reads EXHAUSTED; further redeems are refused |
AC-ENT-03: Reversal returns quota
| Given | When | Then |
|---|---|---|
| A prior redeem on a grant | A reversal is recorded | An append-only reversal entry returns the quota; the original redeem is not edited or deleted |
AC-ENT-04: Target scope
| Given | When | Then |
|---|---|---|
| A grant scoped to specific items | A redeem targets an item outside the scope | Refused |
| A grant whose snapshot has no targets | A redeem targets any item | Allowed (open access) |
7. Constraints & Non-Goals
Constraints
| ID | Constraint |
|---|---|
| C-01 | Items are modifiable only while the order is in DRAFT |
| C-02 | Order split / merge only in DRAFT |
| C-03 | At most one open POS session per device |
| C-04 | Exactly one Z report per session |
| C-05 | Kitchen ticket send is idempotent via key |
| C-06 | All records use soft-delete |
| C-07 | All operations require authentication and are scoped to the merchant |
Non-Goals
- Refund / return flow
- Delivery order tracking
- Table-layout / seating management
- Dedicated kitchen display application
- Order templates
8. Version History
| Date | Author | Description | Ver |
|---|---|---|---|
| 2026-02-26 | P. Do - Product Owner | Initial user stories | v0.1 |
| 2026-04-16 | P. Do - Product Owner | Restructured to functional-area requirement tables; added combo, check-splitting, station, reservation, points areas | v0.3 |
| 2026-06-04 | Claude (AI pair) | Reorganize by feature (Feature Spine); each feature carries its own requirements + acceptance; CON moved to Constraints | v0.4 |
| 2026-06-15 | Sale squad | Add ENT Entitlements feature (policy / target / grant / redemption) with requirements + acceptance | v0.5 |
| 2026-06-15 | Sale squad | Extend ORD with split / merge / rollback + transfer-history lineage (URD-ORD-017..023, AC-ORD-03..05); link PRD-ORD-002 | v0.6 |