PRD: Tách, gộp & hoàn tác đơn hàng
| Module | Bán hàng (CORE-07) | PRD ID | PRD-ORD-002 |
| Trạng thái | Shipped | Owner | Sale squad |
| Ngày | 2026-06-15 | Phiên bản | v1.0 |
| Packages | @nx/sale · @nx/core | URD | ORD |
TL;DR
Cho phép thu ngân tái cấu trúc các đơn nháp của một bàn mà không phải dựng lại bằng tay. Tách chia một đơn nháp thành nhiều đơn nháp mới, phân bổ mục và số lượng đã chọn (kèm tên / khách hàng tuỳ chọn) cho từng đơn; một dòng bị lấy hết số lượng sẽ di chuyển nguyên dòng, lấy một phần thì tách dòng và để lại phần còn lại. Gộp nhập nhiều đơn nháp thành một và huỷ các đơn nguồn. Hoàn tác đảo ngược một lần gộp - mọi mục trở lại đúng đơn nó xuất phát. Combo luôn di chuyển như một khối, một chuỗi lineage transfer-history append-only đóng dấu mọi lần di chuyển, tồn kho đã giữ đi cùng các mục, và mỗi thao tác là một giao dịch tất-cả-hoặc-không có re-pricing các đơn nó chạm tới.
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à tồn kho đã giữ.
Thiếu một đường tái cấu trúc hạng nhất, quầy hoặc từ chối các yêu cầu hằng ngày này hoặc dựng lại đơn bằng tay, làm rớt tồn kho đã giữ, định giá sai combo, và không để lại dấu vết gì đã đi đâu. Increment này lấp khoảng trống đó trên nền đơn nháp sẵn có: các thao tác tách / gộp / hoàn tác khai báo, di chuyển mục (và tồn kho đã giữ của chúng) nguyên tử, giữ combo nguyên vẹn, và ghi một lineage có thể đảo ngược để một lần gộp luôn 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 tồn kho đã giữ (allocation usages) đ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.
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 tồn kho | Tồn kho đã giữ đi theo các mục - không giữ trùng, không mất giữ |
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à tồn kho đã giữ |
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 tồn kho đã giữ, 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à tồn kho đã giữ đi theo các mục, để tái định hình không bao giờ làm hỏng một bundle hay tồn kho 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ỷ (FULL_SPLIT); 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 (MERGED_INTO_<target>) | 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ử; allocation usages clone (tách) / move (gộp) / cancel (hoàn tác) đồng bộ và các đơn bị ảnh hưởng được re-price | 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ị row-lock 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ỉ pop 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 tồn kho | Allocation usages đã giữ đi theo các mục - clone khi tách, move khi gộp, cancel khi hoàn tác - nên đặt giữ không bao giờ mất hay nhân đôi |
| Định giá | Mọi đơn có mục thay đổi đều được re-price; tách re-price cả các đơn mới lẫn đơn nguồn tách một phần được giữ lại |
| Tenancy & authz | Mọi thao tác scope theo merchant (x-merchant-id) 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 |
|---|---|
SaleOrder | Đơ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) |
SaleOrderItem | 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 |
TransferHistoryEntry | 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 là soft reference; toàn vẹn được thực thi trong service tách / gộp, không phải bởi ràng buộc database.
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.
- Tồn kho đã giữ (allocation usages) - clone / move / cancel đồng bộ với các mục.
@nx/core- model đơn và mục bán hàng, các helper trạng thái, và hình dạng 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.
- Tồn kho đã giữ cho đơn nguồn tồn tại trước một lần tách hoặc gộp và là cơ sở cho những gì được mang đi.
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 với row-lock - 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ị row-lock suốt thời gian thao tác |
| Tồn kho đã giữ mất hay tính trùng | Allocation usages clone khi tách, move khi gộp, cancel khi hoàn tác - đồng bộ với các mục |
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 tồn kho đã giữ đ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 đặt giữ tồn kho 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.
Tồn kho đã giữ sống sót qua một lần reshape không? Có - allocation usages đã giữ đượ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 đặt giữ không bao giờ mất hay nhân đôi.
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 - tổng quan + truy vết
- Dev: @nx/sale · @nx/core · domain model