PRD: Tách, gộp & hoàn tác đơn hàng
| Module | Bán hàng | PRD ID | PRD-ORD-002 |
| Trạng thái | Sẵn sàng dev | FEAT | ORD |
| Epic | — | Plane | BANA-1518 |
| Ngày | 2026-06-15 | Phiên bản | v1.0 |
| Gói | @nx/sale | URD | ORD |
| Surface | Sale · POS | ||
| Phụ trách | Phát Nguyễn | ||
TL;DR
Thu ngân có thể tách một draft thành nhiều draft mới (phân bổ mục và số lượng cho từng đơn, kể cả tách dòng theo phần), gộp nhiều draft thành một (huỷ các đơn nguồn), hoặc hoàn tác một lần gộp để mọi mục trở về đúng đơn cũ - tất cả trong một giao dịch nguyên tử. Combo luôn di chuyển nguyên khối, giữ chỗ bàn/ghế đi theo mục, và lineage transfer-history append-only cho phép đảo ngược mọi lần gộp.
Ghi chú phạm vi: Các thao tác mang theo giữ chỗ bàn/ghế (allocation usage) của đơn - clone khi tách, move khi gộp, cancel khi hoàn tác. Giữ chỗ tồn kho (stock reservation) không được xử lý riêng; được bảo toàn ngầm vì mục chỉ di chuyển trong cùng merchant và location. Re-pricing khác theo thao tác: tách chạy lại pricing engine cho các đơn mới và đơn giữ lại; gộp và hoàn tác tính lại tổng từ giá dòng đã khoá.
1. Bối cảnh & Vấn đề
Một đơn nháp (ORD) đã đóng cả vai trò giỏ lẫn đơn đã chốt, nhưng một quầy phục vụ thực tế liên tục tái định hình các đơn nháp: hai người bạn ngồi chung giờ muốn hai hoá đơn riêng; ba tab mở nhầm nên gộp lại một; một lần gộp vội phải được huỷ. Riêng vòng đời đơn không cho cách nào để di chuyển mục giữa các đơn nháp - thu ngân sẽ phải huỷ rồi nhập lại, mất giá, liên kết khách hàng, và giữ chỗ bàn/ghế.
Không có cơ chế tái cấu trúc chính thức, quầy phải từ chối các yêu cầu thường ngày này hoặc dựng lại đơn thủ công - làm rớt giữ chỗ bàn/ghế, định giá sai combo, và không để lại dấu vết nào về những gì đã di chuyển. PRD này lấp khoảng trống đó: các thao tác tách / gộp / hoàn tác khai báo di chuyển mục (và giữ chỗ bàn/ghế của chúng) nguyên tử, giữ combo nguyên vẹn, và ghi lineage có thể đảo ngược để mọi lần gộp đều có thể đi ngược lại.
2. Mục tiêu & Ngoài phạm vi
Mục tiêu
- Tách một đơn nháp thành N đơn nháp mới bằng cách phân bổ mục và số lượng, với di chuyển nguyên số lượng và tách dòng theo một phần số lượng.
- Gộp một hoặc nhiều đơn nháp vào một đơn đích và huỷ các đơn nguồn đã rỗng.
- Hoàn tác một lần gộp để mọi mục trở về đơn nguồn ban đầu, khôi phục về nháp.
- Giữ combo nguyên tử - một dòng đầu và mọi dòng con luôn di chuyển cùng nhau với đầy đủ số lượng.
- Đóng dấu một lineage transfer-history append-only trên mọi mục đã di chuyển để đường đi có thể truy vết và đảo ngược.
- Mang giữ chỗ bàn/ghế (allocation usage) đi cùng các mục và re-price mọi đơn bị ảnh hưởng, tất cả trong một giao dịch (giữ chỗ tồn kho không được move riêng - xem ghi chú thuật ngữ ở trên).
Ngoài phạm vi
- Tách hoá đơn thành các check trả độc lập - đó là CHK (PRD-CHK-001); PRD này tái định hình đơn hàng, không phải check thanh toán.
- Tách hoặc gộp đơn không phải nháp - tái cấu trúc chỉ áp dụng cho DRAFT (C-02).
- Tái cấu trúc một đơn đang có check hoạt động - bị chặn cho tới khi các check được hoàn tác.
- Logic thanh toán, hoàn tiền, hay thay đổi tồn kho - thuộc Payment và Inventory.
3. Chỉ số thành công
| Chỉ số | Mục tiêu / tín hiệu |
|---|---|
| Tái định hình không nhập lại | Thu ngân tách, gộp, hoặc hoàn tác từ một thao tác - không huỷ-rồi-nhập-lại |
| Tính nguyên tử | Một bước con lỗi để mọi đơn tham gia y nguyên như trước |
| Toàn vẹn combo | Không lần tách nào bỏ rơi dòng đầu hay dòng con combo; thành viên combo không bao giờ di chuyển một phần |
| Khả năng đảo ngược | Mọi lần gộp đều hoàn tác được về các đơn gốc chỉ bằng lineage đã ghi |
| Liên tục giữ chỗ | Giữ chỗ bàn/ghế (allocation usage) đi theo các mục - không giữ trùng, không mất giữ (giữ chỗ tồn kho được bảo toàn ngầm, không move riêng) |
4. Nhân vật & Tình huống
| Nhân vật | Mục tiêu trong tính năng này |
|---|---|
| Thu ngân | Tách một tab chung thành các hoá đơn riêng, hoặc gộp các tab nhầm, trong một bước |
| Quản lý | Huỷ một lần gộp sai gọn gàng, với các đơn gốc được khôi phục nguyên vẹn |
| Chủ | Tin rằng mọi lần tái định hình giữ đúng giá, combo, và giữ chỗ bàn/ghế |
Tình huống chính: một bàn bốn người mở một đơn nháp nhưng muốn hai hoá đơn. Thu ngân tách đơn - gán hai món chính và một món khai vị dùng chung (đầy đủ số lượng) cho hoá đơn A và phần còn lại cho hoá đơn B - và hệ thống tạo hai đơn nháp mới, di chuyển các dòng (tách những dòng dùng chung theo số lượng), mang giữ chỗ bàn/ghế, re-price cả hai, và huỷ đơn gốc nay đã rỗng. Sau đó họ nhận ra nên là một hoá đơn, gộp B trở lại A, và - phát hiện lỗi thứ ba - hoàn tác lần gộp để B được khôi phục đúng như cũ.
5. User Stories
- Là thu ngân, tôi tách một đơn nháp thành các đơn nháp mới bằng cách chọn mục và số lượng cho từng hoá đơn, để một tab chung thành các hoá đơn riêng trong một bước.
- Là thu ngân, tôi lấy hết số lượng một dòng hoặc chỉ một phần, để có thể di chuyển cả món hoặc chỉ hai trong năm.
- Là thu ngân, tôi gộp nhiều đơn nháp thành một, để các tab tách nhầm thành một hoá đơn và các tab kia đóng lại.
- Là quản lý, tôi hoàn tác một lần gộp, để một lần nhập nhầm được huỷ và mọi mục trở về đơn gốc.
- Là chủ, tôi muốn combo di chuyển như một khối và giữ chỗ bàn/ghế đi theo các mục, để tái định hình không bao giờ làm hỏng một bundle hay phần giữ chỗ của nó.
6. Yêu cầu chức năng
| # | Yêu cầu | URD ref |
|---|---|---|
| FR-1 | Tách một đơn DRAFT thành N đơn DRAFT mới, mỗi đơn được phân bổ mục và số lượng cụ thể, kèm tên và khách hàng tuỳ chọn cho từng đơn mới | URD-ORD-012 · URD-ORD-017 |
| FR-2 | Một dòng được gán hết số lượng sẽ di chuyển nguyên dòng; gán một phần sẽ tách dòng, để lại phần số lượng còn lại trên đơn nguồn | URD-ORD-017 |
| FR-3 | Dòng đầu của một combo và mọi dòng con phải di chuyển cùng nhau, đầy đủ số lượng, vào cùng một đơn đích; di chuyển combo một phần hoặc bỏ rơi sẽ bị từ chối | URD-ORD-018 |
| FR-4 | Một đơn nguồn rỗng hoàn toàn sẽ bị huỷ (đánh dấu là tách toàn bộ); một đơn nguồn tách một phần được giữ lại và re-price | URD-ORD-017 |
| FR-5 | Tách từ chối số lượng không dương, một mục không tồn tại, hoặc over-allocation (gán vượt số lượng khả dụng của dòng) | URD-ORD-017 |
| FR-6 | Gộp di chuyển mọi mục của một hoặc nhiều đơn DRAFT nguồn vào một đơn DRAFT đích và huỷ các đơn nguồn (đánh dấu đã gộp vào đơn đích) | URD-ORD-013 · URD-ORD-019 |
| FR-7 | Chỉ các đơn cùng merchant và cùng sale channel mới được gộp | URD-ORD-019 |
| FR-8 | Mọi mục đã di chuyển ghi thêm một transfer-history entry (nguồn, đích, thời điểm, số lượng) tạo nên lineage tách / gộp | URD-ORD-020 |
| FR-9 | Một lần gộp có thể hoàn tác khi đơn đích vẫn DRAFT và đã được gộp; các mục trở về đơn nguồn ban đầu qua lineage, và mỗi đơn nguồn được khôi phục về DRAFT | URD-ORD-021 |
| FR-10 | Khi hoàn tác, số lượng thêm vào một dòng sau khi gộp sẽ ở lại đơn đích; chỉ số lượng đã di chuyển ban đầu trở về đơn nguồn | URD-ORD-021 |
| FR-11 | Tách, gộp và hoàn tác là nguyên tử; giữ chỗ bàn/ghế (allocation usage) được clone (tách) / move (gộp) / cancel (hoàn tác) đồng bộ và các đơn bị ảnh hưởng được re-price (tách qua pricing engine; gộp/hoàn tác tính lại tổng từ giá dòng đã khoá) | URD-ORD-022 |
| FR-12 | Một đơn có check hoạt động không thể tách, gộp, hay hoàn tác | URD-ORD-023 |
Toàn văn yêu cầu và tiêu chí nghiệm thu nằm trong URD Bán hàng - ORD. PRD này tham chiếu thay vì lặp lại.
7. Yêu cầu phi chức năng
| Lĩnh vực | Yêu cầu |
|---|---|
| Tính nguyên tử | Mỗi thao tác là một giao dịch tất-cả-hoặc-không - một bước con lỗi (di chuyển mục, huỷ nguồn, re-price) cuộn cả lần tái định hình về |
| Đồng thời | Các đơn tham gia và mục của chúng bị khoá suốt thời gian thao tác để hai thu ngân không tái định hình cùng một đơn nháp một lúc |
| Khả năng truy vết | Lineage transfer-history là append-only - một lần di chuyển thêm một entry, một lần hoàn tác chỉ gỡ entry cuối; chỉnh sửa là trạng thái mới, không bao giờ sửa lịch sử |
| Liên tục giữ chỗ | Giữ chỗ bàn/ghế (allocation usage) đi theo các mục - clone khi tách, move khi gộp, cancel khi hoàn tác - nên không bao giờ mất hay nhân đôi; giữ chỗ tồn kho không được thao tác riêng (bảo toàn vì các mục ở trong cùng merchant/location) |
| Định giá | Mọi đơn có mục thay đổi đều được tính lại tổng; tách chạy lại pricing engine cho cả các đơn mới lẫn đơn nguồn tách một phần được giữ lại, còn gộp và hoàn tác tính lại tổng đơn từ giá dòng đã khoá sẵn |
| Tenancy & authz | Mọi thao tác giới hạn trong merchant của chính người dùng và gác bởi quyền tách / gộp / hoàn tác |
| i18n | Nhãn người dùng và lý do từ chối là song ngữ (EN + VI) |
8. UX & Luồng
Tách một đơn nháp
Gộp rồi hoàn tác
Mặt thu ngân cung cấp một panel tách (gán mục và số lượng cho một hoặc nhiều hoá đơn mới, đặt tên từng cái, tuỳ chọn gắn khách hàng), một thao tác gộp (chọn nguồn và đích), và một thao tác hoàn tác trên một đơn nháp đã gộp.
9. Dữ liệu & Miền
| Thực thể | Vai trò trong tái định hình |
|---|---|
| Đơn hàng | Đơn nháp đang được tái định hình; mang các dấu mốc do mỗi thao tác đóng (timestamp tách / gộp, lý do huỷ, liên kết gốc) |
| Dòng đơn hàng | Dòng di chuyển giữa các đơn; di chuyển nguyên sẽ dời nó, di chuyển một phần sẽ tách nó thành một dòng mới |
| Mục transfer-history | Một bước append-only của lineage một dòng - đơn nguồn, đơn đích, thời điểm, và số lượng đã di chuyển; entry cuối báo cho hoàn tác biết đưa dòng về đâu |
Chỉ mang tính khái niệm - schema và bất biến đầy đủ nằm trong domain model bán hàng. Quan hệ đơn-tới-đơn được hệ thống theo dõi, không ràng buộc cứng; toàn vẹn được thực thi bởi logic tách / gộp.
10. Phụ thuộc & Giả định
Phụ thuộc vào
- Vòng đời đơn bán hàng (ORD) - tái cấu trúc chỉ chạy trên đơn DRAFT và tái dùng các chuyển checkout / huỷ.
- Tách combo (URD-ORD-005) - cấu trúc dòng đầu / con mà guard nguyên tử bảo vệ.
- Tách hoá đơn (CHK) - một đơn có check hoạt động bị chặn tái định hình cho tới khi các check được hoàn tác.
- Giữ chỗ bàn/ghế (allocation usage) - clone / move / cancel đồng bộ với các mục (giữ chỗ tồn kho không được xử lý riêng ở đây).
- Nền tảng core - định nghĩa đơn và dòng hàng bán, các helper trạng thái, và cấu trúc transfer-history.
Giả định
- Thu ngân chỉ tái định hình các đơn nháp của merchant và channel của chính mình.
- Giữ chỗ bàn/ghế (allocation usage) của đơn nguồn là phần giữ chỗ đi theo các mục.
11. Rủi ro & Câu hỏi mở
| Rủi ro / câu hỏi | Giảm thiểu / trạng thái |
|---|---|
| Một lỗi một phần để các đơn tái định hình dở dang | Mỗi thao tác là một giao dịch tất-cả-hoặc-không với khoá - mọi lỗi cuộn cả lần tái định hình về |
| Một lần tách bỏ rơi dòng đầu hay con combo | Guard nguyên tử trước - thành viên combo phải di chuyển cùng nhau với đầy đủ số lượng, nếu không sẽ bị từ chối |
| Hoàn tác không biết một mục xuất phát từ đâu | Lineage transfer-history append-only ghi mỗi lần di chuyển; hoàn tác đọc entry cuối theo từng dòng |
| Số lượng đổi sau một lần gộp, rồi hoàn tác | Phần dư thêm sau khi gộp ở lại đích; chỉ số lượng đã di chuyển ban đầu trở về |
| Hai thu ngân tái định hình cùng một đơn nháp một lúc | Các đơn tham gia và mục bị khoá suốt thời gian thao tác |
| Giữ chỗ bàn/ghế mất hay tính trùng | Giữ chỗ bàn/ghế (allocation usage) clone khi tách, move khi gộp, cancel khi hoàn tác - đồng bộ với các mục; giữ chỗ tồn kho được bảo toàn ngầm (các mục ở trong cùng merchant/location) |
12. Kế hoạch phát hành & Tiêu chí ra mắt
| Khía cạnh | Kế hoạch |
|---|---|
| Phase | P2 - năng lực reshape ORD trong danh mục tính năng URD |
| Triển khai | Mọi merchant; không feature flag |
| Migration | Không - reshape chạy trên model đơn và mục nháp sẵn có; transfer-history là một field bổ sung |
| Tiêu chí ra mắt | Tách phân bổ mục / số lượng vào các đơn nháp mới (di chuyển nguyên và một phần) và huỷ hoặc re-price đơn nguồn; gộp nhập các nguồn vào một đích và huỷ chúng; hoàn tác khôi phục các nguồn từ lineage; combo di chuyển nguyên tử; đơn có check hoạt động bị chặn; mọi thao tác nguyên tử với giữ chỗ bàn/ghế đi theo các mục |
| Giám sát | Tỉ lệ lỗi reshape theo lý do (over-allocation, combo bị bỏ rơi, check hoạt động, chưa gộp), tỉ lệ thành công hoàn tác, nhất quán giữ chỗ bàn/ghế sau reshape |
13. FAQ
Cái này khác tách hoá đơn thế nào? Tách hoá đơn (CHK) chia một đơn thành các check trả độc lập cho cùng một hoá đơn. Cái này tái định hình chính các đơn - di chuyển mục giữa các đơn nháp riêng. Xem PRD-CHK-001.
Tôi tách hay gộp được một đơn đã qua nháp không? Không - reshape chỉ áp dụng DRAFT (C-02). Một khi đơn checkout, các mục của nó bị khoá.
Combo ra sao khi tôi tách? Nó di chuyển như một khối. Dòng đầu và mọi dòng con phải đi tới cùng một hoá đơn mới với đầy đủ số lượng; gán chỉ một phần combo sẽ bị từ chối trước khi lưu bất cứ gì.
Nếu tôi chỉ lấy một phần số lượng một dòng, phần còn lại ra sao? Dòng sẽ tách - số lượng đã lấy thành một dòng trên đơn mới, phần còn lại ở lại nguồn, và cả hai được re-price.
Mọi lần gộp đều huỷ được không? Có, khi đơn đích vẫn nháp và không có check hoạt động. Hoàn tác dùng lineage transfer-history của từng dòng để đưa nó về đúng đơn nó xuất phát; số lượng thêm sau khi gộp ở lại đích.
Giữ chỗ bàn/ghế sống sót qua một lần reshape không? Có - giữ chỗ bàn/ghế (allocation usage) được clone khi tách, move khi gộp, và cancel khi hoàn tác, đồng bộ với các mục, nên không bao giờ mất hay nhân đôi. Giữ chỗ tồn kho không được move riêng; chúng được bảo toàn vì các mục chỉ di chuyển giữa các đơn của cùng merchant và location.
Tài liệu liên quan
- URD: Bán hàng - ORD
- PRD liên quan: Đơn bán hàng & giỏ hàng · Tách & gộp check
- Module: Bán hàng - URD
- Dev: @nx/sale · @nx/core · domain model