improve BA skill
This commit is contained in:
215
ba-output/e-commerce/03-specification/AC_US-002_v1.0.md
Normal file
215
ba-output/e-commerce/03-specification/AC_US-002_v1.0.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# AC — Acceptance Criteria — US-002 Xem giỏ hàng nhóm theo seller
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Version** | 1.0 |
|
||||
| **Date** | 2026-09-08 |
|
||||
| **Author** | BA (qua skill ba-3-specification) |
|
||||
| **Status** | 🟡 Draft |
|
||||
| **Approved by** | QA: — *(chưa có QA thật tham gia lần chạy này)* |
|
||||
| **Source** | `SRS_US002-003_v1.0.md` · `RBAC_CartCheckout_v1.0.md` §2, §6 |
|
||||
| **Scope** | US-002 |
|
||||
|
||||
## Change Log
|
||||
|
||||
| Version | Date | Người sửa | Thay đổi | CR |
|
||||
|---|---|---|---|---|
|
||||
| 1.0 | 2026-09-08 | BA (qua skill ba-3-specification) | Bản đầu — 6 AC, 4 nhóm | — |
|
||||
|
||||
---
|
||||
|
||||
## 1. Bốn nhóm bắt buộc
|
||||
|
||||
| Nhóm | Nội dung | Tối thiểu | Số AC đã viết |
|
||||
|---|---|---|---|
|
||||
| 1. Luồng thành công | Xem giỏ hàng nhóm theo seller | 1/hành động | 2 |
|
||||
| 2. Validation | Không áp dụng — US-002 không có input do người dùng nhập, chỉ hiển thị (xem lý do dưới) | — | 0 (N/A có lý do) |
|
||||
| 3. Lỗi hệ thống | Timeout, 5xx, phân biệt "rỗng thật" vs "không tải được" | ≥1 | 2 |
|
||||
| 4. Phân quyền | Không đủ quyền, hết phiên | ≥1/vai trò bị chặn | 2 |
|
||||
|
||||
**Vì sao Nhóm 2 (Validation) ghi N/A:** US-002 là hành động **xem** thuần tuý (`SCR-04` khi mở màn hình) — không có trường nào do người dùng nhập trong phạm vi US-002 (nhập số lượng thuộc US-003, xem `AC_US-003_v1.0.md`). Đây không phải "chỉ có nhóm 1" (vi phạm nguyên tắc), vì Nhóm 3 và Nhóm 4 đã viết đủ.
|
||||
|
||||
---
|
||||
|
||||
## 2. Danh sách AC
|
||||
|
||||
### Nhóm 1 — Luồng thành công
|
||||
|
||||
#### AC-US002-01 — Xem giỏ hàng có nhiều seller
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Liên quan** | SCR-04 · C01, C02, C03–C06, C08, C09, C11 |
|
||||
| **Vai trò** | ROLE-02 Customer (hành vi tương tự với ROLE-01 Guest, khác ở định danh `X-Guest-Session-Id` thay JWT) |
|
||||
|
||||
```
|
||||
Given tôi đăng nhập với ROLE-02 (Customer) và giỏ hàng của tôi (Cart.status=active) có 5 CartItem:
|
||||
3 CartItem thuộc seller "Shop A", 2 CartItem thuộc seller "Shop B"
|
||||
When tôi mở SCR-04 (Giỏ hàng)
|
||||
Then hệ thống hiển thị đúng 2 nhóm theo seller ("Shop A", "Shop B"), mỗi nhóm hiển thị đúng
|
||||
số CartItem thuộc nhóm đó
|
||||
And tạm tính mỗi nhóm (C08) bằng Σ (quantity × đơn giá hiển thị) của các CartItem trong
|
||||
nhóm đó
|
||||
And tổng cộng giỏ hàng (C09) bằng Σ tất cả C08
|
||||
And nút "Tiến hành Checkout" (C11) hiển thị và không bị disable
|
||||
```
|
||||
|
||||
**Dữ liệu mẫu để test:**
|
||||
|
||||
| Field | Giá trị |
|
||||
|---|---|
|
||||
| Shop A — CartItem 1 | quantity=2, đơn giá=100.000 ₫ |
|
||||
| Shop A — CartItem 2 | quantity=1, đơn giá=50.000 ₫ |
|
||||
| Shop A — CartItem 3 | quantity=1, đơn giá=75.000 ₫ |
|
||||
| Shop A — tạm tính (C08) kỳ vọng | 2×100.000 + 1×50.000 + 1×75.000 = 325.000 ₫ |
|
||||
| Shop B — CartItem 1 | quantity=1, đơn giá=200.000 ₫ |
|
||||
| Shop B — CartItem 2 | quantity=2, đơn giá=30.000 ₫ |
|
||||
| Shop B — tạm tính (C08) kỳ vọng | 1×200.000 + 2×30.000 = 260.000 ₫ |
|
||||
| Tổng cộng (C09) kỳ vọng | 325.000 + 260.000 = 585.000 ₫ |
|
||||
|
||||
#### AC-US002-02 — Xem giỏ hàng rỗng
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Liên quan** | SCR-04 · C10, C11 |
|
||||
| **Vai trò** | ROLE-01, ROLE-02 |
|
||||
|
||||
```
|
||||
Given tôi đăng nhập với ROLE-02 và giỏ hàng của tôi không có CartItem nào (Cart.status=active,
|
||||
0 dòng)
|
||||
When tôi mở SCR-04
|
||||
Then hệ thống hiển thị trạng thái "Giỏ hàng trống" (khoá cart.empty.title) và nút
|
||||
"Tiếp tục mua sắm" (C10)
|
||||
And nút "Tiến hành Checkout" (C11) không hiển thị/bị disable — theo §2.A.3.6 của SRS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nhóm 2 — Validation
|
||||
|
||||
*N/A — xem lý do ở mục 1.*
|
||||
|
||||
---
|
||||
|
||||
### Nhóm 3 — Lỗi hệ thống
|
||||
|
||||
#### AC-US002-03 — Lỗi hệ thống khi tải giỏ hàng
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Liên quan** | SCR-04 · C12, C13 |
|
||||
| **Vai trò** | ROLE-01, ROLE-02 |
|
||||
|
||||
```
|
||||
Given tôi đăng nhập với ROLE-02
|
||||
When tôi mở SCR-04 và GET /v1/cart trả về 500/503 hoặc timeout (ngưỡng — xem NFR §1, OQ-033)
|
||||
Then hệ thống hiển thị banner lỗi "Không tải được dữ liệu, thử lại." (E-CART-0004) tại vùng
|
||||
danh sách
|
||||
And nút "Thử lại" (C13) hiển thị; bấm vào gọi lại GET /v1/cart
|
||||
```
|
||||
|
||||
Ba tình huống tối thiểu của nhóm này:
|
||||
|
||||
| # | Tình huống | AC |
|
||||
|---|---|---|
|
||||
| 1 | Server trả 5xx | AC-US002-03 |
|
||||
| 2 | Timeout / mất mạng | AC-US002-03 |
|
||||
| 3 | Dữ liệu bị người khác sửa/xoá trong lúc mình đang mở | Không áp dụng ở US-002 (chỉ xem, không sửa) — xem `AC_US-003_v1.0.md` §3 Nhóm 3 (`AC-US003-11`) cho trường hợp này khi có thao tác sửa/xoá |
|
||||
|
||||
#### AC-US002-04 — Phân biệt "giỏ hàng chưa từng có dữ liệu" và "không tải được"
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Liên quan** | SCR-04 · C10, C12 |
|
||||
| **Vai trò** | ROLE-02 |
|
||||
|
||||
```
|
||||
Given tôi đăng nhập với ROLE-02 lần đầu, chưa từng thêm sản phẩm nào vào giỏ (Cart mới tạo,
|
||||
0 CartItem); GET /v1/cart trả 200 với danh sách rỗng
|
||||
When tôi mở SCR-04
|
||||
Then hệ thống hiển thị trạng thái "Giỏ hàng trống" (không phải banner lỗi C12)
|
||||
And hai trạng thái "giỏ hàng trống thật" (AC-US002-02/04) và "không tải được" (AC-US002-03)
|
||||
dùng hai khối hiển thị khác nhau (data-state khác nhau trong WF), không dùng chung một
|
||||
thông điệp — kiểm chứng bằng cách tắt mạng khi mở màn hình lần đầu (kỳ vọng thấy banner
|
||||
lỗi, KHÔNG phải "Giỏ hàng trống")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nhóm 4 — Phân quyền
|
||||
|
||||
#### AC-US002-05 — Guest chỉ thấy giỏ hàng của session mình
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Liên quan** | SCR-04 |
|
||||
| **Vai trò** | ROLE-01 |
|
||||
|
||||
```
|
||||
Given tôi là Guest với X-Guest-Session-Id = S1; một Guest khác có giỏ hàng thuộc session S2
|
||||
(không phải của tôi)
|
||||
When tôi gọi GET /v1/cart với header X-Guest-Session-Id: S1
|
||||
Then hệ thống chỉ trả về giỏ hàng gắn với S1 (dữ liệu của chính tôi), không có cách nào lộ
|
||||
dữ liệu của S2 qua endpoint này (endpoint không nhận cartId của người khác qua tham số)
|
||||
```
|
||||
|
||||
#### AC-US002-06 — Customer hết phiên đăng nhập cố xem giỏ hàng
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Liên quan** | SCR-04, SCR-08 (đăng nhập) |
|
||||
| **Vai trò** | ROLE-02 |
|
||||
|
||||
```
|
||||
Given tôi trước đó đã đăng nhập và có giỏ hàng theo customer_id; nay access token đã hết hạn
|
||||
(401 ERR_AUTH_INVALID_TOKEN)
|
||||
When tôi mở SCR-04
|
||||
Then hệ thống chuyển hướng về màn đăng nhập (SCR-08)
|
||||
And giữ đường dẫn quay lại SCR-04 sau khi đăng nhập thành công (theo UICONV §2)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Bảng dữ liệu biên
|
||||
|
||||
*N/A — US-002 không có field nhập liệu (chỉ xem). Xem `AC_US-003_v1.0.md` §4 cho F01 (số lượng).*
|
||||
|
||||
---
|
||||
|
||||
## 4. Kịch bản kết hợp
|
||||
|
||||
| # | Kịch bản | Các bước | AC liên quan |
|
||||
|---|---|---|---|
|
||||
| S1 | Mở giỏ hàng nhiều seller rồi tiến hành checkout | SCR-04 (mở, kiểm tra tổng đúng) → bấm "Tiến hành Checkout" (C11) → điều hướng SCR-05 (chỉ kiểm tra điều hướng, ngoài phạm vi hành vi SCR-05) | AC-US002-01 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Truy vết
|
||||
|
||||
| AC | US | BR | Field/Thành phần | Mã lỗi | Test case (QA điền) | Kết quả (QA điền) |
|
||||
|---|---|---|---|---|---|---|
|
||||
| AC-US002-01 | US-002 | — | C01, C02, C03–C06, C08, C09, C11 | — | | |
|
||||
| AC-US002-02 | US-002 | BR §3 CART | C10, C11 | — | | |
|
||||
| AC-US002-03 | US-002 | — | C12, C13 | E-CART-0004 | | |
|
||||
| AC-US002-04 | US-002 | — | C10, C12 | E-CART-0004 | | |
|
||||
| AC-US002-05 | US-002 | RBAC §2 | — | — | | |
|
||||
| AC-US002-06 | US-002 | — | — | — | | |
|
||||
|
||||
---
|
||||
|
||||
## 6. Xác nhận của QA
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Người xác nhận** | *(chưa có — chưa có QA thật tham gia lần chạy này)* |
|
||||
| **Ngày** | — |
|
||||
| ☐ Mọi AC đều test được (không có AC mô tả cảm tính) | |
|
||||
| ☐ Đủ 4 nhóm (Nhóm 2 N/A có lý do) | |
|
||||
| ☐ Bảng dữ liệu biên đủ dùng để viết test case (N/A — không có field ở US-002) | |
|
||||
| ☐ Không có AC nào mâu thuẫn với AC khác | |
|
||||
|
||||
**AC bị QA từ chối:**
|
||||
|
||||
| AC | Lý do từ chối | BA sửa thế nào |
|
||||
|---|---|---|
|
||||
| | | |
|
||||
267
ba-output/e-commerce/03-specification/AC_US-003_v1.0.md
Normal file
267
ba-output/e-commerce/03-specification/AC_US-003_v1.0.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# AC — Acceptance Criteria — US-003 Sửa số lượng / xoá sản phẩm trong giỏ hàng
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Version** | 1.0 |
|
||||
| **Date** | 2026-09-08 |
|
||||
| **Author** | BA (qua skill ba-3-specification) |
|
||||
| **Status** | 🟡 Draft |
|
||||
| **Approved by** | QA: — *(chưa có QA thật tham gia lần chạy này)* |
|
||||
| **Source** | `SRS_US002-003_v1.0.md` · `RBAC_CartCheckout_v1.0.md` §6 · `BR_CartCheckout_v1.0.md` §3 |
|
||||
| **Scope** | US-003 |
|
||||
|
||||
## Change Log
|
||||
|
||||
| Version | Date | Người sửa | Thay đổi | CR |
|
||||
|---|---|---|---|---|
|
||||
| 1.0 | 2026-09-08 | BA (qua skill ba-3-specification) | Bản đầu — 13 AC, 4 nhóm (2 AC còn mở chờ `OQ-028`/`OQ-032`, ghi rõ thay vì bịa) | — |
|
||||
|
||||
---
|
||||
|
||||
## 1. Bốn nhóm bắt buộc
|
||||
|
||||
| Nhóm | Nội dung | Tối thiểu | Số AC đã viết |
|
||||
|---|---|---|---|
|
||||
| 1. Luồng thành công | Tăng/giảm số lượng, xoá dòng, xoá nhóm, xoá giỏ, huỷ modal | 1/hành động | 6 |
|
||||
| 2. Validation | Số lượng không hợp lệ; số lượng về 0 | 1/rule | 2 (1 còn mở — `OQ-028`) |
|
||||
| 3. Lỗi hệ thống | 5xx cập nhật, timeout xoá, item bị xoá đồng thời | ≥1 | 3 |
|
||||
| 4. Phân quyền | Sửa/xoá CartItem không thuộc quyền sở hữu (Customer, Guest) | ≥1/vai trò | 2 (1 còn mở — `OQ-032`) |
|
||||
|
||||
---
|
||||
|
||||
## 2. Danh sách AC
|
||||
|
||||
### Nhóm 1 — Luồng thành công
|
||||
|
||||
#### AC-US003-01 — Tăng số lượng CartItem thành công
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Liên quan** | SCR-04 · F01, C08, C09 |
|
||||
| **Vai trò** | ROLE-02 (tương tự ROLE-01) |
|
||||
|
||||
```
|
||||
Given CartItem X có quantity=2, Cart.status=active
|
||||
When tôi bấm nút "+" (F01) để tăng số lượng lên 3
|
||||
Then hệ thống gọi PATCH /v1/cart/items/{X} với quantity=3, nhận 200 OK
|
||||
And F01 hiển thị 3; tạm tính nhóm (C08) và tổng cộng (C09) cập nhật lại ngay theo giá trị mới
|
||||
```
|
||||
|
||||
#### AC-US003-02 — Giảm số lượng CartItem thành công (còn ≥1)
|
||||
|
||||
```
|
||||
Given CartItem X có quantity=3
|
||||
When tôi bấm nút "-" (F01) để giảm còn 2
|
||||
Then PATCH thành công, F01=2, C08/C09 cập nhật
|
||||
```
|
||||
|
||||
#### AC-US003-03 — Xoá CartItem thành công (đã xác nhận modal)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Liên quan** | SCR-04 · C07, C08, C09 |
|
||||
| **Vai trò** | ROLE-02 |
|
||||
|
||||
```
|
||||
Given CartItem X tồn tại trong nhóm seller "Shop A" (nhóm đang có 2 CartItem)
|
||||
When tôi bấm "Xoá" (C07) trên CartItem X, modal xác nhận hiện ra (theo UICONV §5), tôi bấm
|
||||
nút xác nhận trong modal
|
||||
Then hệ thống gọi DELETE /v1/cart/items/{X}, nhận 200/204
|
||||
And dòng CartItem X biến mất khỏi danh sách; tạm tính nhóm "Shop A" (C08) cập nhật (còn
|
||||
1 CartItem); tổng cộng (C09) cập nhật
|
||||
```
|
||||
|
||||
#### AC-US003-04 — Xoá CartItem cuối cùng của một nhóm seller
|
||||
|
||||
```
|
||||
Given nhóm seller "Shop B" chỉ có 1 CartItem
|
||||
When tôi xoá CartItem đó (đã xác nhận modal)
|
||||
Then toàn bộ khối nhóm "Shop B" (C02 + dòng CartItem) biến mất khỏi màn hình — không hiển
|
||||
thị một nhóm rỗng không có dòng nào
|
||||
```
|
||||
|
||||
#### AC-US003-05 — Xoá CartItem cuối cùng của toàn giỏ hàng
|
||||
|
||||
```
|
||||
Given giỏ hàng chỉ còn duy nhất 1 CartItem (1 nhóm, 1 dòng)
|
||||
When tôi xoá CartItem đó (đã xác nhận modal)
|
||||
Then màn hình chuyển sang trạng thái "Giỏ hàng trống" (theo SRS §2.A.3.6); nút
|
||||
"Tiến hành Checkout" (C11) ẩn/disable
|
||||
And Cart.status vẫn là active (KHÔNG tự chuyển abandoned — theo BR §3, chỉ hết hạn phiên
|
||||
mới chuyển abandoned)
|
||||
```
|
||||
|
||||
#### AC-US003-06 — Huỷ modal xác nhận xoá
|
||||
|
||||
```
|
||||
Given modal xác nhận xoá CartItem X đang mở
|
||||
When tôi bấm nút huỷ hoặc đóng modal (không xác nhận)
|
||||
Then CartItem X vẫn còn nguyên trong danh sách; không có request DELETE nào được gửi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Nhóm 2 — Validation
|
||||
|
||||
#### AC-US003-07 — Nhập số lượng không phải số nguyên dương hợp lệ
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Liên quan** | SCR-04 · F01 |
|
||||
| **Vai trò** | ROLE-02 |
|
||||
|
||||
```
|
||||
Given CartItem X có quantity=2
|
||||
When tôi nhập trực tiếp vào F01 giá trị không hợp lệ (VD "-1", "1.5", "abc", để trống)
|
||||
Then hệ thống hiện lỗi inline dưới F01: "Số lượng phải là số nguyên từ 1 trở lên."
|
||||
(E-CART-0002)
|
||||
And không gửi request PATCH; F01 giữ nguyên giá trị hợp lệ trước đó (không lưu giá trị lỗi)
|
||||
```
|
||||
|
||||
#### AC-US003-08 — Giảm số lượng về 0 *(🔴 chưa viết được đầy đủ — chờ `OQ-028`)*
|
||||
|
||||
```
|
||||
Given CartItem X có quantity=1 (đã ở mức tối thiểu)
|
||||
When tôi bấm nút "-" hoặc nhập trực tiếp "0" vào F01
|
||||
Then 🔴 CHƯA XÁC ĐỊNH — phụ thuộc OQ-028 (PO chưa chốt: (a) chặn nút "-"/nhập 0, hiện
|
||||
thông báo giữ tối thiểu 1, hay (b) tự động mở modal xác nhận xoá dòng)
|
||||
```
|
||||
|
||||
*QA không viết test case cho AC này cho tới khi `OQ-028` được trả lời — đây là chỗ SRS chưa
|
||||
viết xong, không phải QA bỏ sót.*
|
||||
|
||||
---
|
||||
|
||||
### Nhóm 3 — Lỗi hệ thống
|
||||
|
||||
#### AC-US003-09 — Server lỗi 5xx khi cập nhật số lượng
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Liên quan** | SCR-04 · F01 |
|
||||
| **Vai trò** | ROLE-02 |
|
||||
|
||||
```
|
||||
Given CartItem X có quantity=2
|
||||
When tôi đổi thành 5 (bấm "+" 3 lần hoặc nhập tay) và PATCH /v1/cart/items/{X} trả 500
|
||||
Then hệ thống hiện banner lỗi tại dòng CartItem: "Không thể cập nhật giỏ hàng, thử lại."
|
||||
(E-CART-0006)
|
||||
And 🔴 F01 trở về giá trị trước đó (2), KHÔNG giữ giá trị đã nhập lỗi (5); nút +/- mở lại
|
||||
được bấm ngay
|
||||
```
|
||||
|
||||
#### AC-US003-10 — Timeout khi xoá CartItem
|
||||
|
||||
```
|
||||
Given tôi bấm "Xoá" trên CartItem X (đã xác nhận modal)
|
||||
When request DELETE bị timeout theo ngưỡng NFR §1 (xem OQ-033)
|
||||
Then hệ thống hiện banner lỗi tại dòng: "Không thể cập nhật giỏ hàng, thử lại." (E-CART-0006)
|
||||
And CartItem X vẫn hiển thị trong danh sách (không xoá khỏi giao diện khi chưa có xác nhận
|
||||
từ server); nút "Xoá" bấm lại được
|
||||
```
|
||||
|
||||
#### AC-US003-11 — CartItem đã bị xoá bởi thao tác khác (đồng thời, VD 2 tab)
|
||||
|
||||
```
|
||||
Given CartItem X đang hiển thị trên màn hình của tôi, nhưng đã bị xoá khỏi giỏ hàng ở một
|
||||
tab/thiết bị khác trước đó
|
||||
When tôi bấm sửa số lượng (F01) hoặc bấm "Xoá" (C07) trên CartItem X
|
||||
Then PATCH/DELETE trả 404 ERR_NOT_FOUND hoặc 409 ERR_CONFLICT (xem API §4)
|
||||
And hệ thống hiện banner "Sản phẩm này đã được xoá khỏi giỏ hàng trước đó." (E-CART-0003),
|
||||
sau đó ẩn dòng CartItem X khỏi danh sách và cập nhật lại C08/C09
|
||||
```
|
||||
|
||||
Ba tình huống tối thiểu của nhóm này:
|
||||
|
||||
| # | Tình huống | AC |
|
||||
|---|---|---|
|
||||
| 1 | Server trả 5xx | AC-US003-09 |
|
||||
| 2 | Timeout / mất mạng | AC-US003-10 |
|
||||
| 3 | Dữ liệu bị người khác sửa/xoá trong lúc mình đang mở | AC-US003-11 |
|
||||
|
||||
---
|
||||
|
||||
### Nhóm 4 — Phân quyền
|
||||
|
||||
#### AC-US003-12 — Gọi thẳng API sửa/xoá CartItem không thuộc quyền sở hữu (Customer)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Liên quan** | SCR-04 |
|
||||
| **Vai trò** | ROLE-02 |
|
||||
|
||||
```
|
||||
Given tôi đăng nhập ROLE-02 với customerId = A; CartItem Y thuộc Cart của customerId = B
|
||||
When tôi gọi thẳng PATCH /v1/cart/items/{Y} với token của A
|
||||
Then hệ thống trả 403 ERR_FORBIDDEN_OWNERSHIP (E-CART-0005), không cập nhật CartItem Y
|
||||
And không có thông tin nào của CartItem Y bị lộ ra trong response lỗi
|
||||
```
|
||||
|
||||
#### AC-US003-13 — Guest cố sửa/xoá CartItem không thuộc session của mình *(🔴 chưa viết được đầy đủ — chờ `OQ-032`)*
|
||||
|
||||
```
|
||||
Given tương tự AC-US003-12 nhưng với ROLE-01 (Guest): X-Guest-Session-Id = S1 của tôi,
|
||||
CartItem Z thuộc Cart của session S2
|
||||
When tôi gọi thẳng PATCH /v1/cart/items/{Z} với X-Guest-Session-Id: S1
|
||||
Then 🔴 CHƯA XÁC ĐỊNH — đề xuất BA: trả 403 ERR_FORBIDDEN_OWNERSHIP (E-CART-0005) giống hệt
|
||||
AC-US003-12, cần Tech Lead xác nhận cơ chế đối chiếu session_id (OQ-032) trước khi coi
|
||||
AC này là chốt
|
||||
```
|
||||
|
||||
🔴 Chặn ở giao diện là trải nghiệm, **chặn ở backend mới là bảo mật**. Cả hai AC nhóm này đều
|
||||
kiểm tra gọi thẳng API, không chỉ kiểm tra ẩn nút trên giao diện.
|
||||
|
||||
---
|
||||
|
||||
## 3. Bảng dữ liệu biên
|
||||
|
||||
| Field | Dưới ngưỡng | Ngưỡng dưới | Trong khoảng | Ngưỡng trên | Trên ngưỡng | Rỗng | Ký tự đặc biệt | Khoảng trắng đầu/cuối |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| F01 Số lượng (≥1, ngưỡng trên **chưa chốt** — `OQ-030`) | `0` → lỗi `E-CART-0002` (hành vi cụ thể — `OQ-028`) | `1` → OK | `5` → OK | 🔴 chưa có ngưỡng trên xác nhận — `OQ-030` | 🔴 chưa có ngưỡng trên — `OQ-030` | Xoá trắng ô nhập → lỗi bắt buộc (`E-CART-0002`) | `"abc"`, `"1.5"`, `"-1"` → lỗi `E-CART-0002` | N/A — F01 là input dạng số (`type=number`), không nhận khoảng trắng |
|
||||
|
||||
---
|
||||
|
||||
## 4. Kịch bản kết hợp
|
||||
|
||||
| # | Kịch bản | Các bước | AC liên quan |
|
||||
|---|---|---|---|
|
||||
| S1 | Sửa số lượng rồi xoá hết một nhóm seller | SCR-04 (mở) → sửa số lượng CartItem của "Shop B" (tổng cập nhật) → xoá cả 2 CartItem còn lại của "Shop B" → nhóm "Shop B" biến mất, tổng cộng chỉ còn của "Shop A" | AC-US003-01, 03, 04 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Truy vết
|
||||
|
||||
| AC | US | BR | Field/Thành phần | Mã lỗi | Test case (QA điền) | Kết quả (QA điền) |
|
||||
|---|---|---|---|---|---|---|
|
||||
| AC-US003-01 | US-003 | BR §3 CART | F01, C08, C09 | — | | |
|
||||
| AC-US003-02 | US-003 | BR §3 CART | F01, C08, C09 | — | | |
|
||||
| AC-US003-03 | US-003 | BR §3 CART | C07, C08, C09 | — | | |
|
||||
| AC-US003-04 | US-003 | — | C02, C07 | — | | |
|
||||
| AC-US003-05 | US-003 | BR §3 CART | C07, C10, C11 | — | | |
|
||||
| AC-US003-06 | US-003 | — | C07 | — | | |
|
||||
| AC-US003-07 | US-003 | — | F01 | E-CART-0002 | | |
|
||||
| AC-US003-08 | US-003 | — | F01 | E-CART-0002 (dự kiến) | *(chờ OQ-028)* | |
|
||||
| AC-US003-09 | US-003 | — | F01 | E-CART-0006 | | |
|
||||
| AC-US003-10 | US-003 | — | C07 | E-CART-0006 | | |
|
||||
| AC-US003-11 | US-003 | — | F01, C07 | E-CART-0003 | | |
|
||||
| AC-US003-12 | US-003 | RBAC §6 | — | E-CART-0005 | | |
|
||||
| AC-US003-13 | US-003 | RBAC §6 | — | E-CART-0005 (dự kiến) | *(chờ OQ-032)* | |
|
||||
|
||||
---
|
||||
|
||||
## 6. Xác nhận của QA
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Người xác nhận** | *(chưa có — chưa có QA thật tham gia lần chạy này)* |
|
||||
| **Ngày** | — |
|
||||
| ☐ Mọi AC đều test được (2 AC còn mở, chờ OQ trước khi viết test case) | |
|
||||
| ☐ Đủ 4 nhóm | |
|
||||
| ☐ Bảng dữ liệu biên đủ dùng để viết test case | |
|
||||
| ☐ Không có AC nào mâu thuẫn với AC khác | |
|
||||
|
||||
**AC bị QA từ chối:**
|
||||
|
||||
| AC | Lý do từ chối | BA sửa thế nào |
|
||||
|---|---|---|
|
||||
| | | |
|
||||
235
ba-output/e-commerce/03-specification/API_US002-003_v1.0.md
Normal file
235
ba-output/e-commerce/03-specification/API_US002-003_v1.0.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# API — Contract — US-002, US-003 (SCR-04 Giỏ hàng)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Version** | 1.0 |
|
||||
| **Date** | 2026-09-08 |
|
||||
| **Author** | BA (qua skill ba-3-specification) |
|
||||
| **Status** | 🟡 Draft |
|
||||
| **Nguồn contract** | 🔶 **Hỗn hợp** — method/path/auth: ✅ **BE cung cấp** (`e-commerce/docs/sections/04-api-design.md` §4.1.5 "Cart & Order Service"); request/response body schema: ⚠️ **BA đề xuất — chờ BE xác nhận** (SAD liệt kê method/path/mô tả ngắn nhưng không có ví dụ JSON cho ba endpoint dưới đây, khác với `POST /v1/checkout` là endpoint duy nhất SAD có ví dụ đầy đủ) |
|
||||
| **Approved by** | BE Lead: — |
|
||||
| **Source** | `SRS_US002-003_v1.0.md` · `e-commerce/docs/sections/04-api-design.md` §4.1.1, §4.1.5, §4.1.13, §4.2 |
|
||||
| **Scope** | US-002, US-003 |
|
||||
|
||||
> 🔴 **Đọc dòng `Nguồn contract` trước.** Method/path là ràng buộc thật (đã có trong SAD đã
|
||||
> duyệt). Cấu trúc request/response bên dưới là **giả định của BA**, dev phải đối chiếu với
|
||||
> tài liệu BE thật (hoặc OpenAPI spec nếu có) trước khi code.
|
||||
|
||||
---
|
||||
|
||||
## 1. Ba điểm phải chốt trước tiên
|
||||
|
||||
| # | Vấn đề | Quyết định | Lý do |
|
||||
|---|---|---|---|
|
||||
| 1 | **Số lớn** (`cartItemId`, `productVariantId`, `sellerId`) truyền dạng gì | `string` | Khớp ví dụ `POST /v1/checkout` của SAD (`"orderId": "order_001"` dạng string) — áp dụng nhất quán cho mọi id trong hệ thống |
|
||||
| 2 | **Thời gian** — SCR-04 không hiển thị trường thời gian nào | N/A | Không có trường `*_at` nào cần hiển thị ở US-002/US-003 |
|
||||
| 3 | **Phân trang** | Không áp dụng — `GET /v1/cart` trả toàn bộ giỏ hàng, không phân trang | Giỏ hàng không có khái niệm "trang", số dòng thực tế nhỏ (không giống danh sách sản phẩm) |
|
||||
|
||||
## 2. Quy ước chung
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Base URL** | `https://api.<domain>/v1` (theo SAD §4.1.1) |
|
||||
| **Xác thực** | Bearer JWT (Customer) hoặc header `X-Guest-Session-Id` (Guest) — theo SAD §4.1.1, §4.1.5 |
|
||||
| **Định dạng phản hồi thành công** | `{ "data": {...} }` (theo SAD §4.1.1) |
|
||||
| **Định dạng phản hồi lỗi** | `{ "error": { "code", "message", "details" }, "traceId" }` (theo SAD §4.1.13 — **khác** với format mặc định của template BA, ưu tiên đúng theo SAD) |
|
||||
| **Mã HTTP dùng** | 200 · 204 · 400 · 401 · 403 · 404 · 409 · 500 · 503 |
|
||||
| **Ngôn ngữ thông điệp** | FE tự dịch theo mã lỗi (`code`) + bảng text §4.2 của `SRS`, không dựa vào `message` trả về từ BE để hiển thị cho người dùng cuối (theo `UICONV` §9 — BA sở hữu text hiển thị, W5) |
|
||||
|
||||
🔴 **Lỗi nghiệp vụ trả mã HTTP 4xx** (không phải 200 kèm cờ lỗi) — đúng theo thiết kế đã có ở
|
||||
SAD §4.1.13.
|
||||
|
||||
---
|
||||
|
||||
## 3. Endpoint
|
||||
|
||||
### 3.1 `GET /v1/cart` — Lấy giỏ hàng hiện tại (đa seller)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Mục đích** | Tải dữ liệu hiển thị SCR-04 (US-002) |
|
||||
| **Màn hình** | SCR-04 |
|
||||
| **Quyền** | ROLE-01 Guest (`X-Guest-Session-Id`) hoặc ROLE-02 Customer (Bearer JWT) — không cần scope riêng, chỉ cần định danh hợp lệ |
|
||||
|
||||
**Query parameters:** không có (không phân trang).
|
||||
|
||||
**Response 200** *(⚠️ đề xuất của BA — SAD không có ví dụ JSON cho endpoint này)*
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"cartId": "cart_123",
|
||||
"status": "active",
|
||||
"sellers": [
|
||||
{
|
||||
"sellerId": "seller_11",
|
||||
"sellerName": "Shop A",
|
||||
"subtotalVnd": 325000,
|
||||
"items": [
|
||||
{
|
||||
"cartItemId": "citem_001",
|
||||
"productVariantId": "variant_789",
|
||||
"productName": "Áo thun basic",
|
||||
"variantLabel": "Size M, Đỏ",
|
||||
"unitPriceVnd": 100000,
|
||||
"quantity": 2,
|
||||
"imageUrl": "https://cdn.example.com/p/789.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"totalVnd": 585000,
|
||||
"totalItemCount": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Từng trường**
|
||||
|
||||
| Trường | Kiểu | Có thể null | Nguồn | Ghi chú |
|
||||
|---|---|---|---|---|
|
||||
| `cartId` | string | ❌ | `cart.id` | — |
|
||||
| `status` | enum | ❌ | `cart.status` | `active`/`converted`/`abandoned` — SCR-04 chỉ hiển thị khi `active` |
|
||||
| `sellers[].sellerId` | string | ❌ | `cart_item.seller_id` | — |
|
||||
| `sellers[].sellerName` | string | ❌ | Module Seller Management (tham chiếu) | 🔴 **BA đề xuất BE trả kèm tên đã join sẵn** — cần BE xác nhận, xem §5 mục 1 |
|
||||
| `sellers[].subtotalVnd` | number | ❌ | Tính từ `items[]` | 🔴 **BA đề xuất BE tính sẵn** (không để FE tự cộng) — tránh sai lệch làm tròn giữa FE/BE, xem §5 mục 2 |
|
||||
| `items[].unitPriceVnd` | number | ❌ | `cart_item.unit_price_snapshot` hoặc giá real-time | 🔴 Phụ thuộc `OQ-029` (chưa chốt ở `SRS`) |
|
||||
| `items[].quantity` | number | ❌ | `cart_item.quantity` | Số nguyên |
|
||||
| `totalVnd` | number | ❌ | Σ `sellers[].subtotalVnd` | — |
|
||||
| `totalItemCount` | number | ❌ | Σ `items[].quantity` toàn giỏ | Dùng cho C01 |
|
||||
|
||||
**Response lỗi**
|
||||
|
||||
| HTTP | `code` | Khi nào | Mã lỗi SRS |
|
||||
|---|---|---|---|
|
||||
| 401 | `ERR_AUTH_INVALID_TOKEN` | Customer token hết hạn/không hợp lệ | Điều hướng đăng nhập (`AC-US002-06`), không có mã `E-CART` riêng |
|
||||
| 500 / 503 | `ERR_INTERNAL` / `ERR_SERVICE_UNAVAILABLE` | Lỗi hệ thống/timeout | `E-CART-0004` |
|
||||
|
||||
---
|
||||
|
||||
### 3.2 `PATCH /v1/cart/items/{cartItemId}` — Cập nhật số lượng
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Mục đích** | Cập nhật `quantity` của một `CartItem` (US-003, field F01) |
|
||||
| **Màn hình** | SCR-04 |
|
||||
| **Quyền** | Chủ sở hữu `CartItem` (ownership theo `customerId`/`sellerId` trong JWT, hoặc `session_id` cho Guest — cơ chế Guest chưa xác nhận, `OQ-032`) |
|
||||
|
||||
**Request body** *(⚠️ đề xuất)*
|
||||
|
||||
```json
|
||||
{ "quantity": 3 }
|
||||
```
|
||||
|
||||
| Trường | Kiểu | Bắt buộc | Ràng buộc | Field SRS |
|
||||
|---|---|---|---|---|
|
||||
| `quantity` | number | ✅ | Số nguyên ≥ 1; ngưỡng trên 🔴 chưa chốt (`OQ-030`) | F01 |
|
||||
|
||||
🔴 **Ràng buộc ở API phải khớp bảng field trong SRS** — giữ nguyên "≥ 1", không thêm ngưỡng
|
||||
trên tự ý cho tới khi `OQ-030` được trả lời.
|
||||
|
||||
**Response 200** *(⚠️ đề xuất — cần BE xác nhận trả về `CartItem` đã cập nhật hay toàn bộ
|
||||
`Cart` mới, xem §5 mục 3)*
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"cartItemId": "citem_001",
|
||||
"quantity": 3,
|
||||
"sellerSubtotalVnd": 375000,
|
||||
"cartTotalVnd": 635000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response lỗi**
|
||||
|
||||
| HTTP | `code` | Khi nào | Mã lỗi SRS |
|
||||
|---|---|---|---|
|
||||
| 400 | `ERR_VALIDATION` | `quantity` không phải số nguyên ≥ 1 | `E-CART-0002` |
|
||||
| 403 | `ERR_FORBIDDEN_OWNERSHIP` | `cartItemId` không thuộc giỏ hàng của caller | `E-CART-0005` |
|
||||
| 404 | `ERR_NOT_FOUND` | `cartItemId` không tồn tại (đã bị xoá) | `E-CART-0003` |
|
||||
| 409 | `ERR_CONFLICT` | `cartItemId` đã bị xoá bởi request khác trong lúc xử lý | `E-CART-0003` |
|
||||
| 422 | `ERR_BUSINESS_RULE` | 🔴 Dự kiến — nếu `OQ-030` chốt có kiểm tra tồn kho tại đây | *(chưa có mã — chỉ tạo khi `OQ-030` xác nhận thuộc scope)* |
|
||||
| 500 / 503 | `ERR_INTERNAL` / `ERR_SERVICE_UNAVAILABLE` | Lỗi hệ thống/timeout | `E-CART-0006` |
|
||||
|
||||
---
|
||||
|
||||
### 3.3 `DELETE /v1/cart/items/{cartItemId}` — Xoá sản phẩm khỏi giỏ
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Mục đích** | Xoá một `CartItem` (US-003, nút C07, sau khi xác nhận modal) |
|
||||
| **Màn hình** | SCR-04 |
|
||||
| **Quyền** | Chủ sở hữu `CartItem` — cùng quy tắc §3.2 |
|
||||
|
||||
**Response 200/204** *(⚠️ đề xuất — 204 không có body; BA đề xuất 200 kèm `sellerSubtotalVnd`/
|
||||
`cartTotalVnd` mới để FE khỏi tự tính lại, cần BE xác nhận, xem §5 mục 4)*
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"deletedCartItemId": "citem_001",
|
||||
"sellerSubtotalVnd": 260000,
|
||||
"cartTotalVnd": 260000,
|
||||
"sellerRemoved": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response lỗi**
|
||||
|
||||
| HTTP | `code` | Khi nào | Mã lỗi SRS |
|
||||
|---|---|---|---|
|
||||
| 403 | `ERR_FORBIDDEN_OWNERSHIP` | Không thuộc quyền sở hữu | `E-CART-0005` |
|
||||
| 404 | `ERR_NOT_FOUND` | Đã bị xoá trước đó | `E-CART-0003` |
|
||||
| 409 | `ERR_CONFLICT` | Xung đột đồng thời | `E-CART-0003` |
|
||||
| 500 / 503 | `ERR_INTERNAL` / `ERR_SERVICE_UNAVAILABLE` | Lỗi hệ thống/timeout | `E-CART-0006` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Bảng đối chiếu mã lỗi
|
||||
|
||||
| Mã lỗi SRS | Endpoint | HTTP | `code` của API | ☐ Khớp |
|
||||
|---|---|---|---|---|
|
||||
| `E-CART-0002` | `PATCH /v1/cart/items/{id}` | 400 | `ERR_VALIDATION` | ☐ |
|
||||
| `E-CART-0003` | `PATCH`, `DELETE /v1/cart/items/{id}` | 404/409 | `ERR_NOT_FOUND` / `ERR_CONFLICT` | ☐ |
|
||||
| `E-CART-0004` | `GET /v1/cart` | 500/503 | `ERR_INTERNAL` / `ERR_SERVICE_UNAVAILABLE` | ☐ |
|
||||
| `E-CART-0005` | `PATCH`, `DELETE /v1/cart/items/{id}` | 403 | `ERR_FORBIDDEN_OWNERSHIP` | ☐ |
|
||||
| `E-CART-0006` | `PATCH`, `DELETE /v1/cart/items/{id}` | 500/503 | `ERR_INTERNAL` / `ERR_SERVICE_UNAVAILABLE` | ☐ |
|
||||
|
||||
*Cột ☐ do BE đánh dấu khi xác nhận khớp implementation thật.*
|
||||
|
||||
## 5. Điểm cần BE xác nhận
|
||||
|
||||
| # | Điểm cần chốt | Đề xuất của BA | BE trả lời | Ngày |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `GET /v1/cart` trả `sellerName` đã join sẵn, hay chỉ `sellerId` để FE tự gọi API khác lấy tên? | Trả sẵn `sellerName` — tránh FE gọi thêm N request cho N seller | | |
|
||||
| 2 | `sellers[].subtotalVnd`/`totalVnd` do BE tính sẵn, hay FE tự cộng từ `items[]`? | BE tính sẵn — tránh sai lệch làm tròn giữa FE/BE | | |
|
||||
| 3 | `PATCH /v1/cart/items/{id}` trả về `CartItem` đã cập nhật + tổng nhóm/tổng giỏ, hay toàn bộ `Cart` mới? | Trả `CartItem` + tổng nhóm/tổng giỏ liên quan (nhẹ hơn trả toàn bộ `Cart`) | | |
|
||||
| 4 | `DELETE /v1/cart/items/{id}` trả `200` kèm body hay `204` rỗng? | `200` kèm tổng nhóm/tổng giỏ mới + cờ `sellerRemoved` (để FE biết có cần ẩn cả nhóm không) | | |
|
||||
| 5 | `id` (`cartItemId`, `productVariantId`, `sellerId`) dạng `string` hay `number`? | `string` | | |
|
||||
| 6 | Với Guest, ownership của `cartItemId` có được đối chiếu theo `session_id` giống cơ chế `403 ERR_FORBIDDEN_OWNERSHIP` của JWT không? (`OQ-032`, đã ghi ở `SRS`) | Áp dụng cùng cơ chế | | |
|
||||
| 7 | `PATCH /v1/cart/items/{id}` có kiểm tra tồn kho (trả `422 ERR_BUSINESS_RULE`) không? Phụ thuộc `OQ-030` ở `SRS` | Không kiểm tra ở endpoint này (giữ đúng khoanh vùng `BACKLOG`) — chờ PO xác nhận | | |
|
||||
|
||||
## 6. Hành vi khi API lỗi *(giao diện phải làm gì)*
|
||||
|
||||
| Tình huống | Giao diện làm gì | AC |
|
||||
|---|---|---|
|
||||
| 401 hết phiên (Customer) | Chuyển về đăng nhập, giữ đường dẫn quay lại `/cart` | `AC-US002-06` |
|
||||
| 403 (ownership) | Chuyển trang "Không có quyền", không hiển thị dữ liệu | `AC-US003-12`, `13` |
|
||||
| 404/409 (item đã bị xoá) | Banner tại dòng, sau đó ẩn dòng, cập nhật lại tổng | `AC-US003-11` |
|
||||
| 5xx / timeout khi tải (`GET`) | Banner C12 thay chỗ danh sách, nút "Thử lại" | `AC-US002-03` |
|
||||
| 5xx / timeout khi sửa/xoá (`PATCH`/`DELETE`) | Banner tại dòng, **giữ nguyên dữ liệu trước đó**, mở lại nút | `AC-US003-09`, `10` |
|
||||
| Đang gửi `PATCH`/`DELETE` | Khoá F01/C07 của đúng dòng đang xử lý (không khoá toàn trang) để tránh gửi trùng | — |
|
||||
|
||||
🔴 **Khoá đúng nút/dòng đang xử lý, không khoá toàn màn hình** — SCR-04 thường có nhiều dòng
|
||||
`CartItem` cùng lúc; khoá toàn trang khi chỉ một dòng đang cập nhật sẽ chặn nhầm thao tác trên
|
||||
các dòng khác.
|
||||
|
||||
## 7. Open Questions
|
||||
|
||||
| ID | Câu hỏi | Hỏi ai | Từ ngày | Chặn gì |
|
||||
|---|---|---|---|---|
|
||||
| OQ-032 | Cơ chế ownership check của Guest cho `PATCH`/`DELETE /v1/cart/items/{id}` (xem `SRS` §8) | Tech Lead | 2026-09-08 | §5 mục 6, `E-CART-0005` cho Guest |
|
||||
| OQ-030 | `PATCH /v1/cart/items/{id}` có kiểm tra tồn kho (`422 ERR_BUSINESS_RULE`) không (xem `SRS` §8) | PO, Tech Lead | 2026-09-08 | §3.2, §5 mục 7 |
|
||||
124
ba-output/e-commerce/03-specification/NFR_US002-003_v1.0.md
Normal file
124
ba-output/e-commerce/03-specification/NFR_US002-003_v1.0.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# NFR — Yêu cầu phi chức năng — US-002, US-003 (SCR-04 Giỏ hàng)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Version** | 1.0 |
|
||||
| **Date** | 2026-09-08 |
|
||||
| **Author** | BA (qua skill ba-3-specification) |
|
||||
| **Status** | 🟡 Draft |
|
||||
| **Approved by** | Tech Lead: — |
|
||||
| **Source** | `SRS_US002-003_v1.0.md` · `e-commerce/docs/sections/02-phan-tich-yeu-cau.md` (NFR-01…08) · `e-commerce/docs/sections/04-api-design.md` §4.1.1, §4.2 (ownership, rate limit, Guest session) · `RBAC_CartCheckout_v1.0.md` §5 |
|
||||
| **Scope** | US-002, US-003 — màn hình `SCR-04` Giỏ hàng |
|
||||
|
||||
---
|
||||
|
||||
## Quy tắc
|
||||
|
||||
Mỗi NFR bắt buộc ba thứ: **con số đo được** · **điều kiện đo** · **cách verify**. Con số chưa
|
||||
có nguồn ⇒ ghi 🔴 + `OQ-nnn`, không tự đặt.
|
||||
|
||||
---
|
||||
|
||||
## 1. Hiệu năng — `NFR-PERF-nn`
|
||||
|
||||
| ID | Yêu cầu | Ngưỡng | Điều kiện đo | Cách verify | Ai chốt |
|
||||
|---|---|---|---|---|---|
|
||||
| NFR-PERF-01 | Thời gian tải giỏ hàng (`GET /v1/cart`) | 🔴 chưa chốt riêng cho US-002/003 — SAD NFR-01 chỉ có ngưỡng cho danh mục/tìm kiếm (<2s) và checkout (<3s); BA đề xuất áp cùng ngưỡng danh mục (≤2s p95) vì cùng là thao tác đọc — xem `OQ-033` | 🔴 chưa xác nhận số `CartItem` tối đa/giỏ để đo (đề xuất ≤50 dòng, chưa có số liệu vận hành — `greenfield`) | k6 kịch bản tải `GET /v1/cart` | Tech Lech (chờ xác nhận) |
|
||||
| NFR-PERF-02 | Thời gian phản hồi sửa số lượng / xoá (`PATCH`/`DELETE /v1/cart/items`) | 🔴 chưa chốt — BA đề xuất ≤1s p95 (thao tác ghi 1 dòng, đơn giản hơn checkout) — xem `OQ-033` | 1 người dùng, 1 request | k6 kịch bản `PATCH`/`DELETE` đơn lẻ | Tech Lead (chờ xác nhận) |
|
||||
|
||||
🔴 **Không tự đặt số khi PO/Tech Lead chưa xác nhận** — hai dòng trên chỉ là **đề xuất** của BA
|
||||
dựa trên phép loại suy từ NFR-01 của SAD, không phải giá trị đã chốt. Xem `OQ-033`.
|
||||
|
||||
## 2. Dung lượng & tăng trưởng — `NFR-CAP-nn`
|
||||
|
||||
| Đại lượng | Hiện tại | Sau 1 năm | Sau 3 năm | Nguồn số liệu |
|
||||
|---|---|---|---|---|
|
||||
| Số `CartItem` trung bình/tối đa mỗi giỏ hàng | N/A — `greenfield`, chưa vận hành | 🔴 chưa có số liệu | 🔴 chưa có số liệu | Chưa có — cần đo sau go-live (không phải việc của GĐ3) |
|
||||
| Số giỏ hàng đồng thời (giờ cao điểm) | N/A | 🔴 chưa có | 🔴 chưa có | Tham chiếu `BRIEF_CartCheckout_v1.0.md` §2 quy mô "large" — chưa có con số cụ thể cho riêng module Giỏ hàng |
|
||||
|
||||
**N/A theo `LIFECYCLE = greenfield`** — không có dữ liệu vận hành cũ để ước lượng tăng trưởng
|
||||
(nhất quán với `IMPACT_CartCheckout_v1.0.md` §1.6). Ghi nhận, không bỏ trống im lặng.
|
||||
|
||||
| Giờ cao điểm | Khi nào | Vì sao |
|
||||
|---|---|---|
|
||||
| 🔴 chưa xác nhận | *(đề xuất tham khảo: giờ tối, cuối tuần, mùa sale — theo mô hình marketplace bán lẻ thông thường, chưa có số liệu thật)* | Chưa vận hành để có dữ liệu thật |
|
||||
|
||||
## 3. Bảo mật & quyền riêng tư — `NFR-SEC-nn`
|
||||
|
||||
| ID | Yêu cầu | Chi tiết | Căn cứ | Cách verify |
|
||||
|---|---|---|---|---|
|
||||
| NFR-SEC-01 | Mọi `PATCH`/`DELETE` cart item kiểm tra quyền sở hữu (chống IDOR) | Đối chiếu `customerId`/`sellerId` trong JWT (Customer) hoặc `session_id` (Guest — cơ chế cụ thể chờ `OQ-032`) với chủ sở hữu `CartItem`; không khớp → `403 ERR_FORBIDDEN_OWNERSHIP` | `e-commerce/docs/sections/04-api-design.md` §4.1.1 | Test gọi thẳng API với token/session không phải chủ sở hữu — `AC-US003-12`, `13` |
|
||||
| NFR-SEC-02 | Định danh Guest (`X-Guest-Session-Id`) không lộ qua kênh không an toàn | Cookie `HttpOnly; Secure; SameSite=Lax` (theo SAD §4.1.1) — SRS này chỉ tham chiếu, không định nghĩa lại thiết kế kỹ thuật | `e-commerce/docs/sections/04-api-design.md` §4.1.1 | Kiểm tra thuộc tính cookie trên môi trường staging |
|
||||
| NFR-SEC-03 | Giới hạn tần suất gọi (rate limit) cho `PATCH`/`DELETE /v1/cart/items` của Guest | Theo IP nguồn, ngưỡng cụ thể do SAD định nghĩa ở tầng kỹ thuật (không phải quyết định của SRS này) | `e-commerce/docs/sections/04-api-design.md` §4.2 | Tham chiếu, không kiểm thử ở phạm vi BA |
|
||||
|
||||
**Rà bốn câu:**
|
||||
|
||||
| Câu hỏi | Trả lời |
|
||||
|---|---|
|
||||
| Có thông tin cá nhân không? Loại nào? | Không — `CartItem` chỉ chứa `product_variant_id`, `seller_id`, `quantity`; không có PII của khách trong phạm vi US-002/US-003 |
|
||||
| Lưu bao lâu? Xoá thế nào khi hết hạn? | Theo vòng đời `Cart` — `BR_CartCheckout_v1.0.md` §3 (Guest 7 ngày, Customer 30 ngày theo `05-thiet-ke-du-lieu.md` §5.3, **chưa được Tech Lead xác nhận áp dụng đúng số này cho module**, xem `BACKLOG` US-001) |
|
||||
| Ai được xuất ra ngoài hệ thống? | Không áp dụng — không có chức năng xuất dữ liệu giỏ hàng trong US-002/US-003 |
|
||||
| Có quy định pháp luật nào áp dụng không? | Không trực tiếp — dữ liệu giỏ hàng chưa phải giao dịch tài chính (khác `Order`/`Payment`, thuộc NFR-05) |
|
||||
|
||||
## 4. Lưu vết — `NFR-AUD-nn`
|
||||
|
||||
| Nhóm | N/A vì |
|
||||
|---|---|
|
||||
| Lưu vết (audit log) cho sửa/xoá `CartItem` | Đây là thao tác tự phục vụ trên dữ liệu **tạm thời** trước khi đặt hàng, không phải giao dịch tài chính — theo `RBAC_CartCheckout_v1.0.md` §5, yêu cầu ghi vết chỉ bắt buộc từ bước tạo `Order`/`Payment` trở đi (US-004 trở lên). Sửa/xoá `CartItem` không cần audit log riêng ở US-002/US-003 |
|
||||
|
||||
## 5. Khả dụng & xử lý sự cố — `NFR-AVL-nn`
|
||||
|
||||
| ID | Yêu cầu | Ngưỡng | Ghi chú |
|
||||
|---|---|---|---|
|
||||
| NFR-AVL-01 | Hành vi khi Cart & Order Service lỗi/chậm khi tải giỏ hàng | Banner lỗi + nút "Thử lại" (không tự động retry lặp lại) | Đã có AC — `AC-US002-03` |
|
||||
| NFR-AVL-02 | Hành vi khi Cart & Order Service lỗi/chậm khi sửa/xoá | Banner lỗi tại dòng, giữ nguyên dữ liệu trước đó, mở lại nút | Đã có AC — `AC-US003-09`, `10` |
|
||||
|
||||
**Hệ thống ngoài lỗi thì nghiệp vụ này làm gì?**
|
||||
|
||||
| Hệ thống ngoài | Nếu lỗi/chậm | Người dùng thấy gì | Dữ liệu xử lý sao |
|
||||
|---|---|---|---|
|
||||
| Catalog & Inventory (nội bộ — cung cấp tên/ảnh/biến thể sản phẩm hiển thị ở C03–C06) | Chậm/không phản hồi khi tải giỏ hàng | 🔴 chưa xác nhận: chặn hoàn toàn tải giỏ hàng, hay hiển thị giỏ hàng với các trường thiếu (VD ảnh trống, tên "—")? | Chưa xác định — cần Tech Lead xác nhận SLA nội bộ giữa Cart & Order Service và Catalog Service (tương tự khoảng trống đã ghi ở `IMPACT_CartCheckout_v1.0.md` §1.5). Ghi nhận là khoảng trống, không tạo `OQ` mới trùng lặp — thuộc phạm vi `IMPACT` đã có |
|
||||
|
||||
## 6. Đa ngữ & định dạng — `NFR-I18N-nn`
|
||||
|
||||
| Khía cạnh | Yêu cầu | Ghi chú |
|
||||
|---|---|---|
|
||||
| Ngôn ngữ hỗ trợ | VI (mặc định), EN đầy đủ ở SRS §4.2; ZH/KO/JA để trống — `OQ-023` (đã mở ở `UICONV`) | Tham chiếu `UICONV` §9 |
|
||||
| Múi giờ hiển thị | N/A — SCR-04 không hiển thị trường thời gian nào | — |
|
||||
| Định dạng ngày | N/A — không áp dụng cho SCR-04 | — |
|
||||
| Dấu phân cách số | Theo `UICONV` §8 (`#,##0 ₫`) | — |
|
||||
| Đơn vị tiền | VND, không đổi tiền tệ giao dịch; giá quy đổi tham khảo (`CurrencyToggle`) — chưa xác nhận có hiển thị ở SCR-04 hay không, mức độ thấp, không chặn G3, ghi nhận cho Dev FE tham khảo `UICONV` §0 | — |
|
||||
| Sắp xếp chuỗi có dấu | N/A — không có sắp xếp theo tên trên SCR-04 (chỉ nhóm theo seller, thứ tự nhóm — xem `SRS` §2.A.3.2) | — |
|
||||
| Độ dài text sau khi dịch | Tên sản phẩm/seller dài hơn ở ZH/KO/JA so với VI — layout co giãn theo `UICONV` §0, không fix-width | Tham chiếu `UICONV` §0 |
|
||||
|
||||
## 7. Khả năng truy cập & thiết bị — `NFR-ACC-nn`
|
||||
|
||||
| Khía cạnh | Yêu cầu |
|
||||
|---|---|
|
||||
| Trình duyệt hỗ trợ | Tham chiếu `UICONV` — chưa có bảng ma trận trình duyệt riêng, N/A ở mức SRS này |
|
||||
| Kích thước màn hình nhỏ nhất | Tham chiếu `UICONV` §1 — breakpoint mobile < 768px (giá trị cụ thể `OQ-018`, đã mở) |
|
||||
| Dùng trên điện thoại không | Có — web responsive (theo SAD §7.0) |
|
||||
| Thao tác bằng bàn phím | 🔴 chưa xác nhận thứ tự focus cho F01/C07 — mức độ thấp ở `RIGOR = standard` (chỉ bắt buộc đủ ở `strict`), không chặn G3, ghi nhận cho Dev FE |
|
||||
| Tương phản màu | N/A ở mức BA — thuộc thiết kế visual (`OQ-022`, design system chưa chọn) |
|
||||
|
||||
---
|
||||
|
||||
## 8. Nhóm không áp dụng
|
||||
|
||||
| Nhóm | N/A vì |
|
||||
|---|---|
|
||||
| Lưu vết (`NFR-AUD`) | Xem §4 — thao tác tự phục vụ trên dữ liệu tạm thời, không phải giao dịch tài chính |
|
||||
|
||||
## 9. Open Questions
|
||||
|
||||
| ID | Câu hỏi | Hỏi ai | Từ ngày | Chặn gì |
|
||||
|---|---|---|---|---|
|
||||
| OQ-033 | Ngưỡng thời gian phản hồi (p95) cho `GET /v1/cart` và cho `PATCH`/`DELETE /v1/cart/items` là bao nhiêu giây? | PO, Tech Lead | 2026-09-08 | NFR-PERF-01, NFR-PERF-02 |
|
||||
|
||||
## 10. Xác nhận
|
||||
|
||||
| Vai trò | Người | Nội dung xác nhận | Ngày |
|
||||
|---|---|---|---|
|
||||
| Tech Lead | *(chưa có)* | ☐ Ngưỡng khả thi với kiến trúc hiện tại (chờ chốt số ở `OQ-033`) | — |
|
||||
| QA | *(chưa có)* | ☐ Mọi NFR đều có cách verify chạy được | — |
|
||||
| PO | *(chưa có)* | ☐ Ngưỡng khớp kỳ vọng người dùng đã trao đổi | — |
|
||||
379
ba-output/e-commerce/03-specification/SRS_US002-003_v1.0.md
Normal file
379
ba-output/e-commerce/03-specification/SRS_US002-003_v1.0.md
Normal file
@@ -0,0 +1,379 @@
|
||||
# SRS — US-002, US-003 — Xem giỏ hàng nhóm theo seller · Sửa số lượng / xoá sản phẩm trong giỏ
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Version** | 1.0 |
|
||||
| **Date** | 2026-09-08 |
|
||||
| **Author** | BA (qua skill ba-3-specification) |
|
||||
| **Status** | 🟡 Draft |
|
||||
| **Approved by** | PO: — · Tech Lead: — · QA: — · Designer: — *(dự án không có Designer riêng — PO ký thay, `DEC-01`)* |
|
||||
| **Source** | `02-analysis/BACKLOG_CartCheckout_v1.0.md` (US-002, US-003) · `02-analysis/BR_CartCheckout_v1.0.md` (§3 vòng đời `CART`, §4 ERD) · `02-analysis/RBAC_CartCheckout_v1.0.md` (§1, §2, §6) · `02-analysis/IMPACT_CartCheckout_v1.0.md` · `00-index/UICONV_e-commerce_v1.0.md` v1.1 · `e-commerce/docs/sections/07-giao-dien.md` §7.1.1 SCR-04 (prototype tham chiếu dạng văn bản, chế độ 🎨, `DEC-01`) · `e-commerce/docs/sections/04-api-design.md` §4.1.1, §4.1.5, §4.1.13, §4.2 · `e-commerce/docs/sections/05-thiet-ke-du-lieu.md` §5.2.3 (tham chiếu entity `cart`/`cart_item`, không chép schema vật lý) |
|
||||
| **Scope** | US-002 (Xem giỏ hàng nhóm theo seller), US-003 (Sửa số lượng / xoá sản phẩm trong giỏ) — cùng màn hình `SCR-04` Giỏ hàng |
|
||||
| **Profile** | `screen · greenfield · standard` (`00-index/PROFILE_e-commerce.md`) |
|
||||
| **Biến thể PART 2** | `srs-part2/screen.md` (2.A Màn hình) + 2.B API (US-002/US-003 gọi trực tiếp API giỏ hàng để hiển thị/cập nhật) |
|
||||
| **Ngôn ngữ hiển thị** | VI + EN (đầy đủ ở §4.2) · ZH/KO/JA để trống — chờ `OQ-023` (cần biên dịch chuyên nghiệp, không dịch máy — ghi chú người duyệt) |
|
||||
|
||||
## Change Log
|
||||
|
||||
| Version | Date | Người sửa | Thay đổi | CR |
|
||||
|---|---|---|---|---|
|
||||
| 1.0 | 2026-09-08 | BA (qua skill ba-3-specification) | Bản đầu — SRS chung US-002 + US-003, PART 2.A (màn hình SCR-04) + 2.B (API), 6 mã lỗi mới (`E-CART-0002`…`0006`), 6 `OQ` mới (`OQ-028`…`OQ-033`) | — |
|
||||
|
||||
> ⚠️ **Ngoại lệ gate** — tài liệu này được viết trong khi Gate G2 (Solution sign-off) của module Giỏ hàng & Checkout **chưa được PO + Tech Lech ký chính thức** (xem `DEC-02`, `DEC-05`, `00-index/DECISION_e-commerce.md`). Ngoại lệ đã được người điều phối xác nhận rõ ràng — xem `DEC-06`. Nếu `BACKLOG_CartCheckout`/`BR_CartCheckout` đổi khi G2 ký thật, phần bị ảnh hưởng nhiều nhất ở đây là §1.3 (BR áp dụng — hiện chưa có `BR-CART-nn` nào áp dụng trực tiếp) và §1.4 (vòng đời `CART`).
|
||||
|
||||
> **Quy ước đọc tài liệu này** *(quy tắc W11)*. Bảng thành phần ở §2.A.3.1 quyết định **một phần tử có tồn tại hay không** và **nó hành xử thế nào**. `WF_US002-003_v1.0.md` + `.html` (và prototype SAD §7.1.1 nó tham chiếu) quyết định **nó nằm ở đâu, to bằng nào, trông ra sao ở từng trạng thái**. `UICONV_e-commerce_v1.0.md` quyết định **quy ước chung** — tài liệu này chỉ ghi phần khác. Khi mâu thuẫn: bảng thắng về sự tồn tại và hành vi, WF thắng về bố cục — mâu thuẫn đó có dòng ở `WF` §5.
|
||||
|
||||
---
|
||||
|
||||
## PHẦN 1 — NGHIỆP VỤ
|
||||
|
||||
### 1.1 Tóm tắt cho người quyết định
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **US này giải quyết** | RQ-001: Giỏ hàng đa seller, một lần checkout. US-002 cho khách nhìn thấy rõ mình đang mua sản phẩm gì của những seller nào và tổng tiền từng seller/toàn giỏ trước khi quyết định đặt hàng; US-003 cho khách tự sửa số lượng hoặc bỏ bớt sản phẩm ngay tại màn hình này mà không phải rời trang |
|
||||
| **Người dùng được gì** | Nhìn một màn hình duy nhất biết chính xác đang mua của ai, số tiền mỗi seller và tổng cộng; tự điều chỉnh giỏ hàng (số lượng, xoá dòng) và thấy tổng tiền cập nhật ngay, không cần tải lại trang |
|
||||
| **Khác hiện tại chỗ nào** | N/A — dự án `greenfield` (xem `PROFILE_e-commerce.md` §Hệ quả đã áp dụng), không có màn hình giỏ hàng cũ để so sánh; đây là màn hình mới hoàn toàn của module Giỏ hàng & Checkout |
|
||||
|
||||
### 1.2 Vai trò và quyền
|
||||
|
||||
| Vai trò | Được làm gì trong US này | Không được làm gì | RBAC |
|
||||
|---|---|---|---|
|
||||
| ROLE-01 Guest | Xem giỏ hàng của chính mình (theo `X-Guest-Session-Id`); sửa số lượng / xoá `CartItem` của chính mình | Xem/sửa giỏ hàng của người khác; không có sổ địa chỉ (không liên quan ở US này) | `RBAC_CartCheckout_v1.0.md` §2 |
|
||||
| ROLE-02 Customer | Xem giỏ hàng của chính mình (theo `customer_id`); sửa số lượng / xoá `CartItem` của chính mình | Xem/sửa giỏ hàng của người khác | `RBAC_CartCheckout_v1.0.md` §2 |
|
||||
|
||||
### 1.3 Business rule áp dụng
|
||||
|
||||
| BR | Phát biểu ngắn | Áp dụng ở màn hình/field nào | Khi vi phạm |
|
||||
|---|---|---|---|
|
||||
| *(không có)* | Không có `BR-CART-nn` nào áp dụng trực tiếp cho US-002/US-003 — đúng theo `BACKLOG_CartCheckout_v1.0.md` (US-002: "BR áp dụng: chưa có — mục hiển thị thuần tuý"; US-003: "BR áp dụng: chưa có — ràng buộc số lượng tối thiểu/tối đa mỗi dòng chưa được chốt") | — | — |
|
||||
| BR §3 `CART` | Chỉ được sửa số lượng / xoá `CartItem` khi `Cart.status = active` (nguồn: `BR_CartCheckout_v1.0.md` §3, bảng "Chuyển trạng thái KHÔNG được phép") | Toàn màn `SCR-04` | Chặn thao tác — xem §1.4 |
|
||||
|
||||
🔴 **Không có BR nào định nghĩa giới hạn trên của số lượng (kiểm tra tồn kho) hay hành vi khi số lượng về 0** — đây là khoảng trống thật của `BACKLOG`/`BR` ở GĐ2, không phải BA quên chép. Xem `OQ-028`, `OQ-030`.
|
||||
|
||||
### 1.4 Vòng đời trạng thái *(tham chiếu — không định nghĩa lại)*
|
||||
|
||||
*Vòng đời đầy đủ của `CART` ở `BR_CartCheckout_v1.0.md` §3. Ở đây chỉ nêu phần US-002/US-003 chạm tới.*
|
||||
|
||||
| Nguồn | Sự kiện | Điều kiện | Đích | Ai được làm | BR |
|
||||
|---|---|---|---|---|---|
|
||||
| Active | Sửa số lượng `CartItem` | `Cart.status = active` | Active (không đổi) | Guest/Customer chủ giỏ | BR §3 `CART` |
|
||||
| Active | Xoá `CartItem` (kể cả dòng cuối cùng của giỏ) | `Cart.status = active` | Active (không đổi — 🔴 giỏ hàng **không** tự chuyển `abandoned` khi rỗng, chỉ chuyển khi hết hạn phiên theo BR §3) | Guest/Customer chủ giỏ | BR §3 `CART` |
|
||||
|
||||
**Chuyển trạng thái KHÔNG được phép ở US-002/US-003:** không có thao tác nào trong hai US này làm `Cart.status` chuyển sang `converted`/`abandoned` — hai chuyển đổi đó thuộc US-004 (checkout) và cơ chế hết hạn phiên tự động (ngoài phạm vi SRS này).
|
||||
|
||||
### 1.5 Ngoài phạm vi *(quy tắc W12)*
|
||||
|
||||
| # | Không làm gì | Vì sao | Xử lý ở đâu/khi nào |
|
||||
|---|---|---|---|
|
||||
| 1 | Kiểm tra tồn kho thời điểm thực khi xem/sửa giỏ hàng | `BACKLOG_CartCheckout_v1.0.md` US-003 khoanh phạm vi: "cảnh báo hết hàng theo thời gian thực thuộc US-004 tại thời điểm checkout, không phải tại thời điểm xem/sửa giỏ — trừ khi PO quyết khác" | US-004 (checkout); sẽ được cập nhật nếu PO trả lời `OQ-030` theo hướng khác |
|
||||
| 2 | Áp dụng khuyến mãi/mã giảm giá/điểm thưởng | Điểm tích hợp với module Khuyến mãi & Loyalty (`DEC-03`) | US-004/US-005 (module khác gọi vào) |
|
||||
| 3 | Chọn/bỏ chọn từng dòng hoặc từng nhóm seller để checkout một phần giỏ hàng (checkbox) | `SAD §7.1.1 SCR-04` mô tả thành phần này nhưng **không có `US` nào trong `BACKLOG` xác nhận** tính năng "checkout một phần giỏ hàng" | Chưa xác định — xem `OQ-031`, `WF_US002-003` §5 dòng #1. **Không tự thêm vào SRS này** |
|
||||
| 4 | Hiển thị badge "Sản phẩm đã hết hàng" / "Giá đã thay đổi" tại màn Giỏ hàng | `SAD §7.1.1 SCR-04` mô tả nhưng phụ thuộc mục 1, 2 ở trên (nguồn dữ liệu tồn kho/giá real-time chưa xác nhận thuộc scope US-002/US-003) | Xem `OQ-029`, `OQ-030`, `WF_US002-003` §5 dòng #2, #3 |
|
||||
| 5 | Tính phí vận chuyển | Chỉ có ở bước checkout, cần địa chỉ trước | US-005 |
|
||||
| 6 | Thanh toán | Ngoài phạm vi màn hình Giỏ hàng | US-007/US-008 |
|
||||
| 7 | Điều hướng từ dòng `CartItem` (ảnh/tên) về trang chi tiết sản phẩm | Không được `BACKLOG`/`BR` xác nhận là hành vi bắt buộc của US-002/US-003 | Bổ sung qua `CR` sau nếu PO yêu cầu |
|
||||
|
||||
---
|
||||
|
||||
## PHẦN 2 — ĐẶC TẢ SẢN PHẨM
|
||||
|
||||
### 2.A — Màn hình (`srs-part2/screen.md`)
|
||||
|
||||
#### 2.A.1 Danh sách màn hình
|
||||
|
||||
| ID | Tên màn hình | Loại | Đường dẫn | Vai trò truy cập | Vào từ đâu | Ra đi đâu |
|
||||
|---|---|---|---|---|---|---|
|
||||
| SCR-04 | Giỏ hàng | Chi tiết (không phân trang) | `/cart` *(quy ước kỹ thuật, Dev FE xác nhận — không chặn G3)* | ROLE-01 Guest, ROLE-02 Customer | Icon giỏ hàng ở header toàn site (`SCR-01`, theo SAD §7.1.1) | `SCR-05` Checkout (US-004, ngoài phạm vi SRS này) · `SCR-01` (khi giỏ rỗng, nút "Tiếp tục mua sắm") |
|
||||
|
||||
#### 2.A.2 Sơ đồ điều hướng
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
SCR01(["SCR-01 Trang chủ — icon giỏ hàng"]) -->|"bấm icon giỏ hàng"| SCR04["SCR-04 Giỏ hàng"]
|
||||
SCR04 -->|"'Tiếp tục mua sắm' (giỏ rỗng, C10)"| SCR01
|
||||
SCR04 -->|"'Tiến hành Checkout' (C11) — US-004, ngoài phạm vi SRS này"| SCR05[["SCR-05 Checkout — ngoài phạm vi"]]
|
||||
SCR01 -.->|"vào URL /cart trực tiếp"| SCR04
|
||||
```
|
||||
|
||||
*ID node khớp cột `ID` của bảng §2.A.1.*
|
||||
|
||||
| Câu hỏi | SCR-04 |
|
||||
|---|---|
|
||||
| Hai đường quay lại (nút back + breadcrumb)? Quay lại có giữ trạng thái danh sách? | Không áp dụng breadcrumb (SCR-04 không lồng trong danh mục); quay lại dùng logo/menu header (theo `UICONV` §2). Không có "trạng thái danh sách" (giỏ hàng không phân trang/lọc) để giữ |
|
||||
| Vào bằng URL trực tiếp `/cart` khi chưa có `CartItem` nào / chưa có session Guest | Hệ thống tự tạo `Cart` mới rỗng (Guest) hoặc lấy `Cart` rỗng của `customer_id` (Customer) — hiển thị trạng thái rỗng §2.A.3.6, không phải lỗi 404 |
|
||||
| Rời màn hình khi đang sửa dở | N/A — không có "form đang dở": mỗi lần sửa số lượng/xoá gửi ngay lập tức (không có nút Lưu tổng, xem §2.A.3.5 và ngoại lệ `UICONV` §12) |
|
||||
|
||||
#### 2.A.3 SCR-04 — Giỏ hàng
|
||||
|
||||
**Bố cục:** `WF_US002-003_v1.0.md` §3.1 · `WF_US002-003_v1.0.html#SCR-04` · prototype: 🎨 `e-commerce/docs/sections/07-giao-dien.md` §7.1.1 SCR-04. Hành vi xem bảng bên dưới, không suy từ hình (W11).
|
||||
|
||||
##### 2.A.3.1 Bảng thành phần
|
||||
|
||||
| ID | Tên thành phần | Loại | Nhãn (nguyên văn) | Placeholder / Hint | Hành vi & sự kiện | Điều kiện ẩn/khoá | BR |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| C01 | lbl_header_title | Nhãn | "Giỏ hàng của bạn ({n} sản phẩm)" | — | `{n}` = tổng số lượng (Σ quantity mọi `CartItem`), tĩnh, cập nhật khi Σ đổi | — | — |
|
||||
| C02 | grp_seller_header | Nhãn nhóm | "{Tên gian hàng}" | — | Lặp lại theo từng `seller_id` có ≥1 `CartItem`; nguồn tên: `seller_id` → tên gian hàng (module Seller Management, tham chiếu) | ❌ ẩn nếu nhóm không còn `CartItem` nào (xem AC-US003-04) | — |
|
||||
| C03 | img_cart_item | Ảnh | — | Ảnh dạng ô xám khi lỗi tải | Ảnh đại diện `product_variant_id` | — | — |
|
||||
| C04 | lbl_product_name | Nhãn | Tên sản phẩm | — | Hiển thị tĩnh, không điều hướng (xem §1.5 dòng 7) | — | — |
|
||||
| C05 | lbl_variant | Nhãn | "{Thuộc tính biến thể}" (VD "Size M, Đỏ") | — | Hiển thị tĩnh, nguồn `product_variant_id` (module Catalog, tham chiếu, không sở hữu) | — | — |
|
||||
| C06 | lbl_unit_price | Nhãn | "{đơn giá} ₫" | — | Hiển thị tĩnh — nguồn giá trị: 🔴 chưa chốt snapshot hay real-time, xem `OQ-029` | — | — |
|
||||
| F01 | input_quantity | Ô nhập số (bộ đếm +/-) | — | — | Xem bảng field §2.A.3.3 | 🔒 disable trong lúc đang gửi cập nhật (spinner cục bộ, xem §2.A.3.6) | — |
|
||||
| C07 | btn_delete_item | Nút nguy hiểm | "Xoá" | — | Bấm → mở modal xác nhận (theo `UICONV` §5 — mặc định áp dụng, không có ngoại lệ ghi ở `UICONV` §12 cho dòng này) → xác nhận thì gọi `DELETE` | 🔒 disable trong lúc đang gửi | — |
|
||||
| C08 | lbl_seller_subtotal | Nhãn | "Tạm tính: {tổng nhóm} ₫" | — | = Σ (`quantity` × giá hiển thị C06) của các `CartItem` cùng `seller_id`; cập nhật ngay khi F01/C07 thành công | — | — |
|
||||
| C09 | lbl_cart_total | Nhãn | "Tổng cộng: {tổng giỏ} ₫" | — | = Σ tất cả C08 | — | — |
|
||||
| C10 | btn_continue_shopping | Nút phụ | "Tiếp tục mua sắm" | — | Chỉ hiện ở trạng thái rỗng → điều hướng `SCR-01` | ❌ ẩn khi giỏ có ≥1 `CartItem` | — |
|
||||
| C11 | btn_checkout | Nút chính | "Tiến hành Checkout" | — | Bấm → điều hướng `SCR-05` (US-004, ngoài phạm vi SRS này) | 🔒 disable khi giỏ có 0 `CartItem`. **Điều kiện khác (yêu cầu chọn dòng) chưa xác nhận thuộc scope — xem `OQ-031`, không áp dụng ở SRS này** | — |
|
||||
| C12 | banner_error_load | Banner | — | — | Hiện khi `GET /v1/cart` lỗi/timeout, thay chỗ danh sách `CartItem` | ❌ ẩn khi tải thành công | — |
|
||||
| C13 | btn_retry_load | Nút phụ | "Thử lại" | — | Bấm → gọi lại `GET /v1/cart` | Nằm trong C12 | — |
|
||||
|
||||
**Cột `Điều kiện ẩn/khoá` — quy tắc W7 áp dụng đúng ba giá trị `❌ ẩn` / `🔒 disable` / `👁 read-only`, không ghi "tuỳ trường hợp".**
|
||||
|
||||
##### 2.A.3.2 Bảng thuộc tính hiển thị mỗi dòng `CartItem`
|
||||
|
||||
*Không phải danh sách phân trang/sắp xếp (giỏ hàng hiển thị toàn bộ, không giới hạn số dòng mỗi trang) — bảng dưới đây thay cho §2.3.2 chuẩn để ghi nguồn dữ liệu từng thuộc tính hiển thị.*
|
||||
|
||||
| # | Thuộc tính | Nguồn dữ liệu | Định dạng | Xử lý khi rỗng/thiếu | Ghi chú |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | Ảnh | `product_variant_id` → ảnh đại diện (module Catalog) | Ảnh vuông | Ô xám mặc định (theo `UICONV`) | — |
|
||||
| 2 | Tên sản phẩm | `product_variant_id` → tên (module Catalog) | Text, không giới hạn dòng xác nhận — 🔴 chưa chốt số dòng tối đa (không blocking G3, mức độ thấp, Dev FE tự quyết theo CSS) | — | — |
|
||||
| 3 | Biến thể | `product_variant_id` → thuộc tính | Text | `—` nếu sản phẩm không có biến thể | — |
|
||||
| 4 | Đơn giá | `cart_item.unit_price_snapshot` **hoặc** giá hiện tại real-time — 🔴 `OQ-029` | `#,##0 ₫` (`UICONV` §8) | N/A | — |
|
||||
| 5 | Số lượng | `cart_item.quantity` | Số nguyên | N/A (luôn ≥1 khi dòng còn tồn tại) | Xem F01 |
|
||||
|
||||
**Thứ tự hiển thị nhóm seller và `CartItem` trong mỗi nhóm:** 🔴 chưa được `BACKLOG`/SAD xác nhận. BA đề xuất: theo thời điểm thêm vào giỏ tăng dần (`added_at`/tương đương `cart_item` không có cột này trong ERD GĐ2 hiện tại — cần Tech Lech xác nhận có cột thời gian thêm hay dùng thứ tự trả về mặc định của API). Mức độ thấp, không chặn G3 — ghi nhận ở `WF_US002-003` §5 dòng #6.
|
||||
|
||||
##### 2.A.3.3 Bảng field
|
||||
|
||||
| ID | Tên field | Kiểu | Bắt buộc | Độ dài / Khoảng | Default | Nguồn giá trị | Validation | Message khi sai | BR |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| F01 | Số lượng (`quantity`) | Số nguyên (bộ đếm +/- và nhập trực tiếp) | ✅ | Tối thiểu 1 · tối đa 🔴 **chưa chốt — xem `OQ-030`** (BA không tự đặt số, vì đây là ràng buộc tồn kho, quyết định nghiệp vụ) | Giá trị `quantity` hiện có của `CartItem` khi mở màn hình | `cart_item.quantity` (giá trị hiện tại) | Số nguyên ≥ 1 | "Số lượng phải là số nguyên từ 1 trở lên." (`E-CART-0002`) | *(không có BR — xem §1.3)* |
|
||||
|
||||
🔴 **Hành vi khi giảm số lượng xuống 0 chưa chốt** (bấm nút `-` khi `quantity = 1`, hoặc nhập trực tiếp `0`): tự động mở modal xoá dòng, hay chặn không cho giảm dưới 1? — xem `OQ-028`. AC liên quan viết ở trạng thái mở, xem `AC_US-003_v1.0.md` §3 Nhóm 2.
|
||||
|
||||
##### 2.A.3.4 Sơ đồ luồng — Sửa số lượng / Xoá `CartItem`
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor U as Customer/Guest
|
||||
participant FE as Giao diện SCR-04
|
||||
participant BE as Cart & Order Service
|
||||
|
||||
U->>FE: Đổi số lượng (F01) hoặc bấm Xoá (C07, đã xác nhận modal)
|
||||
FE->>FE: Validate phía giao diện (số nguyên ≥ 1)
|
||||
FE->>BE: PATCH /v1/cart/items/{cartItemId} hoặc DELETE /v1/cart/items/{cartItemId} (khoá F01/C07 đang xử lý)
|
||||
alt Thành công
|
||||
BE-->>FE: 200 OK (dữ liệu giỏ hàng cập nhật)
|
||||
FE-->>U: Cập nhật C08/C09, mở lại F01/C07
|
||||
else CartItem không còn tồn tại trong giỏ (404/409 — bị xoá ở nơi khác)
|
||||
BE--xFE: 404 ERR_NOT_FOUND / 409 ERR_CONFLICT
|
||||
FE-->>U: Banner "Sản phẩm này đã được xoá khỏi giỏ hàng trước đó." (E-CART-0003), ẩn dòng, cập nhật C08/C09
|
||||
else Không đủ quyền sở hữu (403)
|
||||
BE--xFE: 403 ERR_FORBIDDEN_OWNERSHIP
|
||||
FE-->>U: Chuyển trang "Không có quyền" (E-CART-0005)
|
||||
else Timeout / lỗi hệ thống (5xx)
|
||||
BE--xFE: timeout / 5xx
|
||||
FE-->>U: Banner lỗi tại dòng CartItem "Không thể cập nhật giỏ hàng, thử lại." (E-CART-0006), GIỮ NGUYÊN giá trị trước đó, mở lại F01/C07
|
||||
end
|
||||
```
|
||||
|
||||
🔴 **Bắt buộc vẽ cả nhánh lỗi** (`alt`/`else`) — đã vẽ đủ 4 nhánh (thành công, xung đột, phân quyền, hệ thống).
|
||||
|
||||
**Bảng đi kèm:**
|
||||
|
||||
| Bước | Mô tả | Timeout | Thất bại thì sao | Mã lỗi | AC |
|
||||
|---|---|---|---|---|---|
|
||||
| 3 | Gửi `PATCH`/`DELETE` cart item | 🔴 chưa chốt — xem `NFR_US002-003_v1.0.md` §1, `OQ-033` | Giữ giá trị/dòng cũ, mở lại nút | `E-CART-0002`/`0003`/`0005`/`0006` (tuỳ nhánh) | `AC-US003-07`…`13` |
|
||||
|
||||
##### 2.A.3.5 Hành động trên màn hình
|
||||
|
||||
| Hành động | Điều kiện được phép | Xác nhận trước khi làm | Kết quả thành công | Kết quả thất bại | Vai trò | AC |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Xem giỏ hàng (tải trang) | — | Không | Hiển thị đủ nhóm/dòng/C08/C09 | Banner lỗi (C12) + Thử lại (C13) | Guest, Customer | `AC-US002-01`…`04` |
|
||||
| Sửa số lượng (F01) | `Cart.status = active`; giá trị mới là số nguyên ≥ 1 | Không | Cập nhật F01, C08, C09 ngay | Hiện lỗi theo bảng mã lỗi §4.1, **giữ giá trị trước đó** | Guest, Customer | `AC-US003-01`, `02`, `07`…`13` |
|
||||
| Xoá `CartItem` (C07) | `Cart.status = active` | ✅ Modal xác nhận (`UICONV` §5) | Xoá dòng, cập nhật C08/C09; nhóm rỗng thì ẩn nhóm (C02); giỏ rỗng thì chuyển trạng thái rỗng | Hiện lỗi theo bảng mã lỗi §4.1, **giữ nguyên dòng** | Guest, Customer | `AC-US003-03`…`06`, `10`, `11` |
|
||||
| Bấm "Tiến hành Checkout" (C11) | Giỏ có ≥1 `CartItem` | Không | Điều hướng `SCR-05` (ngoài phạm vi SRS này) | N/A | Guest, Customer | Ngoài phạm vi |
|
||||
|
||||
🔴 **Thất bại phải giữ nguyên dữ liệu người dùng đã nhập** — áp dụng cho F01 (giữ số lượng cũ khi lỗi) và cho C07 (dòng không biến mất khi `DELETE` lỗi).
|
||||
|
||||
##### 2.A.3.6 Trạng thái rỗng, đang tải, lỗi
|
||||
|
||||
| Trạng thái | Hiển thị gì | Text nguyên văn | Nút hành động |
|
||||
|---|---|---|---|
|
||||
| Đang tải lần đầu | Skeleton theo `UICONV` §6 | — | — |
|
||||
| **Chưa có dữ liệu nào (giỏ trống)** | Minh hoạ + câu dẫn + nút chính. 🔴 **Ngoại lệ so với mẫu chung `UICONV` §6** ("Chưa có \<đối tượng\> nào.") — dùng nguyên văn theo SAD, đã ghi ở `UICONV` §12 | "Giỏ hàng trống" (khoá `cart.empty.title`) | C10 "Tiếp tục mua sắm" |
|
||||
| Bộ lọc không khớp | N/A — giỏ hàng không có bộ lọc | — | — |
|
||||
| Lỗi tải dữ liệu (`GET /v1/cart`) | Banner C12 thay chỗ danh sách | "Không tải được dữ liệu, thử lại." (`error.load`, theo `UICONV` §6 chung) | C13 "Thử lại" |
|
||||
| Đang xử lý sửa/xoá 1 dòng | Spinner cục bộ tại dòng đó (F01/C07 disable), theo `UICONV` §6 "đang tải cục bộ" | — | — |
|
||||
|
||||
### 2.B — API tiêu thụ *(vì SCR-04 gọi trực tiếp API giỏ hàng)*
|
||||
|
||||
*Chi tiết đầy đủ ở `API_US002-003_v1.0.md`. Ở đây chỉ mô tả **góc nhìn người tiêu thụ (FE)**, không lặp lại hợp đồng.*
|
||||
|
||||
| Endpoint | Dùng để | Gọi khi nào | Nguồn contract |
|
||||
|---|---|---|---|
|
||||
| `GET /v1/cart` | Tải dữ liệu SCR-04 khi vào màn hình | Mở màn hình; bấm "Thử lại" (C13) | ✅ method/path — SAD §4.1.5 · ⚠️ schema response — BA đề xuất |
|
||||
| `PATCH /v1/cart/items/{cartItemId}` | Cập nhật F01 | Mỗi lần đổi số lượng (bấm +/-, hoặc rời field sau khi gõ tay) | ✅ method/path — SAD §4.1.5 · ⚠️ schema request/response — BA đề xuất |
|
||||
| `DELETE /v1/cart/items/{cartItemId}` | Xoá C07 (sau khi xác nhận modal) | Người dùng xác nhận trong modal | ✅ method/path — SAD §4.1.5 · ⚠️ schema response — BA đề xuất |
|
||||
|
||||
Guest dùng header `X-Guest-Session-Id` thay JWT cho cả ba endpoint (theo SAD §4.1.1, §4.1.5).
|
||||
|
||||
---
|
||||
|
||||
## PHẦN 3 — TIÊU CHÍ NGHIỆM THU
|
||||
|
||||
*Chi tiết đầy đủ, tách riêng theo US: [`AC_US-002_v1.0.md`](AC_US-002_v1.0.md) và [`AC_US-003_v1.0.md`](AC_US-003_v1.0.md).*
|
||||
|
||||
| AC | Nhóm | Tóm tắt | Field/Thành phần | BR | Test case (QA điền) |
|
||||
|---|---|---|---|---|---|
|
||||
| AC-US002-01 | Thành công | Xem giỏ hàng nhiều seller | C01–C11 | — | |
|
||||
| AC-US002-02 | Thành công | Xem giỏ hàng rỗng | C10 | BR §3 | |
|
||||
| AC-US002-03 | Lỗi hệ thống | Lỗi tải giỏ hàng | C12, C13 | — | |
|
||||
| AC-US002-04 | Lỗi hệ thống | Phân biệt "chưa có dữ liệu" ↔ "không tải được" | — | — | |
|
||||
| AC-US002-05 | Phân quyền | Guest chỉ thấy giỏ của session mình | — | RBAC §2 | |
|
||||
| AC-US002-06 | Phân quyền | Customer hết phiên bị chuyển đăng nhập | — | — | |
|
||||
| AC-US003-01…06 | Thành công | Tăng/giảm/xoá `CartItem`, xoá nhóm/giỏ, huỷ modal | F01, C07, C08, C09 | BR §3 | |
|
||||
| AC-US003-07, 08 | Validation | Số lượng không hợp lệ; giảm về 0 (🔴 mở, `OQ-028`) | F01 | — | |
|
||||
| AC-US003-09…11 | Lỗi hệ thống | 5xx cập nhật, timeout xoá, item bị xoá đồng thời | F01, C07 | — | |
|
||||
| AC-US003-12, 13 | Phân quyền | Gọi API không đúng chủ sở hữu (Customer/Guest — Guest 🔴 mở, `OQ-032`) | — | RBAC §6 | |
|
||||
|
||||
**Mỗi US phải có đủ bốn nhóm** — đã đủ cho cả hai US, trừ hai AC còn mở chờ trả lời `OQ` (ghi rõ thay vì bịa).
|
||||
|
||||
---
|
||||
|
||||
## PHẦN 4 — MÃ LỖI VÀ TEXT HIỂN THỊ
|
||||
|
||||
### 4.1 Mã lỗi
|
||||
|
||||
| Mã | Khi nào xảy ra | Thông điệp hiển thị (nguyên văn) | Hiển thị ở đâu | Người dùng làm gì tiếp | BR/AC |
|
||||
|---|---|---|---|---|---|
|
||||
| `E-CART-0002` | Nhập số lượng không phải số nguyên ≥ 1 | "Số lượng phải là số nguyên từ 1 trở lên." | Inline dưới F01 | Sửa lại giá trị | `AC-US003-07` |
|
||||
| `E-CART-0003` | `CartItem` không còn thuộc giỏ hàng (đã bị xoá ở nơi khác) khi cố sửa/xoá | "Sản phẩm này đã được xoá khỏi giỏ hàng trước đó." | Banner tại dòng (trước khi dòng biến mất) | Không cần làm gì — hệ thống tự cập nhật lại danh sách | `AC-US003-11` |
|
||||
| `E-CART-0004` | `GET /v1/cart` lỗi/timeout | "Không tải được dữ liệu, thử lại." | Banner C12 thay chỗ danh sách | Bấm "Thử lại" (C13) | `AC-US002-03` |
|
||||
| `E-CART-0005` | Gọi `PATCH`/`DELETE` cart item không thuộc quyền sở hữu (403) | "Bạn không có quyền truy cập nội dung này." | Trang trạng thái | Về trang chủ | `AC-US003-12`, `13` |
|
||||
| `E-CART-0006` | Lỗi hệ thống (5xx) hoặc timeout khi `PATCH`/`DELETE` | "Không thể cập nhật giỏ hàng, thử lại." | Banner tại dòng | Bấm lại nút +/- hoặc Xoá | `AC-US003-09`, `10` |
|
||||
|
||||
Dãy mã theo `UICONV` §10: `E-CART-` tiếp từ `0002` (`0001` đã dùng cho `BR-CART-02` ở US-004). Sau SRS này, số kế tiếp chưa dùng là `0007` — đã cập nhật `UICONV` §10.
|
||||
|
||||
### 4.2 Text màn hình
|
||||
|
||||
| Khoá | Ngữ cảnh | VI | EN | KO/ZH/JA | Giới hạn ký tự |
|
||||
|---|---|---|---|---|---|
|
||||
| `cart.title` | Header C01 | "Giỏ hàng của bạn" | "Your Cart" | 🔴 để trống — `OQ-023` | 🔴 chưa chốt — `OQ-023` |
|
||||
| `cart.itemCount` | Header C01 phụ | "({n} sản phẩm)" | "({n} items)" | 🔴 `OQ-023` | — |
|
||||
| `cart.empty.title` | Trạng thái rỗng | "Giỏ hàng trống" | "Your cart is empty" | 🔴 `OQ-023` | — |
|
||||
| `cart.empty.cta` | Nút C10 | "Tiếp tục mua sắm" | "Continue shopping" | 🔴 `OQ-023` | — |
|
||||
| `cart.error.load` | Banner C12 | "Không tải được dữ liệu, thử lại." | "Failed to load data. Please try again." | 🔴 `OQ-023` | — |
|
||||
| `btn.retry` | Nút C13 | "Thử lại" | "Try again" | 🔴 `OQ-023` | — |
|
||||
| `btn.delete` | Nút C07 | "Xoá" | "Remove" | 🔴 `OQ-023` | — |
|
||||
| `cart.delete.confirm.title` | Modal xác nhận xoá | "Xoá sản phẩm khỏi giỏ hàng?" | "Remove item from cart?" | 🔴 `OQ-023` | — |
|
||||
| `cart.delete.confirm.body` | Modal xác nhận xoá | "Sản phẩm sẽ được xoá khỏi giỏ hàng của bạn." | "This item will be removed from your cart." | 🔴 `OQ-023` | — |
|
||||
| `btn.checkout` | Nút C11 | "Tiến hành Checkout" | "Proceed to Checkout" | 🔴 `OQ-023` | — |
|
||||
| `E-CART-0002.msg` | Inline F01 | "Số lượng phải là số nguyên từ 1 trở lên." | "Quantity must be a whole number of 1 or more." | 🔴 `OQ-023` | — |
|
||||
| `E-CART-0003.msg` | Banner dòng | "Sản phẩm này đã được xoá khỏi giỏ hàng trước đó." | "This item has already been removed from your cart." | 🔴 `OQ-023` | — |
|
||||
| `E-CART-0005.msg` | Trang trạng thái | "Bạn không có quyền truy cập nội dung này." | "You don't have permission to access this content." | 🔴 `OQ-023` | — |
|
||||
| `E-CART-0006.msg` | Banner dòng | "Không thể cập nhật giỏ hàng, thử lại." | "Couldn't update your cart. Please try again." | 🔴 `OQ-023` | — |
|
||||
|
||||
*Bản dịch KO/ZH/JA để trống, ghi `OQ-023` (đã mở từ `UICONV`) — không dịch máy, theo ghi chú người duyệt.*
|
||||
|
||||
### 4.3 Định dạng hiển thị
|
||||
|
||||
*Tham chiếu `UICONV` §8, không có ngoại lệ cho SCR-04.*
|
||||
|
||||
| Loại dữ liệu | Định dạng | Ví dụ | Ghi chú |
|
||||
|---|---|---|---|
|
||||
| Số tiền | `#,##0 ₫` | 1.234.567 ₫ | Theo `UICONV` §8, không làm tròn thêm (VND) |
|
||||
| Số lượng | Số nguyên, không định dạng | 3 | — |
|
||||
| Rỗng/null | `—` | | Theo `UICONV` §8 |
|
||||
|
||||
---
|
||||
|
||||
## PHẦN 5 — PHI CHỨC NĂNG
|
||||
|
||||
*Chi tiết ở [`NFR_US002-003_v1.0.md`](NFR_US002-003_v1.0.md). Ở đây chỉ nêu áp dụng cho US-002/US-003.*
|
||||
|
||||
| ID | Nhóm | Yêu cầu (có số đo) | Điều kiện đo | Cách verify |
|
||||
|---|---|---|---|---|
|
||||
| NFR-PERF-01 | Hiệu năng | 🔴 Ngưỡng thời gian tải/cập nhật giỏ hàng chưa chốt riêng — xem `NFR` §1, `OQ-033` | — | — |
|
||||
| NFR-SEC-01 | Bảo mật | Mọi `PATCH`/`DELETE` cart item kiểm tra quyền sở hữu (chống IDOR), trả `403 ERR_FORBIDDEN_OWNERSHIP` khi không khớp | Theo SAD §4.1.1 | Test gọi thẳng API với token/session khác chủ |
|
||||
|
||||
---
|
||||
|
||||
## PHẦN 6 — DỮ LIỆU VÀ TÍCH HỢP
|
||||
|
||||
### 6.1 API sử dụng
|
||||
|
||||
*Chi tiết ở [`API_US002-003_v1.0.md`](API_US002-003_v1.0.md).*
|
||||
|
||||
| # | Mục đích | Method | Endpoint | Nguồn contract |
|
||||
|---|---|---|---|---|
|
||||
| 1 | Tải giỏ hàng | GET | `/v1/cart` | ✅ method/path BE cung cấp · ⚠️ schema BA đề xuất |
|
||||
| 2 | Sửa số lượng | PATCH | `/v1/cart/items/{cartItemId}` | ✅ method/path BE cung cấp · ⚠️ schema BA đề xuất |
|
||||
| 3 | Xoá sản phẩm | DELETE | `/v1/cart/items/{cartItemId}` | ✅ method/path BE cung cấp · ⚠️ schema BA đề xuất |
|
||||
|
||||
### 6.2 Tác động dữ liệu
|
||||
|
||||
*Tham chiếu `IMPACT_CartCheckout_v1.0.md`.* Không có tác động dữ liệu cũ (`greenfield`). Điểm chạm nội bộ duy nhất liên quan trực tiếp US-002/US-003: module Catalog & Inventory cung cấp tên/ảnh/biến thể sản phẩm (`product_variant_id`) hiển thị ở C03–C06 — độ trễ đồng bộ giá/tồn kho vẫn là `ASM-06`/`OQ-009` (chưa xác minh), ảnh hưởng trực tiếp `OQ-029`/`OQ-030` ở SRS này.
|
||||
|
||||
---
|
||||
|
||||
## PHẦN 7 — BÀN GIAO CHO DEV
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Trạng thái** | 🟡 Chưa sẵn sàng — còn 5 `OQ` chặn G3 (`OQ-028`…`OQ-030`, `OQ-032`, `OQ-033`) |
|
||||
| **Tài liệu cần đọc kèm** | `UICONV_e-commerce_v1.0.md` v1.1 → `WF_US002-003_v1.0.md`+`.html` → `AC_US-002_v1.0.md` → `AC_US-003_v1.0.md` → `NFR_US002-003_v1.0.md` → `API_US002-003_v1.0.md` |
|
||||
| **Quyết định đã chốt, không phải mặc định** | Modal xác nhận bắt buộc khi xoá `CartItem` (áp UICONV §5 mặc định, không có ngoại lệ) · Cart không tự chuyển `abandoned` khi rỗng · Checkout (C11) chỉ disable khi 0 `CartItem`, KHÔNG có điều kiện "phải chọn dòng" trong SRS này |
|
||||
| **Điểm còn mở** | `OQ-028` (quantity=0) · `OQ-029` (nguồn giá) · `OQ-030` (giới hạn trên số lượng/tồn kho) · `OQ-031` (checkout một phần giỏ — chủ yếu ảnh hưởng US-004) · `OQ-032` (ownership Guest) · `OQ-033` (ngưỡng hiệu năng) |
|
||||
| **Không được sao chép từ đâu** | Không có màn hình cũ (`greenfield`) — không áp dụng |
|
||||
|
||||
---
|
||||
|
||||
## PHẦN 8 — OPEN QUESTIONS
|
||||
|
||||
| ID | Câu hỏi | Hỏi ai | Từ ngày | Chặn gì | 🔴 chặn G3? | Phương án BA đề xuất |
|
||||
|---|---|---|---|---|---|---|
|
||||
| OQ-028 | Sửa số lượng `CartItem` về 0 (nhập tay hoặc bấm `-` khi đang ở 1) → hệ thống tự mở modal xoá dòng, hay chặn không cho giảm dưới 1? | PO | 2026-09-08 | F01, `AC-US003-08` | 🔴 | Chặn không cho `-` giảm dưới 1 (nút `-` disable ở `quantity=1`); xoá dòng chỉ qua nút "Xoá" (C07) riêng — tách rõ hai hành động, tránh xoá nhầm khi bấm liên tiếp nút `-` |
|
||||
| OQ-029 | Giá hiển thị ở SCR-04 (C06) là giá snapshot lúc thêm giỏ (`cart_item.unit_price_snapshot`) hay giá hiện tại real-time từ Catalog? | PO, Tech Lead | 2026-09-08 | C06, `WF_US002-003` §5 #2/#3 | 🔴 | Dùng `unit_price_snapshot` để hiển thị nhất quán với số tiền đã "chốt" khi thêm giỏ, đối chiếu lại giá thật ở bước checkout (US-004) — tránh giỏ hàng nhảy số liên tục do giá đổi ngoài ý muốn khách |
|
||||
| OQ-030 | Sửa số lượng ở SCR-04 (US-003) có kiểm tra giới hạn trên theo tồn kho hiện tại ngay lúc sửa không, hay chỉ kiểm tra ở checkout (US-004) như `BACKLOG` đã khoanh phạm vi? | PO, Tech Lead | 2026-09-08 | F01, C06 (badge hết hàng/giá đổi), `WF_US002-003` §5 #2/#4 | 🔴 | Giữ đúng khoanh vùng của `BACKLOG`: không kiểm tra tồn kho ở màn Giỏ hàng, chỉ kiểm tra ở US-004 — đơn giản hoá US-002/US-003, tránh phụ thuộc real-time vào Catalog ở màn hình tần suất truy cập cao |
|
||||
| OQ-031 | Nút "Tiến hành Checkout" áp dụng cho toàn bộ giỏ hàng, hay chỉ cho các dòng được tick chọn (theo checkbox mà SAD SCR-04 mô tả nhưng không `US` nào trong `BACKLOG` định nghĩa)? | PO | 2026-09-08 | C11, `WF_US002-003` §5 #1, ảnh hưởng US-004 | 🔴 | Áp dụng cho toàn bộ giỏ hàng ở MVP (không có tính năng chọn từng phần) — khớp đúng phạm vi `BACKLOG` hiện tại, tính năng chọn từng phần đưa vào backlog cho phiên bản sau nếu PO thấy cần |
|
||||
| OQ-032 | Với Guest, cơ chế kiểm tra quyền sở hữu `CartItem` khi gọi `PATCH`/`DELETE /v1/cart/items/{cartItemId}` có tương đương cơ chế ownership theo JWT (`403 ERR_FORBIDDEN_OWNERSHIP`) không? SAD §4.1.1/§4.2 chỉ mô tả rõ cho `customerId`/`sellerId` trong JWT | Tech Lead | 2026-09-08 | `AC-US003-13`, `E-CART-0005` | 🔴 | Áp dụng cùng cơ chế: đối chiếu `cart_id` của `cartItemId` với `session_id` trong `X-Guest-Session-Id`, không khớp → `403 ERR_FORBIDDEN_OWNERSHIP` |
|
||||
| OQ-033 | Ngưỡng thời gian phản hồi (p95) cho `GET /v1/cart` và cho `PATCH`/`DELETE /v1/cart/items` là bao nhiêu giây? SAD NFR-01 chỉ có ngưỡng cho danh mục/tìm kiếm (<2s) và checkout (<3s) | PO, Tech Lead | 2026-09-08 | `NFR_US002-003` §1 | 🔴 | Áp dụng cùng ngưỡng với danh mục/tìm kiếm (≤2s p95) cho `GET /v1/cart` vì cùng là thao tác đọc; ≤1s p95 cho `PATCH`/`DELETE` (thao tác ghi đơn giản, 1 dòng) |
|
||||
|
||||
---
|
||||
|
||||
## Tự chấm
|
||||
|
||||
**Gate G3**
|
||||
|
||||
| # | Tiêu chí | ☐/✅ | Ghi chú |
|
||||
|---|---|---|---|
|
||||
| 1 | Đủ mọi mục bắt buộc (mục N/A có ghi lý do) | ✅ | Mọi mục N/A (§2.A.3.2 phân trang, §4.2/4.3 phần không áp dụng) đều ghi lý do |
|
||||
| 2 | Tiêu chí riêng của biến thể PART 2 đã nạp | ☐ | Bảng field F01 còn 1 ô để trống có lý do rõ (giới hạn trên — `OQ-030`); bảng thành phần đủ 8 cột; hai trạng thái rỗng khác nhau (đã có) |
|
||||
| 3 | Mỗi US có AC đủ 4 nhóm, có luồng lỗi | ✅ (có 2 AC còn mở chờ OQ) | Xem `AC_US-002`, `AC_US-003` |
|
||||
| 4 | Bảng mã lỗi đầy đủ, mỗi mã có thông điệp | ✅ | 5 mã, đủ thông điệp VI+EN |
|
||||
| 5 | NFR có số đo + cách verify | ☐ | Ngưỡng hiệu năng chưa chốt — `OQ-033` |
|
||||
| 6 | API contract có, ghi rõ nguồn | ✅ | Đánh dấu rõ ✅/⚠️ theo từng phần |
|
||||
| 7 | QA xác nhận mọi AC test được | ☐ | Chờ QA — chưa có QA thật tham gia lần chạy này |
|
||||
| 8 | Không còn OQ mở ảnh hưởng hành vi | ☐ | 5/6 OQ mới đánh dấu 🔴 chặn G3 |
|
||||
| 9 | Không còn `TBD` trong bảng đặc tả PART 2 và bảng mã lỗi | ✅ | Không dùng chữ "TBD" — mọi chỗ thiếu dùng 🔴 + `OQ-nnn` cụ thể |
|
||||
| 10 | Đã áp đúng bảng "Bớt ở light"/"Thêm ở strict" theo RIGOR | ✅ | `RIGOR = standard` — áp đúng checklist §2, không bớt/thêm |
|
||||
| 11 | *(screen)* `WF` có cho mọi `SCR`, tập `C-id` khớp hai chiều, §5 không còn lệch Tồn tại/Hành vi chưa quyết | ☐ | Xem `WF_US002-003_v1.0.md` §5 — còn 4 dòng chưa quyết |
|
||||
| 12 | *(screen)* Quy ước chung tham chiếu `UICONV`; chỗ khác có dòng ở `UICONV` §12 | ✅ | 2 dòng ngoại lệ đã thêm vào `UICONV` §12 (text rỗng SCR-04, auto-save không có nút Lưu) |
|
||||
|
||||
**Quét bắt buộc trước khi nộp:**
|
||||
|
||||
```
|
||||
grep -niE "nhanh|mượt|thân thiện|v\.v|phù hợp|tương ứng|nên |có thể " SRS_US002-003_v1.0.md
|
||||
→ chạy thật: 5 dòng khớp ban đầu (§1.5 dòng 1 và 7, §2.A.3.5 hai dòng "tương ứng", §8 OQ-028
|
||||
"bấm nhanh") — đã sửa lại câu chữ cho cả 5 dòng, chạy lại lần hai: 0 dòng khớp
|
||||
grep -n "TBD\|TODO\|???" SRS_US002-003_v1.0.md
|
||||
→ 0 dòng khớp (dùng 🔴 + OQ-nnn thay vì TBD)
|
||||
```
|
||||
|
||||
**Quy tắc viết W1–W13**
|
||||
|
||||
| W1 | W2 | W3 | W4 | W5 | W6 | W7 | W8 | W9 | W10 | W11 | W12 | W13 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| ✅ | ✅ | ✅ (con số thiếu ⇒ `OQ`, không bịa) | ✅ (sequence đủ 4 nhánh) | ✅ (§4.2) | ✅ (§1.4 tham chiếu BR §3) | ✅ (§2.A.3.1 cột riêng) | ✅ (mọi `OQ` ghi rõ đề xuất, không quyết thay) | ✅ (mọi AC/field ghi US/BR) | ✅ (§1.1 cho PO) | ✅ (có câu quy ước đầu tài liệu) | ✅ (§1.5) | ✅ (2 sơ đồ mermaid, đủ bảng đi kèm) |
|
||||
239
ba-output/e-commerce/03-specification/WF_US002-003_v1.0.html
Normal file
239
ba-output/e-commerce/03-specification/WF_US002-003_v1.0.html
Normal file
@@ -0,0 +1,239 @@
|
||||
<!--
|
||||
WF_US002-003_v1.0.html — wireframe low-fi tự chứa, sinh bởi skill ba-3-specification (activity wf).
|
||||
Nguồn hành vi: SRS_US002-003_v1.0.md · Nguồn bố cục: e-commerce/docs/sections/07-giao-dien.md §7.1.1 SCR-04 (chế độ 🎨)
|
||||
Nguồn quy ước: UICONV_e-commerce_v1.0.md v1.1
|
||||
|
||||
Giữ nguyên <style>/<script> của template gốc (ba-3-specification/templates/wireframe.html) để mọi WF
|
||||
trong dự án trông và thao tác giống nhau. Chỉ thay nội dung trong <main>.
|
||||
|
||||
Thành phần chỉ có ở prototype (SAD §7.1.1) mà SRS chưa có (checkbox chọn dòng, badge "Sản phẩm đã hết
|
||||
hàng", badge "Giá đã thay đổi") được vẽ trong khối .proto-only, KHÔNG có data-c — xem WF §5 (#1,#2,#3).
|
||||
-->
|
||||
<!doctype html>
|
||||
<html lang="vi">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>WF — US-002, US-003 — Giỏ hàng (SCR-04)</title>
|
||||
<style>
|
||||
:root{--ink:#222;--mute:#777;--line:#bbb;--fill:#eee;--fill2:#f7f7f7;--accent:#3b6ea5;--danger:#b23a3a;--bg:#fafafa}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;font:14px/1.45 system-ui,Segoe UI,Roboto,sans-serif;color:var(--ink);background:var(--bg)}
|
||||
header.toolbar{position:sticky;top:0;z-index:9;background:#fff;border-bottom:1px solid var(--line);padding:8px 16px;display:flex;gap:16px;align-items:center;flex-wrap:wrap}
|
||||
header.toolbar h1{font-size:15px;margin:0 16px 0 0}
|
||||
header.toolbar label{font-size:12px;color:var(--mute)}
|
||||
header.toolbar select{font:inherit}
|
||||
.legend{font-size:12px;color:var(--mute);margin-left:auto}
|
||||
main{padding:24px 16px;display:grid;gap:40px;justify-items:center}
|
||||
.screen{width:100%;max-width:1200px;background:#fff;border:1px solid var(--line);border-radius:6px;overflow:hidden}
|
||||
.screen>h2{margin:0;padding:8px 12px;font-size:13px;font-weight:600;background:var(--fill2);border-bottom:1px dashed var(--line);color:var(--mute)}
|
||||
.screen>h2 small{font-weight:400;margin-left:8px}
|
||||
.body{padding:12px;display:grid;gap:12px}
|
||||
.zone{border:1px dashed var(--line);border-radius:4px;padding:10px;position:relative;background:#fff;min-height:44px}
|
||||
.zone::before{content:attr(data-zone) " · " attr(data-name);position:absolute;top:-8px;left:8px;background:#fff;padding:0 4px;font-size:10px;color:var(--mute);letter-spacing:.02em}
|
||||
.row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
||||
.row.right{justify-content:flex-end}
|
||||
.row.between{justify-content:space-between}
|
||||
[data-c]{position:relative}
|
||||
[data-c]::after{content:attr(data-c);position:absolute;top:-9px;right:-4px;font-size:9px;background:var(--ink);color:#fff;padding:0 4px;border-radius:3px;line-height:14px;opacity:.85}
|
||||
.label{font-weight:600;font-size:16px}
|
||||
.btn{display:inline-block;padding:6px 14px;border:1px solid var(--ink);border-radius:4px;background:#fff;cursor:default;white-space:nowrap}
|
||||
.btn-primary{background:var(--accent);border-color:var(--accent);color:#fff}
|
||||
.btn-secondary{background:#fff}
|
||||
.btn-danger{border-color:var(--danger);color:var(--danger)}
|
||||
.btn[aria-disabled="true"]{opacity:.45;border-style:dashed}
|
||||
.link{color:var(--accent);text-decoration:underline}
|
||||
.input,.select{display:inline-block;min-width:160px;padding:6px 8px;border:1px solid var(--line);border-radius:4px;background:#fff;color:var(--mute)}
|
||||
.select::after{content:" ▾";float:right}
|
||||
.field{display:grid;gap:4px}
|
||||
.field>span{font-size:12px}
|
||||
.field>span .req{color:var(--danger)}
|
||||
.hint{font-size:12px;color:var(--mute)}
|
||||
.table{width:100%;border-collapse:collapse}
|
||||
.table th,.table td{border:1px solid var(--line);padding:6px 8px;text-align:left;font-size:13px}
|
||||
.table th{background:var(--fill2)}
|
||||
.card{border:1px solid var(--line);border-radius:4px;padding:10px;background:#fff}
|
||||
.badge{display:inline-block;padding:0 8px;border:1px solid var(--line);border-radius:10px;font-size:12px;background:var(--fill2)}
|
||||
.img{background:repeating-linear-gradient(45deg,var(--fill),var(--fill) 6px,var(--fill2) 6px,var(--fill2) 12px);border:1px solid var(--line);min-height:80px;display:grid;place-items:center;color:var(--mute);font-size:12px}
|
||||
.skeleton{height:14px;background:linear-gradient(90deg,var(--fill),var(--fill2),var(--fill));border-radius:3px;margin:8px 0}
|
||||
.state{padding:24px;text-align:center;color:var(--mute);border:1px dashed var(--line);border-radius:4px;display:grid;gap:10px;justify-items:center}
|
||||
.state.error{border-color:var(--danger);color:var(--danger)}
|
||||
.state .illus{width:80px;height:60px}
|
||||
.spinner{display:inline-block;width:14px;height:14px;border:2px solid #fff;border-top-color:transparent;border-radius:50%;vertical-align:-2px;margin-right:6px}
|
||||
.modal{max-width:520px;margin:0 auto;border:1px solid var(--ink);border-radius:6px;background:#fff;box-shadow:0 0 0 2000px rgba(0,0,0,.25)}
|
||||
.modal .body{padding:16px}
|
||||
[data-state]{display:none}
|
||||
[data-state="default"]{display:block}
|
||||
body[data-view="mobile"] .screen{max-width:390px}
|
||||
body[data-view="mobile"] .m-hide{display:none!important}
|
||||
body[data-view="mobile"] .m-stack{flex-direction:column;align-items:stretch}
|
||||
body[data-view="desktop"] .d-hide{display:none!important}
|
||||
body[data-view="mobile"] .table{display:none}
|
||||
body[data-view="mobile"] .cards{display:grid!important;gap:8px}
|
||||
.cards{display:none}
|
||||
footer.note{padding:16px;font-size:12px;color:var(--mute);text-align:center}
|
||||
.proto-only{border:1px dashed var(--danger);border-radius:4px;padding:6px;background:#fdf2f2}
|
||||
.proto-only .tag{display:inline-block;font-size:10px;color:var(--danger);border:1px solid var(--danger);border-radius:3px;padding:0 4px;margin-bottom:4px}
|
||||
</style>
|
||||
</head>
|
||||
<body data-view="desktop">
|
||||
|
||||
<header class="toolbar">
|
||||
<h1>WF — US-002, US-003 · Giỏ hàng (SCR-04)</h1>
|
||||
<label>Khung nhìn
|
||||
<select id="view"><option value="desktop">Desktop ≥1024</option><option value="mobile">Mobile <768</option></select>
|
||||
</label>
|
||||
<label>Trạng thái
|
||||
<select id="state">
|
||||
<option value="default">Mặc định (có dữ liệu)</option>
|
||||
<option value="loading">Đang tải</option>
|
||||
<option value="empty-none">Rỗng — Giỏ hàng trống</option>
|
||||
<option value="error">Lỗi tải dữ liệu</option>
|
||||
</select>
|
||||
</label>
|
||||
<span class="legend">Nhãn đen = C-id/F-id trong SRS §2.A.3.1 · khung đứt xám = vùng Zn (WF §3.1.1) · khung đứt đỏ = chỉ có ở prototype, chưa có trong SRS (xem WF §5) · hành vi xem SRS, không suy từ hình</span>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<section class="screen" id="SCR-04">
|
||||
<h2>SCR-04 · Giỏ hàng <small>US-002 (xem) + US-003 (sửa/xoá) · vào từ SCR-01 (icon giỏ hàng) · vai trò: ROLE-01 Guest, ROLE-02 Customer</small></h2>
|
||||
<div class="body">
|
||||
|
||||
<div class="zone" data-zone="Z1" data-name="Header màn hình">
|
||||
<span class="label" data-c="C01" data-key="cart.title">Giỏ hàng của bạn (5 sản phẩm)</span>
|
||||
</div>
|
||||
|
||||
<div class="row m-stack" style="align-items:flex-start">
|
||||
|
||||
<!-- Z2 — danh sách nhóm seller -->
|
||||
<div class="zone" data-zone="Z2" data-name="Danh sách nhóm seller" style="flex:2;min-width:0">
|
||||
|
||||
<div data-state="default">
|
||||
|
||||
<!-- Nhóm seller "Shop A" -->
|
||||
<div class="card" style="margin-bottom:10px">
|
||||
<div class="row between" data-c="C02"><b>Shop A</b></div>
|
||||
|
||||
<div class="row between" style="margin-top:8px">
|
||||
<div class="row">
|
||||
<div class="img" data-c="C03" style="width:64px;height:64px">ảnh</div>
|
||||
<div>
|
||||
<div data-c="C04">Áo thun basic</div>
|
||||
<div class="hint" data-c="C05">Size M, Đỏ</div>
|
||||
<div class="hint" data-c="C06" data-key="cart.item.price">100.000 ₫</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="proto-only d-hide" style="display:inline-block"><span class="tag">chỉ ở prototype</span><br>☐ chọn dòng</div>
|
||||
<span class="row" data-c="F01">
|
||||
<span class="btn btn-secondary">−</span><span class="input" style="min-width:40px;text-align:center;padding:6px">2</span><span class="btn btn-secondary">+</span>
|
||||
</span>
|
||||
<span class="btn btn-danger" data-c="C07" data-key="btn.delete">Xoá</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row between" style="margin-top:8px">
|
||||
<div class="row">
|
||||
<div class="img" style="width:64px;height:64px">ảnh</div>
|
||||
<div>
|
||||
<div>Quần jean slimfit</div>
|
||||
<div class="hint">Size 30</div>
|
||||
<div class="hint">50.000 ₫</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="row"><span class="btn btn-secondary">−</span><span class="input" style="min-width:40px;text-align:center;padding:6px">1</span><span class="btn btn-secondary">+</span></span>
|
||||
<span class="btn btn-danger" data-key="btn.delete">Xoá</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row right" style="margin-top:8px;border-top:1px dashed var(--line);padding-top:6px">
|
||||
<span data-c="C08">Tạm tính: 250.000 ₫</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nhóm seller "Shop B" -->
|
||||
<div class="card">
|
||||
<div class="row between"><b>Shop B</b></div>
|
||||
<div class="row between" style="margin-top:8px">
|
||||
<div class="row">
|
||||
<div class="img" style="width:64px;height:64px">ảnh</div>
|
||||
<div>
|
||||
<div>Balo du lịch</div>
|
||||
<div class="hint">Đen</div>
|
||||
<div class="hint">200.000 ₫</div>
|
||||
<div class="proto-only" style="margin-top:4px"><span class="tag">chỉ ở prototype — WF §5 #2</span> badge "Sản phẩm đã hết hàng"</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="row"><span class="btn btn-secondary" aria-disabled="true">−</span><span class="input" style="min-width:40px;text-align:center;padding:6px">1</span><span class="btn btn-secondary" aria-disabled="true">+</span></span>
|
||||
<span class="btn btn-danger" data-key="btn.delete">Xoá</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row right" style="margin-top:8px;border-top:1px dashed var(--line);padding-top:6px">
|
||||
<span>Tạm tính: 200.000 ₫</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div data-state="loading"><div class="skeleton"></div><div class="skeleton" style="width:85%"></div><div class="skeleton" style="width:92%"></div></div>
|
||||
|
||||
<div data-state="empty-none">
|
||||
<div class="state">
|
||||
<div class="img illus">minh hoạ</div>
|
||||
<span data-key="cart.empty.title">Giỏ hàng trống</span>
|
||||
<span class="btn btn-primary" data-c="C10" data-key="cart.empty.cta">Tiếp tục mua sắm</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-state="error">
|
||||
<div class="state error" data-c="C12">
|
||||
<span data-key="cart.error.load">Không tải được dữ liệu, thử lại.</span>
|
||||
<span class="btn btn-secondary" data-c="C13" data-key="btn.retry">Thử lại</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Z3 — tổng kết -->
|
||||
<div class="zone m-hide" data-zone="Z3" data-name="Tổng kết (sidebar desktop / footer mobile)" style="flex:1;min-width:220px" data-state-visible-in="default">
|
||||
<div class="proto-only" style="margin-bottom:8px"><span class="tag">chỉ ở prototype — WF §5 #1</span> "Tổng số lượng đã chọn" (phụ thuộc checkbox chọn dòng, chưa có trong SRS)</div>
|
||||
<div class="label" data-c="C09">Tổng cộng: 585.000 ₫</div>
|
||||
<div class="btn btn-primary" data-c="C11" data-key="btn.checkout" style="width:100%;text-align:center;margin-top:10px">Tiến hành Checkout</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Footer tổng kết cố định đáy — chỉ hiện ở mobile -->
|
||||
<div class="zone d-hide" data-zone="Z3" data-name="Tổng kết (footer mobile)" style="position:sticky;bottom:0;background:#fff">
|
||||
<div class="row between">
|
||||
<span class="label">Tổng cộng: 585.000 ₫</span>
|
||||
<span class="btn btn-primary" style="text-align:center">Tiến hành Checkout</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="note">Wireframe low-fi · bố cục và trạng thái · không phải thiết kế visual · nguồn hành vi: SRS_US002-003_v1.0.md · nguồn bố cục: e-commerce/docs/sections/07-giao-dien.md §7.1.1 (chế độ 🎨) · nguồn quy ước: UICONV_e-commerce_v1.0.md</footer>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
var view=document.getElementById('view'),state=document.getElementById('state');
|
||||
function apply(){
|
||||
document.body.setAttribute('data-view',view.value);
|
||||
var s=state.value;
|
||||
document.querySelectorAll('[data-state]').forEach(function(el){
|
||||
var on=el.getAttribute('data-state')===s;
|
||||
el.style.display=on?(el.tagName==='SPAN'?'inline':'block'):'none';
|
||||
});
|
||||
}
|
||||
view.addEventListener('change',apply);state.addEventListener('change',apply);apply();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
217
ba-output/e-commerce/03-specification/WF_US002-003_v1.0.md
Normal file
217
ba-output/e-commerce/03-specification/WF_US002-003_v1.0.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# WF — Wireframe & bố cục — US-002, US-003 (SCR-04 Giỏ hàng)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Version** | 1.0 |
|
||||
| **Date** | 2026-09-08 |
|
||||
| **Author** | BA (qua skill ba-3-specification, activity `wf`) |
|
||||
| **Status** | 🟡 Draft |
|
||||
| **Approved by** | Designer: — · PO: — *(dự án không có Designer riêng — PO ký thay, `DEC-01`)* |
|
||||
| **Source** | `SRS_US002-003_v1.0.md` v1.0 · `UICONV_e-commerce_v1.0.md` v1.1 · prototype: `e-commerce/docs/sections/07-giao-dien.md` §7.1.1 SCR-04 (status: approved, version 1) |
|
||||
| **Scope** | US-002, US-003 · `SCR-04` |
|
||||
| **Nguồn bố cục** | 🎨 **Prototype tham chiếu** — `e-commerce/docs/sections/07-giao-dien.md` §7.1.1 SCR-04, coi là nguồn sự thật về bố cục dạng văn bản (`DEC-01` v1.2) |
|
||||
| **Tệp kèm** | `WF_US002-003_v1.0.html` |
|
||||
|
||||
## Change Log
|
||||
|
||||
| Version | Date | Người sửa | Thay đổi | CR |
|
||||
|---|---|---|---|---|
|
||||
| 1.0 | 2026-09-08 | BA (qua skill ba-3-specification, activity `wf`) | Bản đầu — bố cục `SCR-04` chế độ 🎨, 6 dòng lệch ở §5 (4 chưa quyết, 1 đã xử lý theo `UICONV` mặc định, 1 mức thấp không chặn) | — |
|
||||
|
||||
> **Quy ước đọc** *(quy tắc W11)*. Bảng thành phần trong `SRS` §2.A.3.1 quyết định **một phần tử
|
||||
> có tồn tại không** và **hành xử thế nào**. Tài liệu này và tệp HTML kèm theo quyết định **nó
|
||||
> nằm ở đâu, thứ tự nào, to bằng nào, trông ra sao ở từng trạng thái**. Mâu thuẫn ⇒ SRS thắng về
|
||||
> tồn tại/hành vi, WF thắng về bố cục — mâu thuẫn đó được ghi ở §5, không im lặng.
|
||||
|
||||
---
|
||||
|
||||
## 0. Nguồn bố cục và độ tin cậy
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Prototype tham chiếu** | `e-commerce/docs/sections/07-giao-dien.md` §7.1.1 SCR-04 — mô tả văn bản có cấu trúc (mục đích, persona, FR phục vụ, bố cục, 3 trạng thái, validation), `status: approved`, `version: 1` |
|
||||
| **Định dạng** | Mô tả văn bản có cấu trúc (không phải Figma/ảnh) — SAD §7.0 xác nhận "không có brand guideline cố định" |
|
||||
| **Mức phủ** | 1/1 màn hình của US-002/US-003 (`SCR-04`) có mô tả trong prototype |
|
||||
| **Design system** | Chưa chọn — `OQ-022` (đã mở ở `UICONV`) |
|
||||
| **Độ tin cậy** | 🎨 SAD §7 đã `approved` ở cấp SA/dự án ⇒ WF là **bản ghi lại** bố cục của SCR-04 và đối chiếu với `SRS` |
|
||||
|
||||
## 1. Bản đồ prototype ↔ màn hình
|
||||
|
||||
| SCR | Tên màn hình | Có trong prototype | Frame / trang / mục trong prototype | Mức khớp với SRS | Ghi chú |
|
||||
|---|---|---|---|---|---|
|
||||
| SCR-04 | Giỏ hàng | ✅ | SAD §7.1.1 "SCR-04 — Giỏ hàng" | 🔴 khác tồn tại/hành vi | 4 điểm lệch — xem §5 (#1–#4) |
|
||||
|
||||
## 2. Khung bố cục chung
|
||||
|
||||
*Lấy từ `UICONV` §1. Chỉ ghi phần khác/bổ sung cho SCR-04.*
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Grid** | 🔴 Chưa chốt — `UICONV` §1, `OQ-018` (áp dụng chung, không riêng SCR-04) |
|
||||
| **Vùng cố định** | Header toàn site kế thừa `SCR-01` (logo, tìm kiếm, `LanguageSwitcher`, `CurrencyToggle`, icon giỏ hàng, icon tài khoản) — theo `UICONV` §1. SCR-04 không có sidebar/menu cố định riêng |
|
||||
|
||||
| Breakpoint | Khoảng (px) | Bố cục | Nguồn |
|
||||
|---|---|---|---|
|
||||
| Desktop | ≥ 1024 *(ngưỡng chính xác — `OQ-018`)* | Danh sách nhóm seller full-width, sidebar tổng kết bên phải | `UICONV` §1 + SAD §7.1.1 (mô tả "Sidebar/footer tổng kết") |
|
||||
| Tablet | 768–1023 | 🔴 Chưa mô tả riêng trong SAD — BA đề xuất giữ như desktop nhưng tổng kết chuyển xuống dưới danh sách (footer thay vì sidebar) | BA đề xuất, chưa xác nhận — mức thấp, không chặn G3 |
|
||||
| Mobile | < 768 | Danh sách nhóm seller full-width, tổng kết chuyển thành **footer cố định đáy màn hình** ("Sidebar/footer tổng kết" — SAD dùng cả hai từ, BA diễn giải: sidebar ở desktop, footer ở mobile) | SAD §7.1.1 + BA diễn giải |
|
||||
|
||||
---
|
||||
|
||||
## 3. Bố cục từng màn hình
|
||||
|
||||
### 3.1 SCR-04 — Giỏ hàng
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Loại** | Chi tiết (không phân trang) |
|
||||
| **Prototype** | SAD §7.1.1 SCR-04 |
|
||||
| **HTML** | `WF_US002-003_v1.0.html#SCR-04` |
|
||||
| **Thành phần (SRS §2.A.3.1)** | C01–C13, F01 — khớp đủ (xem tự kiểm §"Tự chấm") |
|
||||
|
||||
#### 3.1.1 Bảng vùng
|
||||
|
||||
| Vùng | Tên | Vị trí desktop | Vị trí mobile | Chứa thành phần | Chiều rộng | Ưu tiên hiển thị |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Z1 | Header màn hình | Hàng 1, full | Hàng 1, full | C01 | 100% | 1 |
|
||||
| Z2 | Danh sách nhóm seller | Hàng 2, cột trái (2/3) | Hàng 2, full (trên footer tổng kết) | C02, C03, C04, C05, C06, F01, C07 (lặp theo nhóm/dòng) | 66% desktop / 100% mobile | 1 |
|
||||
| Z3 | Tổng kết (sidebar desktop / footer mobile) | Hàng 2, cột phải (1/3), sticky | Cố định đáy màn hình | C09, C11 (C08 nằm trong từng nhóm ở Z2, không lặp lại ở Z3) | 33% desktop / 100% mobile | 1 |
|
||||
| Z4 | Trạng thái đặc biệt (rỗng/lỗi) | Thay chỗ Z2 khi kích hoạt | Thay chỗ Z2 khi kích hoạt | C10, C12, C13 | Bằng Z2 | 1 |
|
||||
|
||||
*Ưu tiên hiển thị: 1 = luôn hiện.*
|
||||
|
||||
**Ghi chú vị trí C08 (tạm tính nhóm):** theo mô tả SAD "mỗi nhóm = 1 seller... tạm tính theo VND" nằm cuối mỗi khối nhóm trong Z2 (không phải trong Z3) — khác với cách trình bày "tổng kết ở sidebar" chỉ áp dụng cho **tổng toàn giỏ** (C09), không áp dụng cho tạm tính từng nhóm (C08).
|
||||
|
||||
#### 3.1.2 Vị trí thành phần
|
||||
|
||||
*Tập `C-id`/`F-id` phải khớp hai chiều với `SRS` §2.A.3.1 — xem "Tự chấm" cuối tài liệu.*
|
||||
|
||||
| C-id | Tên thành phần | Vùng | Thứ tự trong vùng | Kích thước / độ rộng | Kiểu trình bày | Phần tử trong prototype | Khớp |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| C01 | lbl_header_title | Z1 | 1 | auto | Tiêu đề cấp 1 | "Header: tiêu đề 'Giỏ hàng của bạn' + số lượng sản phẩm" | ✅ |
|
||||
| C02 | grp_seller_header | Z2 | 1 (đầu mỗi nhóm, lặp) | 100% | Nhãn | "danh sách nhóm theo seller (mỗi nhóm = 1 seller, hiển thị tên gian hàng)" | ✅ |
|
||||
| C03 | img_cart_item | Z2 | 1 (trong mỗi dòng) | 64px (đề xuất, chưa xác nhận — mức thấp) | Ảnh | "CartItem (ảnh...)" | ✅ |
|
||||
| C04 | lbl_product_name | Z2 | 2 | auto | Nhãn | "CartItem (...tên...)" | ✅ |
|
||||
| C05 | lbl_variant | Z2 | 3 | auto | Nhãn | "CartItem (...biến thể...)" | ✅ |
|
||||
| C06 | lbl_unit_price | Z2 | 4 | auto | Nhãn | "CartItem (...đơn giá...)" | 🟠 *(nguồn giá — `OQ-029` — bố cục khớp, nội dung/nguồn dữ liệu chưa chốt)* |
|
||||
| F01 | input_quantity | Z2 | 5 | 96px (đề xuất) | Ô nhập số (bộ đếm +/-) | "CartItem (...bộ đếm số lượng...)" | ✅ |
|
||||
| C07 | btn_delete_item | Z2 | 6 | auto | Nút nguy hiểm | "CartItem (...nút xoá)" | ✅ |
|
||||
| C08 | lbl_seller_subtotal | Z2 | 7 (cuối mỗi nhóm) | auto | Nhãn | "mỗi nhóm... hiển thị tên gian hàng" *(SAD không tách riêng "tạm tính theo nhóm" — BA suy ra từ "danh sách nhóm theo seller" + "tạm tính" ở footer; xem §5 #6)* | 🟠 |
|
||||
| C09 | lbl_cart_total | Z3 | 1 | auto | Nhãn | "Sidebar/footer tổng kết: tổng số lượng đã chọn, tạm tính (subtotal theo VND)" | ✅ |
|
||||
| C10 | btn_continue_shopping | Z4 | 2 (trong khối rỗng) | auto | Nút phụ | "empty = 'Giỏ hàng trống' + nút 'Tiếp tục mua sắm'" | ✅ |
|
||||
| C11 | btn_checkout | Z3 | 2 | 100% (nút chính, nổi bật) | Nút chính | "nút 'Tiến hành Checkout'" | ✅ *(bố cục khớp; điều kiện bật/tắt theo checkbox — xem §5 #1, KHÔNG áp dụng ở SRS này)* |
|
||||
| C12 | banner_error_load | Z4 | 1 | 100% | Banner | "error = cảnh báo dòng sản phẩm hết hàng/giá thay đổi (badge...)" *(SAD mô tả lỗi ở mức dòng, BA áp dụng mẫu banner lỗi tải chung của `UICONV` §6 cho lỗi `GET /v1/cart` — không phải cùng ý nghĩa với badge SAD, xem §5 #2/#3)* | 🟠 |
|
||||
| C13 | btn_retry_load | Z4 | 1 (trong C12) | auto | Nút phụ | Suy ra từ mẫu "lỗi tải + retry" chung toàn site (`UICONV` §6), SAD không viết riêng cho SCR-04 | 🟠 |
|
||||
|
||||
**Thành phần chỉ có ở prototype, chưa có trong SRS này** *(không gán C-id — theo quy tắc "không tự thêm vào SRS")*:
|
||||
|
||||
| Mô tả trong prototype (SAD §7.1.1) | Vì sao chưa vào SRS | Xem §5 |
|
||||
|---|---|---|
|
||||
| Checkbox chọn/bỏ chọn từng dòng `CartItem` hoặc cả nhóm | Không có `US` nào trong `BACKLOG` định nghĩa tính năng chọn từng phần giỏ hàng | #1 |
|
||||
| Badge "Sản phẩm đã hết hàng" chặn tick chọn | Phụ thuộc mục trên (checkbox) + kiểm tra tồn kho real-time ngoài scope US-002/US-003 theo `BACKLOG` | #2 |
|
||||
| Badge "Giá đã thay đổi" | Nguồn giá hiển thị (snapshot/real-time) chưa xác nhận thuộc scope US-002 | #3 |
|
||||
|
||||
**Kiểu trình bày** dùng đúng từ vựng `UICONV` §11 (Nhãn, Nút chính, Nút phụ, Nút nguy hiểm, Ô nhập số, Card, Modal, Banner...).
|
||||
|
||||
#### 3.1.3 Trạng thái hiển thị
|
||||
|
||||
| Trạng thái | Vùng thay đổi | Hiển thị gì | Khoá text (SRS §4.2) | Nút hành động |
|
||||
|---|---|---|---|---|
|
||||
| Đang tải lần đầu | Z2 | Skeleton 3 dòng (theo `UICONV` §6) | — | — |
|
||||
| **Chưa có dữ liệu nào (rỗng)** | Z2→Z4 | Minh hoạ + câu dẫn + nút | `cart.empty.title` | C10 |
|
||||
| Lỗi tải dữ liệu | Z2→Z4 | Banner C12 thay chỗ danh sách | `cart.error.load` | C13 |
|
||||
| Đang gửi (sửa số lượng/xoá 1 dòng) | Đúng dòng `CartItem` đang xử lý trong Z2 | Spinner cục bộ tại dòng, F01/C07 disable | — | — |
|
||||
|
||||
🔴 Hai trạng thái rỗng: SCR-04 chỉ có MỘT loại rỗng (chưa có dữ liệu), không có "bộ lọc không
|
||||
khớp" (giỏ hàng không có bộ lọc) — khác với mẫu chung `UICONV` §6 vốn có hai loại rỗng. Đây
|
||||
không phải lệch, mà là **không áp dụng** loại rỗng thứ hai.
|
||||
|
||||
#### 3.1.4 Responsive
|
||||
|
||||
| Breakpoint | Thay đổi so với desktop | Thành phần ẩn / gộp / đổi kiểu |
|
||||
|---|---|---|
|
||||
| Tablet | 🔴 Chưa mô tả riêng trong SAD — BA đề xuất giữ layout 1 cột, Z3 chuyển xuống footer | Mức thấp, không chặn G3 |
|
||||
| Mobile | Z3 (C09, C11) chuyển thành footer cố định đáy màn hình; Z2 giữ danh sách dạng dọc | Không ẩn thành phần nào, chỉ đổi vị trí Z3 |
|
||||
|
||||
#### 3.1.5 Tương tác trình bày
|
||||
|
||||
| Thành phần | Sự kiện | Phản hồi trình bày | Ngưỡng / thời lượng | Nguồn |
|
||||
|---|---|---|---|---|
|
||||
| F01 | Bấm +/- hoặc rời field sau khi gõ tay | Gọi `PATCH` ngay, dòng chuyển spinner cục bộ | 🔴 chưa có ngưỡng debounce khi gõ tay — `OQ` mới nếu cần, mức thấp không chặn G3 | BA đề xuất |
|
||||
| C07 | Bấm "Xoá" | Mở modal xác nhận giữa màn (theo `UICONV` §5) | — | `UICONV` §5 |
|
||||
| Toàn màn | Sửa/xoá thành công | Cập nhật C08/C09 tại chỗ, không toast (🔴 `UICONV` §5 chưa chốt vị trí/thời lượng toast — `OQ-020`, đã mở) | — | `UICONV` §5 (chưa chốt) |
|
||||
|
||||
#### 3.1.6 Thứ tự focus
|
||||
|
||||
| Thứ tự | ID | Ghi chú |
|
||||
|---|---|---|
|
||||
| 1 | F01 (dòng đầu tiên) | Focus mặc định khi vào màn hình — 🔴 chưa xác nhận với PO/Dev FE, mức thấp không chặn G3 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Luồng màn hình theo prototype
|
||||
|
||||
| Từ | Hành động | Đến | Có trong SRS §2.A.2 | Có trong prototype |
|
||||
|---|---|---|---|---|
|
||||
| SCR-01 | Bấm icon giỏ hàng | SCR-04 | ✅ | ✅ (SAD §7.2.1 bước D "Thêm vào giỏ hàng (SCR-04)") |
|
||||
| SCR-04 | Bấm "Tiến hành Checkout" (C11) | SCR-05 | ✅ (ngoài phạm vi hành vi) | ✅ (SAD §7.2.1 bước E→H) |
|
||||
| SCR-04 | Bấm "Tiếp tục mua sắm" (C10, giỏ rỗng) | SCR-01 | ✅ | 🟠 SAD không vẽ rõ cạnh này trong `7.2.1` (chỉ mô tả trong bố cục SCR-04, không có trong flowchart) — không mâu thuẫn, chỉ là chưa vẽ |
|
||||
|
||||
---
|
||||
|
||||
## 5. Lệch giữa prototype và SRS
|
||||
|
||||
| # | Màn hình | Prototype có | SRS nói | Loại lệch | Bên thắng theo W11 | Xử lý | Ai quyết | Trạng thái |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 1 | SCR-04 | Checkbox chọn/bỏ chọn từng dòng hoặc cả nhóm seller; nút Checkout ngụ ý chỉ áp dụng cho phần đã chọn | `SRS` §1.5 dòng 3: không có tính năng chọn từng phần; C11 chỉ disable khi giỏ có 0 `CartItem` | Tồn tại | Chưa quyết — đây là khoảng trống scope, không phải "SRS đã nói khác" | PO xác nhận có bổ sung tính năng chọn từng phần vào phạm vi US-002/US-003 (hoặc US mới) hay giữ nguyên "Checkout toàn giỏ" | PO | ☐ (`OQ-031`) |
|
||||
| 2 | SCR-04 | Badge "Sản phẩm đã hết hàng" chặn tick chọn | `SRS` §1.5 dòng 4: không kiểm tra tồn kho tại màn Giỏ hàng (khoanh vùng theo `BACKLOG`, thuộc US-004) | Tồn tại/Hành vi | Chưa quyết | PO + Tech Lead xác nhận có đưa kiểm tra tồn kho real-time vào US-002/US-003 hay giữ ở US-004 | PO, Tech Lead | ☐ (`OQ-030`) |
|
||||
| 3 | SCR-04 | Badge "Giá đã thay đổi"; đơn giá hiển thị ngụ ý là giá real-time (để so sánh phát hiện thay đổi) | `SRS` C06: nguồn giá chưa chốt (snapshot hay real-time) | Tồn tại/Hành vi | Chưa quyết | PO + Tech Lead xác nhận nguồn giá trước, sau đó mới quyết có badge này không | PO, Tech Lead | ☐ (`OQ-029`) |
|
||||
| 4 | SCR-04 | "số lượng ≥ 1 và ≤ tồn kho hiện tại" (validation chính của SAD SCR-04) | `SRS` F01: chỉ xác nhận ràng buộc dưới (≥1); ràng buộc trên chưa chốt | Hành vi | Chưa quyết | PO + Tech Lead xác nhận có kiểm tra tồn kho khi sửa số lượng ở màn Giỏ hàng không (cùng nguồn với #2) | PO, Tech Lead | ☐ (`OQ-030`, gộp cùng #2) |
|
||||
| 5 | SCR-04 | Chỉ liệt kê "nút xoá" trong `CartItem`, không nhắc xác nhận trước khi xoá | `SRS`/`WF` áp dụng modal xác nhận mặc định theo `UICONV` §5 (quy ước chung cho "xoá") | Hành vi | **SRS** (áp dụng đúng quy ước dự án, không phải SRS tự ý khác `UICONV`) | Đã xử lý — không cần PO quyết thêm, ghi nhận ở đây để không ai tưởng là bỏ sót | — | ✅ Đã xử lý |
|
||||
| 6 | SCR-04 | Không nêu rõ tiêu chí sắp xếp nhóm seller/dòng `CartItem` trong mỗi nhóm | `SRS` §2.A.3.2: BA đề xuất theo thời điểm thêm vào giỏ, mức thấp không chặn G3 | Bố cục nhỏ | **Prototype** (không có, để BA/Dev FE quyết định tự do) | Giữ đề xuất của BA, PO xác nhận không bắt buộc trước G3 | PO *(không chặn)* | ☐ *(không chặn G3 — mức thấp)* |
|
||||
|
||||
**Tổng kết:** đã đối chiếu 1 màn hình (`SCR-04`); **6 dòng lệch** — 4 dòng loại Tồn tại/Hành vi
|
||||
**chưa quyết** (🔴 chặn G3: #1, #2, #3, #4 — thực chất #2 và #4 cùng một nguồn `OQ-030`, đếm là
|
||||
2 câu hỏi độc lập `OQ-029`/`OQ-030`/`OQ-031`), 1 dòng Hành vi đã xử lý bằng cách áp dụng mặc định
|
||||
`UICONV` (#5, không chặn), 1 dòng Bố cục mức thấp không chặn (#6).
|
||||
|
||||
## 6. Bàn giao
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Designer còn phải làm** | Không có Designer riêng ở dự án này (`DEC-01`) — việc còn lại (màu, typography, icon, minh hoạ trạng thái rỗng, motion, khoảng cách chính xác) chuyển cho **Dev FE**, dùng `UICONV` §11 làm ràng buộc tối thiểu và tự chọn design system theo `OQ-022` khi được Tech Lead xác nhận |
|
||||
| **Dev FE dùng WF để** | Dựng layout Z1–Z4 và chọn component theo §3.1.2; **không** suy ra hành vi từ WF — hành vi ở `SRS` |
|
||||
| **Không được suy ra từ WF** | Khoảng cách/kích thước chính xác (chưa có design system); màu; font; ngưỡng debounce F01 (chưa chốt) |
|
||||
| **Khi prototype thay đổi** | Cập nhật `07-giao-dien.md` SCR-04 (SA/BE) → BA cập nhật §1, §5 và version WF; `SRS` chỉ đổi nếu lệch loại Tồn tại/Hành vi được PO chấp nhận qua `CR` (vì `SRS` chưa `✅ Baselined`, chưa cần `CR` chính thức, chỉ cần sửa trực tiếp + Change Log) |
|
||||
|
||||
## 7. Open Questions
|
||||
|
||||
| ID | Câu hỏi | Hỏi ai | Từ ngày | Chặn gì | 🔴 chặn G3? |
|
||||
|---|---|---|---|---|---|
|
||||
| OQ-031 | Checkout áp dụng toàn giỏ hay theo dòng được chọn? | PO | 2026-09-08 | §5 #1, C11 | 🔴 |
|
||||
| OQ-030 | Kiểm tra tồn kho khi sửa số lượng tại màn Giỏ hàng — có hay không? | PO, Tech Lead | 2026-09-08 | §5 #2, #4, F01 | 🔴 |
|
||||
| OQ-029 | Nguồn giá hiển thị C06 — snapshot hay real-time? | PO, Tech Lead | 2026-09-08 | §5 #3, C06 | 🔴 |
|
||||
|
||||
---
|
||||
|
||||
## Tự chấm
|
||||
|
||||
| # | Tiêu chí | ☐/✅ | Ghi chú |
|
||||
|---|---|---|---|
|
||||
| 1 | Mọi `SCR` trong `SRS` §2.A.1 có một mục §3 | ✅ | Chỉ 1 SCR (SCR-04), có §3.1 |
|
||||
| 2 | Mọi `C-id`/`F-id` khớp hai chiều giữa `SRS` §2.A.3.1 và WF §3.1.2 | ✅ | Xem kết quả chạy thật ngay dưới bảng này |
|
||||
| 3 | Mỗi màn hình có §3.x.3 với trạng thái rỗng | ✅ (có 1 loại rỗng, ghi rõ lý do không có loại thứ 2) | — |
|
||||
| 4 | Mỗi màn hình web có responsive ≥ 2 breakpoint | ✅ | Desktop/Tablet/Mobile — Tablet mức đề xuất thấp |
|
||||
| 5 | Mọi con số ở §3.x.5 có nguồn (UICONV/prototype/OQ) | ✅ | Debounce F01 chưa có ngưỡng, ghi rõ mức thấp không chặn |
|
||||
| 6 | §5 đã điền, mọi lệch Tồn tại/Hành vi có người quyết | ☐ | Đã điền đủ, nhưng 4 dòng còn ☐ chờ PO/Tech Lead — đây là kết quả thật của lần chạy này, không phải thiếu sót của WF |
|
||||
| 7 | Tệp HTML mở được, mỗi `SCR` một `<section id>`, mỗi thành phần có `data-c` khớp C-id | ✅ | Xem `WF_US002-003_v1.0.html` |
|
||||
| 8 | Kiểu trình bày chỉ dùng từ vựng `UICONV` §11 | ✅ | — |
|
||||
| 9 | Có chữ ký Designer *(hoặc PO thay, `DEC-01`)* | ☐ | Chưa ký — chờ PO xác nhận thật ở G3 |
|
||||
|
||||
**Kết quả chạy thật tự kiểm C-id/F-id hai chiều** *(trích `[CF][0-9]{2}` từ `SRS` §2.A.3.1 và
|
||||
từ `WF` .md §3.1.2 + `data-c` trong `.html`)*:
|
||||
|
||||
- Tập trong `SRS`: `C01, C02, C03, C04, C05, C06, F01, C07, C08, C09, C10, C11, C12, C13` (13 `C` + 1 `F` = 14)
|
||||
- Tập trong `WF` .md §3.1.2: `C01, C02, C03, C04, C05, C06, F01, C07, C08, C09, C10, C11, C12, C13` (khớp)
|
||||
- Tập `data-c` trong `.html`: `C01, C02, C03, C04, C05, C06, F01, C07, C08, C09, C10, C11, C12, C13` (khớp)
|
||||
- **Kết luận: 0 lệch hai chiều.** Ba thành phần chỉ có ở prototype (checkbox chọn dòng, badge hết hàng, badge giá đổi) **không** được gán `C-id` và **không** xuất hiện trong `data-c` của HTML — đúng quy tắc "không tự thêm vào SRS", được mô tả riêng ở khối chú thích trực quan trong HTML (không có `data-c`).
|
||||
Reference in New Issue
Block a user