PRD: Check split & merge
| Module | Orders (CORE-07) | PRD ID | PRD-CHK-001 |
| Status | Shipped | Owner | Sale squad |
| Date | 2026-04-02 | Version | v1.0 |
| Packages | @nx/sale · @nx/core · apps/client | URD | CHK · ORD |
TL;DR
Lets a full-service venue split one table's order into several independently-payable checks - each with its own items, quantities, and even a different customer - and merge separate orders back together. The order completes automatically once all of its checks are paid, and the POS and kitchen displays stay in sync through real-time events. The result: groups settle their own portions of a shared bill without forcing the floor staff into manual workarounds.
1. Context & Problem
A full-service venue regularly serves one table whose guests want to pay separately - different people pay for their own items, sometimes under a different customer profile - and equally needs to combine orders that were opened separately for the same party. Treating an order as a single payable unit blocks both: there is no way to allocate specific items across payment groups, no way to complete an order check-by-check, and no clean way to fold one order into another.
This increment builds the SaleCheck aggregate on top of the existing sale-order lifecycle, the services that allocate items into checks and reconcile completion, and the order-merge path for combining two draft orders - closing a hard gap for F&B and any multi-guest bill scenario KICKO targets.
2. Goals & Non-Goals
Goals
- A SaleCheck aggregate that splits a checked-out order into multiple independently-payable checks.
- Per-check item allocation - assign specific items and quantities to each check, with overallocation guarded.
- A different customer per check, so each payment group can carry its own customer link.
- Order completes automatically once all of its checks complete, driven by the payment webhook.
- Rollback of a split while no check has been paid yet.
- Order merge for two PROCESSING orders in the same merchant/branch.
- Real-time events (
check.created/updated/merged/rolledBack/paid,order.merged) to keep POS and kitchen displays in sync.
Non-Goals
- Refund / return flow - URD Non-Goal (Planned).
- Payment provider integration itself - only the webhook hand-off is wired (see Payment).
- Tax engine / e-invoice issuance per check.
- Stock mutation on check allocation (see Inventory).
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Split adoption | Share of full-service orders that use checks where a split-bill is requested |
| Allocation integrity | Zero checks allocated beyond the order's available item quantities |
| Completion accuracy | Order completes only when all of its checks are complete - no early/late closes |
| Rollback safety | Rollback succeeds whenever no check is paid; never after a check has paid |
| Sync latency | POS / kitchen reflect check & merge events in real time |
4. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Cashier | Split a table's bill into checks, allocate items, take payment per check, merge orders |
| Server / Host | Combine separately-opened orders for one party |
| Manager / Owner | Trust that an order closes exactly when every check is settled |
Core scenarios: a checked-out order is split into multiple checks → items and quantities (and optionally a separate customer) are allocated to each → guests pay their own checks → the order completes once all checks complete; while nothing is paid, a split can be rolled back, and two draft orders for one party can be merged.
5. User Stories
- As a cashier, I want to split a checked-out order into multiple checks, so each guest can pay their own portion.
- As a cashier, I want to allocate specific items and quantities to a check, so the bill split reflects what each guest had.
- As a cashier, I want to attach a different customer to a check, so each payment group keeps its own customer link.
- As a cashier, I want the order to complete automatically once all checks are paid, so I don't have to close it manually.
- As a cashier, I want to roll back a split while no check is paid, so I can fix a mistaken split.
- As a server, I want to merge two orders for the same party, so a single bill represents the whole table.
6. Functional Requirements
| # | Requirement | URD ref |
|---|---|---|
| FR-1 | Split a checked-out order into multiple independently-payable checks via the SaleCheck aggregate | URD-CHK-001 |
| FR-2 | Allocate specific items and quantities to each check, with overallocation guarded | URD-CHK-002 |
| FR-3 | Allow a different customer per check | URD-CHK-003 |
| FR-4 | Order completes automatically when all of its checks complete, driven by the payment webhook | URD-CHK-004 |
| FR-5 | Roll back a split while no check has been paid | URD-CHK-005 |
| FR-6 | Split one order into multiple, DRAFT only | URD-ORD-012 |
| FR-7 | Merge multiple orders, DRAFT only / same merchant/branch | URD-ORD-013 |
| FR-8 | Emit real-time events: check.created/updated/merged/rolledBack/paid and order.merged | URD-CHK-001..005 |
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 |
|---|---|
| Data integrity | Per-check allocation never exceeds the order's available quantities; completion state is derived, not hand-set |
| Consistency | Split, allocation, rollback, and merge are transactional - a partial failure leaves no half-written checks |
| Tenancy & authz | All operations scoped per merchant (x-merchant-id); merge restricted to the same merchant/branch; permission-gated |
| Real-time | Check and merge state changes broadcast via WebSocket events for POS/KDS sync |
| Soft-delete | All records use soft-delete |
| i18n | User-facing labels/statuses are bilingual ({ en, vi }) |
8. UX & Flows
Key screens (in apps/client): the order detail with a split-bill action, the per-check allocation editor, per-check payment, and an order-merge action.
9. Data & Domain
| Entity | Role |
|---|---|
SaleCheck | An independently-payable group within an order - status, optional customer, totals |
CheckItem | A per-check allocation of an order item - item ref + quantity |
SaleOrder | The parent order; completes when all its checks complete |
| Real-time events | SaleCheckEvents - check.created/updated/merged/rolledBack/paid, order.merged |
Conceptual only - full schema and invariants in the sale domain model.
10. Dependencies & Assumptions
Depends on
- Sale Order lifecycle (URD-ORD) - a check is split from a checked-out order; merge operates on draft/processing orders.
- Payment (
@nx/payment) - the payment webhook drives check completion and, in turn, order completion. - Customer - optional per-check customer link.
Assumptions
- The order has been checked out (PROCESSING) before it is split into checks.
- Payment events arrive via the webhook to mark a check paid.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| Allocation could exceed available quantities | Overallocation is guarded at allocation time |
| Rollback after a check has paid | Rollback only permitted while no check is paid |
| Completion races as multiple checks pay near-simultaneously | Completion derived from all-checks-complete; webhook-driven, idempotent |
| Stock not mutated per check allocation | Out of scope - owned by Inventory |
| Tax / e-invoice per check | Out of scope - owned by Tax & Invoice |
12. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P2 - see Orders feature catalog (MoSCoW: Should) |
| Rollout | All merchants; no feature flag |
| Migration | New SaleCheck aggregate (schema + migration); no data backfill |
| Launch criteria | Split → allocate → pay-per-check → order auto-completes verified end-to-end; rollback verified while unpaid; order merge verified for same merchant/branch |
| Monitoring | Split volume per merchant, overallocation rejection rate, check-vs-order completion consistency, event delivery |
13. FAQ
Can I split an order before checkout? No - a check is split from a checked-out (PROCESSING) order. Order split/merge itself is DRAFT-only.
Can each check have its own customer? Yes - a different customer can be attached per check.
When does the order complete? Automatically, once all of its checks complete - driven by the payment webhook, not a manual close.
Can I undo a split? Yes, by rolling it back - but only while no check has been paid.
Does splitting a bill change stock or issue invoices per check? No - stock mutation is owned by Inventory and tax / e-invoice issuance is out of scope here.
References
- URD: Orders - CHK (Check Splitting) · ORD (split/merge)
- Related: Payment · Inventory
- Module: Orders - overview + traceability
- Developer: @nx/sale · domain model