PRD: Ngăn xóa nhầm đơn đang chờ của một bàn
| Module | Bán hàng | PRD ID | PRD-SLF-002 |
| Trạng thái | Sẵn sàng dev | FEAT | SLF · BANA-1432 |
| Epic | BANA-1325 | Plane | BANA-1750 |
| Ngày | 2026-07-06 | Phiên bản | v0.1 |
| Gói | @nx/sale · @nx/commerce | URD | SLF |
| Surface | Sale · POS | ||
| Phụ trách | Tinh Nguyễn | ||
TL;DR
Hiện tại, xóa một đơn hàng chỉ kiểm tra đơn đó không ở trạng thái COMPLETED/CANCELLED - hệ thống không kiểm tra đơn đó có đang chiếm một bàn hay không. Chỉ cần bấm nhầm là đơn của bàn đang phục vụ bị xóa mềm ngay lập tức, không cần xác nhận, không có cách khôi phục trên giao diện. PRD này thêm một bước xác nhận biết-về-bàn (table-aware) vào luồng xóa - nêu rõ tên bàn và nội dung đơn trước khi đơn biến mất - và biến bước chặn này thành bắt buộc (không chỉ là cảnh báo) đối với đơn khách tự gọi đang chờ nhân viên xác nhận/từ chối, ngay khi QR self-order ra mắt.
1. Bối cảnh & Vấn đề
Một đơn dine-in không chỉ là một dòng trong giỏ hàng - khi còn mở (DRAFT/PROCESSING/PARTIAL), đơn thường đang giữ một lượt chiếm bàn còn hiệu lực (AllocationUsage, trạng thái reserved/active/success) thông qua Table & floor allocation. API xóa hiện có (DELETE /sale-orders/:id, chạy qua SaleOrderService.archive()) chỉ từ chối các đơn ở trạng thái terminal (COMPLETED/CANCELLED) - còn đơn DRAFT, PROCESSING, hay PARTIAL thì bị xóa mềm ngay, bất kể bàn có đang được sử dụng hay không, và không có bước xác nhận nào ngoài những gì (nếu có) phía client tự vẽ thêm.
Trên thực tế, điều này nghĩa là một nhân viên phục vụ dọn một bàn trống, hoặc bấm nhầm dòng trong danh sách bàn đang đông khách, có thể xóa mất một đơn đang chờ - đơn đang nằm trên một bàn đang sử dụng, chưa checkout hay chưa phục vụ xong - với các món khách vẫn đang chờ, mà không để lại dấu vết nào cho nhân viên ngoài sự kiện socket ORDER_DELETED và một mốc deletedAt trong DB. Không có cách nào khôi phục lại trên sản phẩm.
Rủi ro này sắp trở nên nghiêm trọng hơn: QR self-order (SLF, đang lên kế hoạch) sẽ đưa đơn do khách tự gửi thẳng vào danh sách chờ của bàn, chờ nhân viên xác nhận/từ chối. Một đơn mà nhân viên chưa từng gõ tay lại càng dễ bị "dọn nhầm theo phản xạ" (dọn bàn này) trước khi ai đó kịp đọc - và khác với đơn nhân viên tự nhập, khách hàng hoàn toàn không biết chuyện gì đã xảy ra. PRD này khép lại lỗ hổng ngay từ bây giờ, độc lập và đi trước khi SLF ra mắt, đồng thời định nghĩa luật chặt hơn mà SLF cần có ngay từ ngày đầu: một đơn khách đang chờ có thể bị từ chối (một hành động độc lập, hiển thị cho khách) nhưng không bao giờ được xóa âm thầm.
2. Mục tiêu & Loại trừ
Mục tiêu
- Mọi thao tác xóa một đơn không-terminal đang chiếm một bàn phải qua bước xác nhận rõ ràng, nêu tên bàn và tóm tắt nội dung đơn (số món, thời gian đã mở) trước khi thực hiện.
- Đơn khách tự gọi (SLF) đang chờ xác nhận/từ chối không bao giờ bị xóa trực tiếp - chỉ có thể từ chối, một hành động riêng biệt, có ghi log, và khách nhìn thấy được.
- Mọi lượt xóa đã xác nhận trên đơn đang gắn bàn đều được ghi log kiểm toán (ai, khi nào, bàn nào, snapshot đơn hàng) để có thể điều tra khi cần.
- Bước chặn áp dụng như nhau cho xóa từng đơn lẻ và xóa hàng loạt/đa lựa chọn - không có đường tắt.
Loại trừ
- Khôi phục đơn đã xóa từ giao diện ("undo delete") - dòng dữ liệu đã bị xóa mềm (
deletedAt) sẵn rồi; công cụ khôi phục là một increment tương lai, không nằm trong phạm vi này. - Thay đổi luồng chuyển trạng thái cancel (
PROCESSING/PARTIAL→CANCELLED) - cancel đã tồn tại như một đường đi riêng, không phá hủy dữ liệu, và không nằm trong phạm vi PRD này. - Bản thân luồng xác nhận/từ chối đơn khách, hay phần còn lại của QR self-order (menu, giỏ hàng, chuyển bếp) - thuộc
PRD-SLF-001; PRD này chỉ thêm bước chặn xóa mà các luồng đó phải tuân theo. - Thiết kế lại mô hình chiếm dụng bàn/sơ đồ - dùng nguyên trạng từ Table & floor allocation.
3. Thước đo thành công
| Thước đo | Mục tiêu |
|---|---|
| Số lượt xóa nhầm đơn của bàn | Số ca "mất đơn" do support ghi nhận giảm về 0 sau khi triển khai |
| Độ phủ xác nhận | 100% lượt xóa trên đơn không-terminal đang chiếm bàn hiển thị bước xác nhận biết-về-bàn - không có luồng nào bỏ qua được |
| An toàn đơn khách | 0 đơn khách tự gọi có thể bị xóa; từ chối là đường duy nhất để loại bỏ khi SLF ra mắt |
| Khả năng kiểm toán | 100% lượt xóa đơn-của-bàn đã xác nhận có một bản ghi kiểm toán truy vấn được (ai / khi nào / bàn nào / snapshot) |
4. Persona & Tình huống
| Persona | Mục tiêu trong tính năng này |
|---|---|
| Phục vụ / Thu ngân | Dọn draft cũ của một bàn trống mà không lo xóa nhầm bàn đang đông khách |
| Khách (qua SLF, khi ra mắt) | Tin rằng đơn đã gửi luôn được xác nhận, từ chối kèm lý do, hoặc vẫn đang chờ - không bao giờ biến mất âm thầm |
| Quản lý | Điều tra khiếu nại "đơn của tôi biến mất" bằng log kiểm toán thay vì phỏng đoán |
Tình huống chính: nhân viên bấm xóa đơn của bàn 5 nhưng ý định là xóa bàn 2 (đang trống). Hiện tại thao tác này thành công ngay lập tức. Với PRD này, họ sẽ thấy "Xóa đơn của Bàn 5 - 3 món, đã mở 12 phút? Không thể hoàn tác" và phải xác nhận - bắt lỗi trước khi nó xảy ra.
5. User Story
- Là nhân viên phục vụ, tôi muốn có bước xác nhận nêu rõ tên bàn và nội dung đơn trước khi xóa được thực hiện, để không mất đơn của bàn đang đông khách chỉ vì bấm nhầm.
- Là quản lý, tôi muốn mọi lượt xóa đơn-của-bàn được ghi log ai/khi nào/nội dung gì, để điều tra khiếu nại mất đơn thay vì đoán mò.
- Là khách gọi món qua QR, tôi muốn đơn tôi đã gửi chỉ có thể được xác nhận hoặc từ chối rõ ràng - không bao giờ bị xóa âm thầm - để biết chuyện gì đã xảy ra với đơn của mình.
6. Yêu cầu chức năng
| # | Yêu cầu | Trạng thái | URD ref |
|---|---|---|---|
FR-001 | Xóa một đơn không-terminal (DRAFT/PROCESSING/PARTIAL) đang có AllocationUsage còn hiệu lực (bàn đang sử dụng) yêu cầu một bước xác nhận rõ ràng trước khi xóa được thực hiện | 🚧 | URD-SLF-009 |
FR-002 | Bước xác nhận nêu tên bàn/unit và tóm tắt đơn (số món, thời gian đã mở) để nhân viên nhận ra họ sắp mất gì | 🚧 | URD-SLF-009 |
FR-003 | Đơn khách tự gọi đang chờ xác nhận/từ chối không thể bị xóa qua bất kỳ luồng nào; hành động loại bỏ duy nhất là từ chối, tách biệt với xóa, bắt buộc nhập lý do, và khách nhìn thấy được | 🚧 | URD-SLF-009 · URD-SLF-004 |
FR-004 | Bước chặn áp dụng cho thao tác xóa hàng loạt/đa lựa chọn giống hệt xóa từng đơn - không có hành động nào xóa được đơn-của-bàn mà bỏ qua xác nhận | 🚧 | URD-SLF-009 |
FR-005 | Mọi lượt xóa đã xác nhận trên đơn-của-bàn ghi một bản ghi kiểm toán: người thực hiện, thời gian, bàn/unit, và snapshot đơn (món, trạng thái) tại thời điểm xóa | 🚧 | URD-SLF-009 |
FR-006 | Xóa một đơn không đang chiếm bàn (ví dụ draft khách vãng lai chưa từng gán bàn) không bị ảnh hưởng - chỉ áp dụng kiểm tra trạng thái terminal như hiện tại, không cần xác nhận thêm | ✅ | URD-SLF-009 |
6.1 Tiêu chí nghiệm thu
- Một đơn
DRAFTđang chiếm bàn 5 (AllocationUsagecòn hiệu lực) và nhân viên bấm xóa → hộp thoại xác nhận nêu tên bàn 5 và tóm tắt đơn (số món, thời gian đã mở); chỉ xóa khi xác nhận. - Một đơn khách gửi tại bàn 5 đang chờ xác nhận/từ chối và nhân viên cố loại bỏ → không có hành động xóa - chỉ Xác nhận / Từ chối; từ chối bắt buộc nhập lý do và khách được báo.
- Một đơn không đang chiếm bàn nào (draft khách vãng lai chưa gán bàn) và nhân viên xóa → bị xóa theo kiểm tra trạng thái terminal hiện có, không hiện thêm xác nhận.
- Nhân viên chọn xóa hàng loạt một lô đơn có bao gồm một đơn đang chiếm bàn đông khách → đơn đang chiếm bàn được giữ lại chờ xác nhận riêng, phần còn lại của lô vẫn xử lý bình thường - không thao tác hàng loạt nào bỏ qua được bước chặn.
- Nhân viên xác nhận xóa một đơn-của-bàn → một bản ghi kiểm toán được ghi với người thực hiện, thời gian, bàn/unit, và snapshot đơn, truy vấn lại được sau đó để điều tra.
- Usage của bàn hoàn tất (đơn đã thanh toán) giữa lúc client hiển thị "đang dùng" và lúc request xóa tới nơi → bước chặn kiểm tra lại trạng thái chiếm dụng còn hiệu lực tại thời điểm request, nên lượt xóa trên bàn vừa trống được thực hiện mà không bị chặn nhầm theo dữ liệu cũ.
7. Yêu cầu phi chức năng
| Khu vực | Yêu cầu |
|---|---|
| Toàn vẹn dữ liệu | Bước chặn đọc trạng thái AllocationUsage còn hiệu lực tại thời điểm xóa (không dùng cờ cache cũ), để một bàn vừa trống không chặn nhầm một lượt xóa hợp lệ |
| Tenancy & authz | Bước xác nhận và log kiểm toán giới hạn theo merchant; xóa vẫn qua quyền archive/delete hiện có - đây là lớp chặn thêm phía trước, không phải một tầng quyền mới |
| Khả năng kiểm toán | Bản ghi kiểm toán append-only, truy vấn được theo đơn, theo bàn, và theo người thực hiện để support điều tra |
| Hiệu năng | Việc kiểm tra chiếm dụng chỉ thêm tối đa một lượt tra cứu có index vào luồng xóa hiện có - không gây độ trễ đáng kể trên POS |
| Đa ngôn ngữ | Nội dung xác nhận và lời nhắc lý do từ chối song ngữ (EN + VI) |
8. UX & Luồng
Xóa bị chặn vì bàn đang sử dụng
Đơn khách tự gọi (khi SLF ra mắt) - chỉ từ chối, không xóa
Bề mặt chính: danh sách đơn theo bàn và màn hình chi tiết đơn đều thêm hộp thoại xác nhận ở mọi nơi hiện có hành động xóa; thẻ đơn khách đang chờ chỉ hiển thị Xác nhận/Từ chối - không có icon xóa.
9. Dữ liệu & Miền nghiệp vụ
| Thực thể | Vai trò |
|---|---|
SaleOrder | Đơn hàng bị xóa; mang status và deletedAt (xóa mềm) |
AllocationUsage | Bản ghi chiếm dụng đa hình gắn một AllocationUnit (bàn) với đơn này; reserved/active/success = đang dùng, completed/cancelled = đã trống - đọc tại thời điểm xóa để quyết định có kích hoạt bước chặn hay không |
| Bản ghi kiểm toán xóa (mới) | Bản ghi append-only: người thực hiện, thời gian, bàn/unit, snapshot đơn - ghi mỗi khi có lượt xóa đơn-của-bàn đã xác nhận |
Chỉ mang tính khái niệm - schema đầy đủ nằm ở domain model của sale và Allocation Usage.
10. Phụ thuộc & Giả định
Phụ thuộc vào
- Vòng đời đơn hàng (ORD) - luồng
archive()/xóa hiện có mà PRD này thêm bước chặn phía trước. - Table & floor allocation (commerce/FLR) -
AllocationUsagelà nguồn sự thật cho việc "bàn này có đang được dùng không." - QR self-order (SLF, đang lên kế hoạch) - định nghĩa luồng xác nhận/từ chối mà quy tắc "chỉ từ chối, không xóa" của PRD này phải khớp vào khi được xây dựng.
Giả định
- Bước chặn xóa có thể ra mắt trước SLF - nó bảo vệ ngay đơn của bàn do nhân viên tự nhập hôm nay; quy tắc chỉ-từ-chối cho đơn khách kích hoạt ngay khi luồng xác nhận/từ chối của SLF tồn tại.
- Tại một thời điểm, tối đa một
AllocationUsagecòn hiệu lực chiếm một bàn (theo mô hình FLR), nên việc kiểm tra "bàn này có đang bị chiếm bởi đơn này không" là rõ ràng, không mơ hồ.
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ỏi tay xác nhận - nhân viên bấm qua mọi hộp thoại mà không đọc | Nội dung xác nhận mở đầu bằng tên bàn và số món, không phải "Bạn có chắc không?" chung chung; xem lại nội dung/độ ma sát sau khi có phản hồi pilot |
| Bàn hết usage (đơn đã thanh toán) giữa lúc client hiển thị "đang dùng" và lúc request xóa tới nơi | Bước chặn kiểm tra lại trạng thái AllocationUsage còn hiệu lực tại thời điểm request, không dùng cờ cache phía client (NFR) |
| Nhân viên cần loại bỏ một đơn khách trùng/lỗi mà chắc chắn sẽ không bao giờ được xác nhận | Đã có FR-003 - từ chối (kèm lý do) là đường loại bỏ hợp lệ, không phải xóa |
| Có nên cho quản lý bỏ qua bước xác nhận khi dọn hàng loạt bàn (ví dụ cuối ca)? | Còn mở - mặc định v1 không cho bỏ qua; xem lại nếu vận hành có nhu cầu thực tế |
12. Kế hoạch phát hành & Tiêu chí
| Khía cạnh | Kế hoạch |
|---|---|
| Giai đoạn | P2 - ra mắt như một lớp chặn trên luồng xóa ORD hiện có; quy tắc chỉ-từ-chối cho khách kích hoạt cùng SLF |
| Rollout | Toàn bộ merchant; không feature flag - đây là bản vá an toàn, không phải hành vi tùy chọn |
| Migration | Không cần - đọc trạng thái AllocationUsage sẵn có; bản ghi kiểm toán là dữ liệu bổ sung |
| Tiêu chí ra mắt | Xóa một đơn không-terminal đang chiếm bàn luôn hiển thị xác nhận biết-về-bàn và bị chặn nếu không xác nhận; đơn khách đang chờ không có hành động xóa, chỉ có xác nhận/từ chối; mọi lượt xóa đơn-của-bàn đã xác nhận có bản ghi kiểm toán truy vấn được |
| Giám sát | Tỷ lệ hiển-thị-xác-nhận so với xóa-đã-xác-nhận (bắt được các suýt-nhầm), khối lượng bản ghi kiểm toán theo bàn/người thực hiện, bất kỳ request xóa nào bỏ qua được bước chặn (phải luôn bằng 0) |
13. FAQ
PRD này có thay đổi cách hủy (cancel) đơn không? Không - cancel (PROCESSING/PARTIAL → CANCELLED) là một chuyển trạng thái riêng, đã tồn tại từ trước và không bị đụng tới. PRD này chỉ chặn hành động xóa/archive mang tính phá hủy.
Nhân viên có còn xóa nhanh được draft lạc của một bàn trống không? Có - đơn không đang chiếm bàn nào thì không bị bước chặn này áp dụng (FR-006); chỉ đơn đang chiếm bàn mới cần xác nhận.
Nếu nhân viên không phản hồi đơn của khách thì sao? Ngoài phạm vi PRD này - thuộc quy tắc thời gian xác nhận/từ chối của QR self-order. PRD này chỉ đảm bảo dù chuyện gì xảy ra, đó phải là xác nhận hoặc từ chối, không bao giờ là xóa âm thầm.
Đơn bị xóa nhầm có khôi phục lại được không? Chưa qua giao diện ở v1 - dòng dữ liệu đã bị xóa mềm (deletedAt) nên dữ liệu vẫn còn, nhưng công cụ khôi phục là một increment tương lai (xem Loại trừ).
Tham chiếu
- URD: Sale - SLF
- PRD liên quan: QR self-order · Table & floor allocation
- Module: Sale - URD
- Developer: @nx/sale · domain model · Allocation Usage