# ADR-003 — Mô hình dữ liệu Order (cha) / OrderSeller (con) để tách đơn theo seller khi checkout *Tên file: `adr/ADR-003_mo-hinh-order-orderseller-tach-don-theo-seller.md`* | | | |---|---| | **Status** | `Proposed` | | **Date** | 2026-09-15 | | **Người quyết** | SA + Tech Lead (cấu trúc dữ liệu — `decision-radar.md §5`) | | **Người đề xuất** | SA (qua skill `sa-2-architecture`, hoạt động `adr`) | | **Điểm radar** | **~6/10** | | **Supersedes** | — | | **Superseded by** | — | | **Liên quan** | `ASR-003` · `BR-CART-01` (BA) · `DRV-01` · `DRV-02` · `DRV-06` · `OQ-011` (BA, chưa đóng) · `CMP-04` | > ⚠️ **ADR bất biến sau khi `Accepted`.** Muốn đổi quyết định thì viết ADR mới có > `Supersedes: ADR-003` và đổi trạng thái bản cũ thành `Superseded by`. --- ## 1. Bối cảnh Giỏ hàng có thể chứa sản phẩm của nhiều seller. `DRV-01` (blocker MVP) và `DRV-06` (minh bạch dòng tiền 3 bên khách–sàn–seller) buộc phải có một cách nhất quán để tách trách nhiệm tài chính và vận chuyển giữa các seller trong cùng một lần đặt hàng. `BR-CART-01` (BA) đã phát biểu quy tắc nghiệp vụ: nhóm `CartItem` theo `seller_id`, tạo `OrderSeller` riêng cho mỗi nhóm, `Order` là tập hợp các `OrderSeller`. **Cơ chế tách chi tiết** ("luôn đúng N đơn cho N seller, không gộp") vẫn phụ thuộc `OQ-011` (BA, `ASM-03` chưa xác minh) — ADR này ghi nhận mô hình cấu trúc, không tự đóng `OQ-011`. **Ràng buộc đang chi phối:** | Nguồn | Nội dung | |---|---| | `BR-CART-01` (BA) | Tách `CartItem` theo `seller_id`, tạo `OrderSeller` riêng cho mỗi nhóm | | `ASR-003` | Ép mô hình dữ liệu 1 `Order` — N `OrderSeller` — N `OrderItem`; mỗi `OrderSeller` có vòng đời trạng thái độc lập | | `DRV-06` | Dòng tiền minh bạch 3 bên, hoa hồng/payout theo từng seller | | `OQ-011` (BA) | Giỏ N seller luôn tách đúng N đơn, không gộp — **chưa xác nhận bởi PO** | **Cái đã biết chắc / cái còn là giả định:** | Điều | 🟢 Đã kiểm chứng / 🔴 Giả định | Bằng chứng | |---|---|---| | Mô hình kinh doanh là marketplace đa seller, cần tách trách nhiệm tài chính/vận chuyển theo seller | 🟢 Đã kiểm chứng | `00-project-brief.md` vòng 1, `BR-CART-01` | | Không có ngoại lệ gộp nhiều seller vào 1 `OrderSeller` (VD cùng kho vận trung tâm) | 🔴 Giả định — PO chưa xác nhận | `OQ-011` (BA), `ASM-03` | ## 2. Phương án đã cân nhắc ### PA-1 — Một `Order` phẳng, không có cấu trúc con theo seller (dùng trường `seller_id` lặp ở từng `OrderItem`) | | | |---|---| | **Mô tả** | Chỉ có bảng `Order`/`OrderItem`, mỗi `OrderItem` gắn `seller_id` riêng; không có tầng `OrderSeller` trung gian — trạng thái/hoa hồng tính bằng cách nhóm động theo `seller_id` mỗi lần truy vấn | | **Ưu** | Đơn giản hơn về số bảng, ít JOIN hơn cho các truy vấn không cần phân seller | | **Nhược** | Không có đơn vị hạch toán/vận chuyển/đối soát độc lập theo seller — mỗi lần cần trạng thái giao hàng hoặc hoa hồng theo seller phải nhóm động, dễ sai lệch khi có seller thay đổi trạng thái không đồng thời; vi phạm `ASR-003` ("mỗi `OrderSeller` có vòng đời trạng thái độc lập") | | **Chi phí đảo ngược** | Cao — thêm tầng `OrderSeller` sau khi đã có dữ liệu lịch sử cần migrate toàn bộ đơn hàng cũ | ### PA-2 — `Order` (cha) — N `OrderSeller` (con) — N `OrderItem` *(chọn)* | | | |---|---| | **Mô tả** | `Order` là tập hợp logic của các `OrderSeller`; mỗi `OrderSeller` có `status` riêng, thuộc về đúng 1 `Order` cha và đúng 1 `seller_id` | | **Ưu** | Mỗi `OrderSeller` là đơn vị hạch toán/vận chuyển/đối soát độc lập — khớp trực tiếp `BR-CART-01`, `DRV-06`; các module tiêu thụ (Commission & Payout, Shipping & Fulfillment) chỉ cần đọc `OrderSeller`, không phải tự nhóm | | **Nhược** | Thêm một tầng bảng, thêm JOIN khi cần tổng `Order` (đã có công thức `BR-CART-09` bù); ranh giới sở hữu dữ liệu giữa Cart & Order (tạo) và Commission/Shipping (tiêu thụ) cần rõ ràng | | **Chi phí đảo ngược** | Trung bình-cao — đổi mô hình sau go-live cần migrate toàn bộ đơn hàng lịch sử + sửa mọi module đọc `Order` | ### PA-3 — Mỗi seller là một `Order` độc lập, không có `Order` cha chung | | | |---|---| | **Mô tả** | Bỏ hẳn khái niệm "đơn cha" — khách checkout giỏ 3 seller thì tạo 3 `Order` độc lập hoàn toàn, không có liên kết logic nào | | **Ưu** | Đơn giản nhất về mặt mô hình — mỗi `Order` tự chứa đủ thông tin | | **Nhược** | Khách hàng mất khả năng xem "một lần đặt hàng" gồm nhiều seller như một đơn vị trải nghiệm (UX); mất điểm neo cho thanh toán chung 1 lần cho nhiều seller (khởi tạo Payment 1 lần cho N `OrderSeller`) — vi phạm luồng B5-B8 đã có ở `PROCESS_CartCheckout` | | **Chi phí đảo ngược** | Cao — dựng lại khái niệm "đơn cha" sau này cần thêm bảng liên kết + backfill dữ liệu | ### Bảng so sánh | Tiêu chí | PA-1 | PA-2 | PA-3 | |---|---|---|---| | Đơn vị hạch toán/vận chuyển độc lập theo seller (`ASR-003`) | ❌ | ✅ | ✅ | | Trải nghiệm "một lần đặt hàng" cho nhiều seller (`PROCESS` B5-B8) | 🔶 Nhóm động | ✅ | ❌ | | Số bảng/độ phức tạp | Thấp | Trung bình | Thấp | | Khớp `BR-CART-01` (BA) | ❌ | ✅ | 🔶 Một phần | ## 3. Quyết định > **Chọn PA-2 — `Order` (cha) — N `OrderSeller` (con) — N `OrderItem`.** **Vì sao:** Đây là mô hình duy nhất khớp trực tiếp `BR-CART-01` (nguồn nghiệp vụ đã chốt), đáp ứng `ASR-003` (mỗi `OrderSeller` có vòng đời độc lập) và giữ được trải nghiệm "một lần đặt hàng" theo luồng đã thiết kế ở `PROCESS_CartCheckout` B5-B8. **Phạm vi áp dụng:** Toàn bộ luồng checkout và mọi module tiêu thụ `Order`/`OrderSeller` (Commission & Payout, Shipping & Fulfillment, Review). ### Điều kiện chuyển `Proposed → Accepted` | Điều kiện | Ai xác nhận | Trạng thái hiện tại | |---|---|---| | `OQ-011` (BA) — PO xác nhận không có ngoại lệ gộp seller (VD cùng kho vận trung tâm) | PO | Mở | | Không bắt buộc POC (đây là quyết định mô hình dữ liệu, không phải con số hiệu năng) | — | N/A | ## 4. Phương án bị loại và lý do | Phương án | Loại vì | Gắn với | Điều kiện nào thì xét lại | |---|---|---|---| | PA-1 — `Order` phẳng, không tầng `OrderSeller` | Vi phạm `ASR-003` (không có vòng đời độc lập theo seller); các module tiêu thụ phải tự nhóm động, dễ sai lệch | `ASR-003`, `BR-CART-01` | Nếu về sau xác nhận không cần vòng đời độc lập theo seller (VD gộp hết trách nhiệm về sàn) — chưa có dấu hiệu | | PA-3 — Mỗi seller một `Order` độc lập, không cha chung | Phá vỡ trải nghiệm "một lần đặt hàng" đã thiết kế ở `PROCESS` B5-B8; mất điểm neo thanh toán chung | `PROCESS_CartCheckout` B5-B8 | Nếu PO đổi mô hình sản phẩm sang "mỗi seller là một cửa hàng độc lập hoàn toàn, khách checkout riêng từng seller" — thay đổi nghiệp vụ lớn, chưa có dấu hiệu | ## 5. Hệ quả **Hệ quả tích cực** - Commission & Payout, Shipping & Fulfillment chỉ cần đọc `OrderSeller`, không tự nhóm động — giảm rủi ro sai lệch đối soát - Khách hàng có trải nghiệm nhất quán "một lần đặt hàng" dù giỏ có nhiều seller **Hệ quả tiêu cực phải sống chung** - Thêm JOIN khi cần tổng `Order.total_amount` (đã có công thức bù `BR-CART-09`) - Nếu `OQ-011` trả lời "có ngoại lệ gộp seller" (VD cùng kho vận), mô hình này phải viết lại một phần (không phải toàn bộ — vẫn giữ cấu trúc cha-con, chỉ đổi quy tắc nhóm) **Cái quyết định này khoá lại** | Muốn đổi về sau thì | Tốn | |---|---| | Đổi mô hình tách đơn (VD gộp nhiều seller vào 1 đơn) | Migrate toàn bộ đơn hàng lịch sử + sửa mọi module đọc `Order` — đây là core domain model, `SAD §4.2` đã cảnh báo đây là loại thay đổi "không nên xảy ra thường xuyên" | **Việc phát sinh** | Việc | Chủ | Hạn | Ghi ở đâu | |---|---|---|---| | BA xác nhận `OQ-011` với PO | BA + PO | Trước khi `Accepted` | `ba-output/.../00-index/OQ_e-commerce.md` | | Thiết kế schema chi tiết `Order`/`OrderSeller`/`OrderItem` | SA (hoạt động `dat`) | GĐ2 tiếp theo | `DAT_e-commerce` | ## 6. Cách kiểm chứng quyết định này được tuân thủ | Cách kiểm | Công cụ | Chạy ở đâu | `FIT` | |---|---|---|---| | Mọi `OrderSeller` luôn có đúng 1 `seller_id` và thuộc đúng 1 `Order` cha (ràng buộc FK + constraint) | DB schema constraint + migration test | CI | `FIT-05` (ứng viên) | | Tổng `Order.total_amount` luôn bằng Σ `OrderSeller.subtotal_amount` (`BR-CART-09`) | Unit test / DB trigger kiểm tra | CI | `FIT-06` (ứng viên) | ## 7. Điều kiện xét lại | Dấu hiệu | Ngưỡng | Ai theo dõi | |---|---|---| | PO xác nhận cần ngoại lệ gộp seller (VD cùng kho vận trung tâm) | Khi `OQ-011` trả lời khác giả định hiện tại | PO + BA | | Phát sinh nhu cầu đổi mô hình tách đơn thường xuyên | ≥ 2 lần yêu cầu thay đổi trong 6 tháng | Tech Lead | ## 8. Tham chiếu - POC: Không áp dụng - Bài đo: `FIT-05`, `FIT-06` (ứng viên GĐ3) - Tài liệu ngoài: `BR_CartCheckout_v1.0.md §1 BR-CART-01, §4 ERD` (BA) - Thảo luận: `ASR_e-commerce_v1.0.md §B2 ASR-003` ## 9. Review log (không đổi Status) | Ngày | Người review | Vai trò | Quyết định | Lý do chưa chuyển `Accepted` | |---|---|---|---|---| | 2026-09-15 | Điều phối dự án (thay mặt Tech Lead, chế độ chạy thử) | Tech Lead (ký thay, ngoại lệ `DEC-01`) | `Reviewed (Proposed giữ nguyên)` — nội dung đủ làm cơ sở thiết kế tiếp (`icd`/`dat`/`sec`/`inf`/`fail`) | Chờ `OQ-011` — BA + PO xác nhận mô hình `Order`/`OrderSeller` | > Đây là ghi nhận **review nội dung**, không phải "sign" theo nghĩa gate: `Status` giữ nguyên > `Proposed`. Xem `00-index/ADL_e-commerce.md` (Change Log — Review 2026-09-15) và `ADL §8` > "Việc phải làm" cho điều kiện chuyển `Accepted`. Confidence tổng thể của lượt review: 🔴 — > `AG2` chưa ký.