PRD: Sale order & cart
| Module | Orders (CORE-07) | PRD ID | PRD-ORD-001 |
| Status | Shipped | Owner | Orders squad |
| Date | 2025-12-31 | Version | v1.0 |
| Packages | @nx/sale | URD | ORD |
TL;DR
Lets a merchant build a sale as a draft cart, add and edit its line items, then drive it through checkout into a payable order that reaches a paid/completed terminal state. This is the first end-to-end "cart → order → payment" spine of the Orders module - every order now has a controlled lifecycle, locked prices at checkout, and a place to hold payment and shipping detail.
1. Context & Problem
The order package starts as skeleton controllers (cart, cart-item, order, order-item, payment-info, shipping-info) with no working lifecycle: there is no way to build an order as a cart, mutate its lines, and move it through checkout to payment. Without that spine, nothing downstream - payment capture, kitchen tickets, POS sessions, loyalty - has an order to attach to.
This increment establishes the foundational sale-order lifecycle: a single entity that plays both the cart (while in DRAFT) and the committed order role, with line-item mutation in draft, a checkout path that validates and locks prices, and the order service that drives state transitions. It founds the Orders URD ORD feature that everything else in the module builds on.
2. Goals & Non-Goals
Goals
- Build an order as a draft cart and add/update/remove line items while in DRAFT.
- A controlled lifecycle from creation, through checkout into processing, to a paid/completed terminal state, with revert and cancellation paths.
- Checkout that validates the cart (non-empty, prices ≥ 0, quantity ≥ 1) and locks prices.
- Hold payment and shipping detail against the order (payment-info, shipping-info).
- Provide working order request/response contracts and a checkout endpoint.
Non-Goals
- Refund / return flow - a URD non-goal (Planned).
- Stock mutation - owned by Inventory.
- Payment-provider integration - owned by Payment.
- Combo fan-out, split/merge, customer link, and multi-currency - later refinements (URD-ORD-005, URD-ORD-012..016).
- Check splitting, kitchen tickets, stations, POS sessions, reservations, loyalty points - later F&B features (
CHK/KIT/STA/POS/RSV/PNT).
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Lifecycle coverage | 100% of sales flow through the order lifecycle (no order created outside DRAFT → checkout) |
| Checkout integrity | Zero orders reach PROCESSING with an empty cart, negative price, or zero quantity |
| Price stability | Prices locked at checkout do not drift before payment |
| Cycle time | Median time DRAFT → COMPLETED per merchant trends down |
4. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Cashier | Build a cart, edit lines, check out, and take an order to payment |
| Manager / Owner | Trust that every sale follows a controlled, auditable order lifecycle |
Core scenarios: create a draft cart → add/update/remove line items while in DRAFT → checkout (validate + lock prices) into PROCESSING → reach PARTIAL on partial payment or COMPLETED on full payment → cancel a non-terminal order or revert PROCESSING back to DRAFT.
5. User Stories
- As a cashier, I want to create a draft order and add line items to it, so I can build up a sale as a cart.
- As a cashier, I want to update or remove items while the order is in DRAFT, so I can correct the cart before committing.
- As a cashier, I want to check out a draft order, so the cart is validated, prices are locked, and the order is ready for payment.
- As a cashier, I want to revert a checked-out order back to DRAFT, so I can fix the cart after starting checkout.
- As a cashier, I want to record payment and shipping detail against the order, so the order carries the information a payment needs.
- As a cashier, I want to cancel a non-terminal order, so mistaken orders don't proceed to payment.
- As a manager, I want an order to reach PARTIAL on partial payment and COMPLETED on full payment, so the lifecycle reflects what was paid.
6. Functional Requirements
| # | Requirement | URD ref |
|---|---|---|
| FR-1 | Create a draft order (cart) with line items | URD-ORD-001 |
| FR-2 | Add, update, and remove items only while in DRAFT | URD-ORD-002 |
| FR-3 | Adding the same product merges quantity onto the existing line | URD-ORD-003 |
| FR-4 | Setting an item quantity to zero removes the line | URD-ORD-004 |
| FR-5 | Checkout: DRAFT → PROCESSING (validate items, lock prices) | URD-ORD-006 |
| FR-6 | Checkout validation: non-empty cart, prices ≥ 0, quantity ≥ 1 | URD-ORD-007 |
| FR-7 | Revert: PROCESSING → DRAFT | URD-ORD-008 |
| FR-8 | Cancel from DRAFT, PROCESSING, or PARTIAL (with optional reason) | URD-ORD-009 |
| FR-9 | Partial payment → PARTIAL status | URD-ORD-010 |
| FR-10 | Full payment → COMPLETED status | URD-ORD-011 |
| FR-11 | Hold payment-info and shipping-info against the order | URD-ORD-001 |
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 | Items are mutable only in DRAFT; checkout locks prices so totals can't drift before payment |
| Lifecycle integrity | Transitions are constrained to the defined state graph; no skipping from DRAFT straight to a paid state |
| Tenancy & authz | All operations scoped per merchant and require authentication |
| Consistency | Cart mutation and checkout are transactional; partial failures don't leave a half-built order |
| i18n | User-facing labels/statuses are bilingual ({ en, vi }) |
8. UX & Flows
Key screens (in apps/client): the cart/order builder, line-item editing, the checkout action, and a cancel/revert control. Full screen inventory lives in the Orders module flows.
9. Data & Domain
| Entity | Role |
|---|---|
SaleOrder | The order document - plays both cart (DRAFT) and committed-order roles; carries status and totals |
OrderItem | A line item - product reference, quantity, locked price |
PaymentInfo | Payment detail held against the order |
ShippingInfo | Shipping detail held against the order |
Conceptual only - full schema and invariants in the sale domain model.
10. Dependencies & Assumptions
Depends on
- Product (Products) - order items reference product variants.
- Payment (
@nx/payment) - payment events drive PARTIAL / COMPLETED / CANCELLED transitions.
Assumptions
- A merchant context is present on every request (merchant-scoped, authenticated).
- Products exist to add as line items before checkout.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| Cart mutation after checkout could corrupt locked totals | Items mutable only in DRAFT; revert to DRAFT is the supported path to edit |
| Lifecycle state values evolve as the package matures | URD states (DRAFT → PROCESSING → PARTIAL → COMPLETED / CANCELLED) are the source of truth; later features align to them |
| No refund/return path in this increment | Out of scope; owned by a Planned refund feature |
| Combo / split / multi-currency not yet supported | Deferred to later increments (URD-ORD-005, URD-ORD-012..016) |
12. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P1 (foundation) - see URD feature catalog |
| Rollout | All merchants; no feature flag |
| Migration | New entities; schema migrations for the order tables |
| Launch criteria | Create cart → edit lines → checkout → payment verified end-to-end; checkout validation rejects empty cart / bad price / bad quantity; prices locked at checkout |
| Monitoring | Order volume per merchant, checkout rejection rate, lifecycle transition errors |
13. FAQ
Can items be changed after checkout? No - items are editable only in DRAFT. Use the PROCESSING → DRAFT revert to make changes, or cancel.
What does checkout actually do? It validates the cart (non-empty, prices ≥ 0, quantity ≥ 1), locks prices, and moves the order DRAFT → PROCESSING so it is ready for payment.
What's the difference between PARTIAL and COMPLETED? PARTIAL means the order is underpaid; COMPLETED means it is fully paid.
Does this increment handle combos, bill splitting, or multi-currency? No - those are later features/refinements. This increment founds the core single-order lifecycle.
Where does stock get reserved or consumed? Not here - stock mutation is owned by Inventory. This feature only drives the order lifecycle.
References
- URD: Orders -
ORDSale Order - requirements and acceptance - Module: Orders - overview + traceability
- Related: Inventory · Payment
- Developer: @nx/sale · domain model