PRD: Tách và gộp check
| Module | Đơn hàng (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
Cho phép một quán phục vụ tại bàn tách đơn hàng của một bàn thành nhiều check thanh toán độc lập - mỗi check có item, số lượng riêng, thậm chí một khách hàng khác - và gộp các đơn hàng riêng lẻ lại với nhau. Đơn hàng tự động hoàn tất khi tất cả check của nó đã được thanh toán, và màn hình POS lẫn màn hình bếp luôn đồng bộ qua các sự kiện real-time. Kết quả: các nhóm khách tự thanh toán phần của mình trên một hoá đơn chung mà không buộc nhân viên phải xử lý thủ công.
1. Context & Problem
Một quán phục vụ tại bàn thường xuyên phục vụ một bàn mà khách muốn thanh toán riêng - mỗi người trả cho item của mình, đôi khi dưới một hồ sơ khách hàng khác - và cũng cần gộp các đơn hàng được mở riêng cho cùng một nhóm khách. Coi đơn hàng như một đơn vị thanh toán duy nhất chặn cả hai nhu cầu: không có cách phân bổ item cụ thể giữa các nhóm thanh toán, không có cách hoàn tất đơn hàng theo từng check, và không có cách gọn gàng để gộp một đơn hàng vào đơn khác.
Increment này xây dựng aggregate SaleCheck trên nền vòng đời sale order hiện có, các service phân bổ item vào từng check và đối soát việc hoàn tất, cùng luồng gộp đơn để kết hợp hai đơn hàng nháp - lấp một khoảng trống lớn cho F&B và mọi tình huống hoá đơn nhiều khách mà KICKO hướng tới.
2. Goals & Non-Goals
Goals
- Một aggregate SaleCheck tách một đơn hàng đã checkout thành nhiều check thanh toán độc lập.
- Phân bổ item theo từng check - gán item và số lượng cụ thể cho mỗi check, có chặn phân bổ vượt mức.
- Một khách hàng khác cho mỗi check, để mỗi nhóm thanh toán mang liên kết khách hàng riêng.
- Đơn hàng tự động hoàn tất khi tất cả check của nó hoàn tất, được điều khiển bởi payment webhook.
- Hoàn tác việc tách khi chưa có check nào được thanh toán.
- Gộp đơn hàng cho hai đơn PROCESSING trong cùng merchant/branch.
- Sự kiện real-time (
check.created/updated/merged/rolledBack/paid,order.merged) để giữ màn hình POS và bếp đồng bộ.
Non-Goals
- Luồng hoàn tiền / trả hàng - Non-Goal của URD (Planned).
- Tích hợp nhà cung cấp thanh toán - chỉ nối phần bàn giao qua webhook (xem Thanh toán).
- Engine thuế / phát hành hoá đơn điện tử theo từng check.
- Thay đổi tồn kho khi phân bổ check (xem Kho hàng).
3. Success Metrics
| Metric | Target / tín hiệu |
|---|---|
| Mức dùng tách | Tỷ lệ đơn phục vụ tại bàn dùng check khi có yêu cầu tách hoá đơn |
| Toàn vẹn phân bổ | Không check nào được phân bổ vượt quá số lượng item còn lại của đơn |
| Độ chính xác hoàn tất | Đơn chỉ hoàn tất khi tất cả check của nó hoàn tất - không đóng sớm/muộn |
| An toàn hoàn tác | Hoàn tác thành công khi chưa check nào được thanh toán; không bao giờ sau khi một check đã trả |
| Độ trễ đồng bộ | POS / bếp phản ánh sự kiện check & gộp theo real-time |
4. Personas & Use Cases
| Persona | Mục tiêu trong tính năng này |
|---|---|
| Thu ngân | Tách hoá đơn của một bàn thành các check, phân bổ item, thu tiền theo từng check, gộp đơn |
| Phục vụ / Lễ tân | Kết hợp các đơn mở riêng cho cùng một nhóm khách |
| Quản lý / Chủ | Tin rằng một đơn đóng đúng lúc mọi check đã thanh toán xong |
Core scenarios: một đơn đã checkout được tách thành nhiều check → item và số lượng (và tuỳ chọn một khách hàng riêng) được phân bổ cho mỗi check → khách thanh toán check của mình → đơn hàng hoàn tất khi tất cả check hoàn tất; khi chưa có gì được thanh toán, việc tách có thể được hoàn tác, và hai đơn nháp cho cùng một nhóm khách có thể được gộp.
5. User Stories
- Là một thu ngân, tôi muốn tách một đơn đã checkout thành nhiều check, để mỗi khách tự trả phần của mình.
- Là một thu ngân, tôi muốn phân bổ item và số lượng cụ thể cho một check, để việc tách hoá đơn phản ánh đúng những gì mỗi khách đã dùng.
- Là một thu ngân, tôi muốn gắn một khách hàng khác cho một check, để mỗi nhóm thanh toán giữ liên kết khách hàng riêng.
- Là một thu ngân, tôi muốn đơn hàng tự động hoàn tất khi tất cả check đã trả, để không phải đóng đơn thủ công.
- Là một thu ngân, tôi muốn hoàn tác việc tách khi chưa check nào được trả, để sửa một lần tách nhầm.
- Là một phục vụ, tôi muốn gộp hai đơn cho cùng một nhóm khách, để một hoá đơn duy nhất đại diện cho cả bàn.
6. Functional Requirements
| # | Yêu cầu | URD ref |
|---|---|---|
| FR-1 | Tách một đơn đã checkout thành nhiều check thanh toán độc lập qua aggregate SaleCheck | URD-CHK-001 |
| FR-2 | Phân bổ item và số lượng cụ thể cho mỗi check, có chặn phân bổ vượt mức | URD-CHK-002 |
| FR-3 | Cho phép một khách hàng khác cho mỗi check | URD-CHK-003 |
| FR-4 | Đơn tự động hoàn tất khi tất cả check của nó hoàn tất, điều khiển bởi payment webhook | URD-CHK-004 |
| FR-5 | Hoàn tác việc tách khi chưa có check nào được thanh toán | URD-CHK-005 |
| FR-6 | Tách một đơn thành nhiều đơn, chỉ ở DRAFT | URD-ORD-012 |
| FR-7 | Gộp nhiều đơn, chỉ ở DRAFT / cùng merchant/branch | URD-ORD-013 |
| FR-8 | Phát sự kiện real-time: check.created/updated/merged/rolledBack/paid và order.merged | URD-CHK-001..005 |
Toàn văn yêu cầu và tiêu chí chấp nhận nằm trong URD Đơn hàng. PRD này tham chiếu chúng thay vì lặp lại.
7. Non-Functional Requirements
| Khía cạnh | Yêu cầu |
|---|---|
| Toàn vẹn dữ liệu | Phân bổ theo từng check không bao giờ vượt số lượng còn lại của đơn; trạng thái hoàn tất là dẫn xuất, không gán tay |
| Nhất quán | Tách, phân bổ, hoàn tác và gộp là một thao tác duy nhất (transactional) - lỗi một phần không để lại check viết dở |
| Tenancy & authz | Mọi thao tác scope theo merchant (x-merchant-id); gộp giới hạn trong cùng merchant/branch; có gating theo permission |
| Real-time | Thay đổi trạng thái check và gộp được broadcast qua sự kiện WebSocket để đồng bộ POS/KDS |
| Soft-delete | Mọi bản ghi dùng soft-delete |
| i18n | Nhãn/trạng thái hiển thị cho người dùng là song ngữ ({ en, vi }) |
8. UX & Flows
Màn hình chính (trong apps/client): chi tiết đơn với thao tác tách hoá đơn, trình phân bổ theo từng check, thanh toán theo từng check, và thao tác gộp đơn.
9. Data & Domain
| Entity | Vai trò |
|---|---|
SaleCheck | Một nhóm thanh toán độc lập trong một đơn - trạng thái, khách hàng tuỳ chọn, tổng tiền |
CheckItem | Một phân bổ theo check của một order item - tham chiếu item + số lượng |
SaleOrder | Đơn cha; hoàn tất khi tất cả check của nó hoàn tất |
| Sự kiện real-time | SaleCheckEvents - check.created/updated/merged/rolledBack/paid, order.merged |
Chỉ ở mức khái niệm - schema đầy đủ và bất biến trong domain model của sale.
10. Dependencies & Assumptions
Phụ thuộc vào
- Vòng đời Sale Order (URD-ORD) - một check được tách từ một đơn đã checkout; gộp thao tác trên đơn nháp/processing.
- Thanh toán (
@nx/payment) - payment webhook điều khiển việc hoàn tất check và, qua đó, hoàn tất đơn. - Khách hàng - liên kết khách hàng tuỳ chọn theo từng check.
Giả định
- Đơn đã được checkout (PROCESSING) trước khi tách thành các check.
- Sự kiện thanh toán đến qua webhook để đánh dấu một check đã trả.
11. Risks & Open Questions
| Rủi ro / câu hỏi | Giảm thiểu / trạng thái |
|---|---|
| Phân bổ có thể vượt số lượng còn lại | Chặn phân bổ vượt mức ngay tại thời điểm phân bổ |
| Hoàn tác sau khi một check đã trả | Chỉ cho hoàn tác khi chưa check nào được trả |
| Tranh chấp khi nhiều check trả gần như đồng thời | Hoàn tất dẫn xuất từ tất-cả-check-hoàn-tất; điều khiển bởi webhook, idempotent |
| Tồn kho không thay đổi theo phân bổ check | Ngoài phạm vi - thuộc Kho hàng |
| Thuế / hoá đơn điện tử theo từng check | Ngoài phạm vi - thuộc Thuế & Hóa đơn |
12. Release Plan & Launch Criteria
| Khía cạnh | Kế hoạch |
|---|---|
| Phase | P2 - xem danh mục tính năng Đơn hàng (MoSCoW: Should) |
| Rollout | Mọi merchant; không có feature flag |
| Migration | Aggregate SaleCheck mới (schema + migration); không backfill dữ liệu |
| Tiêu chí ra mắt | Tách → phân bổ → trả-theo-từng-check → đơn tự hoàn tất được kiểm chứng đầu-cuối; hoàn tác được kiểm chứng khi chưa trả; gộp đơn được kiểm chứng cho cùng merchant/branch |
| Giám sát | Lượng tách theo merchant, tỷ lệ từ chối phân bổ vượt mức, nhất quán hoàn tất check-vs-đơn, việc giao sự kiện |
13. FAQ
Tôi có thể tách một đơn trước khi checkout không? Không - một check được tách từ một đơn đã checkout (PROCESSING). Bản thân việc tách/gộp đơn chỉ ở DRAFT.
Mỗi check có thể có khách hàng riêng không? Có - có thể gắn một khách hàng khác cho mỗi check.
Khi nào đơn hoàn tất? Tự động, khi tất cả check của nó hoàn tất - điều khiển bởi payment webhook, không phải đóng thủ công.
Tôi có thể hoàn tác một lần tách không? Có, bằng cách hoàn tác - nhưng chỉ khi chưa check nào được thanh toán.
Tách hoá đơn có thay đổi tồn kho hay phát hành hoá đơn theo từng check không? Không - thay đổi tồn kho thuộc Kho hàng và phát hành thuế / hoá đơn điện tử nằm ngoài phạm vi ở đây.
References
- URD: Đơn hàng - CHK (Tách hoá đơn) · ORD (tách/gộp)
- Liên quan: Thanh toán · Kho hàng
- Module: Đơn hàng - tổng quan + truy vết
- Developer: @nx/sale · domain model