PRD: Sale order & cart
| Module | Sale | PRD ID | PRD-ORD-001 |
| Status | Ready to dev | FEAT | ORD |
| Epic | — | Plane | BANA-1520 |
| Date | 2025-12-31 | Version | v1.0 |
| Packages | @nx/sale | URD | ORD |
| Surface | Sale · POS | ||
| Owner | Phát Nguyễn | ||
TL;DR
Merchants build a sale as a draft cart, edit line items, then checkout - prices lock and the order enters a controlled lifecycle that drives it to PARTIAL or COMPLETED on payment. First end-to-end "cart → order → payment" spine of the Orders module; payment progress (status, timestamps, paid-amount tally) lives on the order. Shipping detail is not in this increment.
1. Context & Problem
The Orders capability starts as a skeleton 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 record that is both the cart (in DRAFT) and committed order, with line-item mutation in draft, a checkout that validates and locks prices, and lifecycle logic 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.
- Track payment progress on the order (status, timestamps, and a paid-amount tally) - a dedicated shipping-detail record is not built in this increment.
- Provide working order request/response contracts and a checkout action.
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 the order to track its payment progress, so I can see what has been paid; no shipping-detail record is in this increment.
- 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 | Track payment progress on the order (status, timestamps, paid-amount tally) - shipping detail is not built in this increment | 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 the merchant admin app): 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 |
|---|---|
| Order | The order document - plays both cart (DRAFT) and committed-order roles; carries status, totals, and a paid-amount tally |
| Order line item | A line item - product reference, quantity, locked price |
| Payment progress | Tracked on the order via status, timestamps, and the paid-amount tally - no separate payment-detail entity in this increment |
| Shipping detail | Not in this increment |
Conceptual only - full schema and invariants in the sale domain model.
10. Dependencies & Assumptions
Depends on
- Product (Products) - order items reference product variants.
- Payment (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 - URD
- Related: Inventory · Payment
- Developer: @nx/sale · domain model