Appearance
v1.11 — 15/05/2026
| Thay đổi | Section | Ảnh hưởng |
|---|---|---|
Đảo DEC-031 sang Option A — đổi tên section TC-SCR-09 lại về "Tab Ví khuyến mãi 2 trong wallet_screen.dart"; sửa toàn bộ 14 TC cũ reference "route /wallet/promotion2" → "tab thứ 3"; thêm TC-SCR09-13 regression (3 tab cùng work), TC-SCR09-15 regression FolderTabs ở KPI/Tet/RevenueKpi (nếu dev refactor widget gốc) | TC-SCR-09 | QA, FE Mobile |
Thêm 5 TC-006 mở rộng cho SCR-06-NEW-03 — popup StatisticWalletPromotion2Popup.tsx lịch sử giao dịch KM 2 (filter, date range, regression popup KM 1, trigger từ CustomerKm2WalletPopup) | TC-FR-006 | QA, FE Web |
| Update D4 traceability matrix: 131 → 137 ca kiểm thử | D4 | QA |
v1.10 — 15/05/2026
| Thay đổi | Section | Ảnh hưởng |
|---|---|---|
Đổi tên section TC-SCR-09 từ "Tab Ví KM 2" → "Screen Ví KM 2" (DEC-031); update 12 TC cũ (TC-SCR09-01..12) để reference route mới /wallet/promotion2 thay vì tab [ĐÃ ĐẢO ở v1.11] | (TC-SCR-09) | QA, FE Mobile |
| Thêm 2 TC regression mới TC-SCR09-13/14 — wallet_screen 2 tab hiện hữu KHÔNG bị ảnh hưởng (DEC-031); AffiliateFor không có option KM 2 (DEC-032) | (TC-SCR-09) | QA, FE Mobile |
Thêm TC-SCR09-ENUM-01..05 (5 TC mới) — WalletType.promotion2 enum + l10n shared (SCR-09-MOBILE-03) | (TC-SCR09-ENUM) | QA, FE Mobile |
| Thêm TC-SCR-10 — Staff app customer detail balance KM 2 (7 TC, SCR-10-STAFF-01) | (TC-SCR-10) | QA, FE Mobile |
| Update D4 traceability matrix: 117 → 131 ca kiểm thử | D4 | QA |
v1.8 — 15/05/2026
| Thay đổi | Section | Ảnh hưởng |
|---|---|---|
| Thêm TC-IDEMPOTENT-01..09 cho TG-003 idempotency gate (DEC-028) — 9 test scenarios | D2) Ca kiểm thử | QA, BE |
Thêm TC-BALANCE-RACE-01..05 cho action get_customer_km2_balance (DEC-029) — 5 test scenarios | D2) Ca kiểm thử | QA, BE |
| Update D4 traceability matrix: 80 → 94 ca kiểm thử | D4 | QA |
| Lint vietnamese clean: chuẩn hoá thuật ngữ "Trường hợp cá biệt" thay cho calque cũ | Toàn file | None |
Kế hoạch kiểm thử (QA Test Plan) — Ví KM 2 (Promotion Wallet 2)
Tham chiếu: PRD v1.11 + SOURCE_OF_TRUTH v1.6 | Ngày: 15/05/2026
File này dùng để làm gì: chốt coverage test, seed data và traceability cho
Ví KM 2. Quy tắc ưu tiên: nếu có khác biệt vớiSOURCE_OF_TRUTH.md, ưu tiênSOURCE_OF_TRUTH.md.
Đầu vào chuẩn (Canonical Inputs)
| File | Vai trò |
|---|---|
| SOURCE_OF_TRUTH.md | Nguồn sự thật chuẩn + khóa giải pháp |
| EVIDENCE_PACK.md | Bằng chứng code/screen/db/config thật |
| prd.md | FR/AC và công thức nghiệp vụ cần kiểm thử |
| dev-spec.md | Data/API/security contract để đối chiếu test tích hợp |
| ui-spec.md | State, permission, copy và interaction contract |
D1) Phạm vi kiểm thử
| FR | Mô tả | Mức ưu tiên |
|---|---|---|
| FR-001 | Cấu hình Ví KM 2 (max%, toggle, refund policy) | Must |
| FR-002 | Tạo/sửa Gói Ví KM2 (wallet_target, expiry_months) | Must |
| FR-003 | Bán Gói Ví KM2 → tạo lot(s) trong Ví KM 2 | Must |
| FR-004 | Thanh toán đơn hàng bằng Ví KM 2 (FIFO, max%, deduction) | Must |
| FR-005 | Flag allow_promo_wallet_2 per sản phẩm/dịch vụ | Must |
| FR-006 | Tab Ví KM 2 trong profile khách (danh sách lần mua Ví KM2, cảnh báo) | Must |
| FR-007 | Scheduler tự động hết hạn lot | Must |
| FR-008 | Refund đơn DV — phần Ví KM 2 | Should |
| FR-009 | Hoàn ví KM2 qua Yêu cầu hoàn tiền (refund_km2_wallet) | Should |
| FR-010 | Report dashboard | Could |
| FR-011 | ZNS thông báo | Could |
| FR-012 | Hiển thị Ví KM 2 trong đơn hàng hiện có (summary, bảng, hoá đơn in, fund/report impact) | Must |
D2) Ca kiểm thử
TC-FR-001: Cấu hình Ví KM 2
Luồng đúng:
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-001-00 | Sau migration, KM2 chưa tự bật | Migration seed config xong, chưa bấm enable | wallet_km2_config.disabled=true, payment method KM2 chưa hiện ở POS | P0 |
| TC-001-01 | Bật Ví KM 2, lưu config mặc định | Bật toggle, max% = 20, KM1+KM2 = tắt | Toast "Lưu cài đặt thành công", config lưu DB | P0 |
| TC-001-02 | Sửa max% thành 40%, lưu | max% = 40 | Config cập nhật, đơn mới áp dụng max 40% | P0 |
Trường hợp cá biệt / lỗi:
| TC | Loại | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|---|
| TC-001-03 | Biên | max% = 0 | Nhập 0 | Lỗi kiểm tra hợp lệ: "Tối thiểu 1%" | P1 |
| TC-001-04 | Biên | max% = 101 | Nhập 101 | Lỗi kiểm tra hợp lệ: "Tối đa 100%" | P1 |
| TC-001-05 | Lỗi | Tắt Ví KM 2 khi khách đang có số dư | Tắt toggle | Ẩn payment method KM2, số dư không mất, bật lại → hiện lại | P1 |
| TC-001-06 | Quyền | Staff truy cập config | Đăng nhập Staff, vào URL config | Ẩn menu, redirect | P1 |
TC-FR-002: Tạo/sửa loại Gói Ví KM2
Luồng đúng:
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-002-01 | Tạo Gói Gold target KM2 | value=500k, value_into_wallet=5tr, expiry_months=6 | Lưu thành công, danh sách hiện "Ví đích = KM2", data có wallet_target=VND_PROMOTION_2 | P0 |
| TC-002-02 | Tạo thẻ VND cũ | wallet_target=VND | Trường expiry_months ẩn, flow cũ không đổi | P0 |
Trường hợp cá biệt / lỗi:
| TC | Loại | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|---|
| TC-002-03 | Biên | KM2 nhưng expiry_months rỗng | wallet_target=KM2, expiry empty | Lỗi kiểm tra hợp lệ, không lưu | P1 |
| TC-002-04 | Biên | value_into_wallet < value | value=500k, value_into_wallet=300k | Lỗi kiểm tra hợp lệ "Số tiền nạp phải >= mệnh giá thẻ" | P1 |
| TC-002-05 | Hồi quy | Sửa thẻ VND hiện có | Không đổi wallet_target | Không phát sinh expiry bắt buộc, data cũ không bị đổi sang KM2 | P1 |
TC-FR-003: Bán Gói Ví KM2 → tạo lot(s)
Luồng đúng:
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-003-01 | Bán 1 Gói Gold (500k → 5tr, 6 tháng) | qty=1, payment=cash 500k | 1 lot: initial=5tr, balance=5tr, expired_at=+6m, status=active | P0 |
| TC-003-02 | Bán 3 Gói Silver (300k → 2tr, 3 tháng) | qty=3, payment=cash 900k | 3 lots riêng biệt, mỗi lot initial=2tr, balance=2tr, expired_at=+3m | P0 |
Trường hợp cá biệt / lỗi:
| TC | Loại | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|---|
| TC-003-03 | Kết hợp | Mix thẻ VND + Gói Ví KM2 trong cùng 1 đơn | Dòng 1: Thẻ 10tr (VND), Dòng 2: Gói Gold (KM2) | Cột "NẠP VÍ KM 2" xuất hiện, dòng 1 KM2=0, dòng 2 KM2=5tr. Sidebar hiện "Tiền vào ví KM 2". Submit: VND cộng ví thường, KM2 tạo lot | P0 |
| TC-003-04 | Kết hợp | Chỉ thẻ VND (không có KM2) | 2 dòng thẻ VND | Cột "NẠP VÍ KM 2" KHÔNG hiện. Sidebar KHÔNG có dòng "Tiền vào ví KM 2". Flow cũ 100% | P0 |
| TC-003-05 | Lỗi | Bán Gói Ví KM2 cho khách chưa có wallet VND_PROMOTION_2 | Khách mới | Hệ thống auto-create wallet, tạo lot thành công | P1 |
| TC-003-06 | Biên | qty = 0 hoặc âm | qty=0 | Lỗi kiểm tra hợp lệ, không tạo đơn | P1 |
| TC-003-07 | Kết hợp | Xoá dòng KM2 cuối cùng khỏi đơn | Có 1 dòng KM2, bấm xoá | Cột "NẠP VÍ KM 2" tự ẩn, sidebar ẩn dòng KM2 | P1 |
TC-FR-004: Thanh toán đơn hàng bằng Ví KM 2
Luồng đúng:
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-004-01 | Đơn 1tr, max 20%, ví còn 5tr | Chọn KM2 | Auto-fill 200k, khách trả 800k | P0 |
| TC-004-02 | Đơn eligible 700k (mix eligible + non-eligible), max 20% | 2 items, 1 eligible | KM2 = 700k × 20% = 140k, khách trả 860k | P0 |
| TC-004-03 | FIFO: 2 lots, lot cũ 300k, lot mới 4.7tr, cần trừ 400k | Đơn 2tr, max 20% | Lot cũ: trừ 300k (exhausted), lot mới: trừ 100k. 2 deduction records | P0 |
Trường hợp cá biệt / lỗi:
| TC | Loại | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|---|
| TC-004-04 | Biên | Ví chỉ còn 50k, max% cho phép 200k | Đơn 1tr, max 20% | KM2 = MIN(200k, 50k) = 50k, khách trả 950k | P1 |
| TC-004-05 | Biên | Ví = 0 | Đơn 1tr | Ẩn payment method KM2 | P1 |
| TC-004-06 | Lỗi | Tất cả items không eligible | Đơn 1tr, 0 item eligible | Ẩn payment method KM2 | P1 |
| TC-004-07 | Kết hợp | Toggle KM1+KM2 tắt, đã chọn KM1 | Chọn KM2 | KM1 bị ẩn, chỉ thấy KM2 | P1 |
| TC-004-08 | Kết hợp | Toggle KM1+KM2 bật, chọn cả 2 | KM1 300k + KM2 200k + cash 500k | Cả 2 ví bị trừ đúng, invoice ghi đúng | P1 |
| TC-004-09 | Race condition | 2 NV thanh toán cùng lúc cho 1 khách | 2 đơn submit song song | 1 thành công, 1 lỗi "Số dư không đủ" (SELECT FOR UPDATE) | P0 |
| TC-004-10 | Cross-branch | Khách mua Gói Ví KM2 chi nhánh A, thanh toán chi nhánh B | Branch khác | Thanh toán thành công, lot bị trừ | P1 |
| TC-004-11 | Idempotency | Retry cùng payment_attempt_id sau timeout | Submit lại cùng order/payment_attempt_id | Không tạo deduction mới, wallet không bị trừ lần 2 | P0 |
| TC-004-12 | Idempotency + multi-lot | Cùng một payment_attempt_id cần trừ qua 2 lot | Lot A balance 100k, Lot B balance 300k, amount KM2 = 140k; submit rồi retry cùng key | Lần đầu tạo 1 wallet_km2_payment_attempt + 2 deduction rows; retry trả kết quả cũ, không tạo thêm row và không trừ ví lần 2 | P0 |
| TC-004-13 | Expiry guard | Lot active nhưng expired_at <= NOW() do cron miss | Thanh toán bằng KM2 | Lot đó bị skip, nếu không còn lot hợp lệ thì báo số dư không đủ | P0 |
TC-IDEMPOTENT: Bộ test bắt buộc cho TG-003 (Idempotency Contract — DEC-028)
Bộ test này port từ dev-spec C5 (section E "Test scenarios bắt buộc"). TL ký xác nhận trước khi merge
deduct_km2_paymentlên staging.
| TC | Scenario | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-IDEMPOTENT-01 | Bấm Thanh toán 1 lần, BE 200 | 1 lần submit thành công | Lot trừ 1 lần; wallet_km2_payment_attempt.status='completed'; idempotent_replay=false | P0 |
| TC-IDEMPOTENT-02 | Bấm 2 lần liên tục < 1s (double-click) | 2 click submit cùng payment_attempt_id | Lot trừ 1 lần; lần 2 nhận idempotent_replay=true (FE đợi response lần đầu thì tự bỏ) hoặc 409 attempt_in_progress | P0 |
| TC-IDEMPOTENT-03 | Bấm 1 lần, mạng timeout, retry sau 5s | Submit lần đầu timeout, retry cùng payment_attempt_id | Lot trừ 1 lần; lần retry nhận idempotent_replay=true với result cached | P0 |
| TC-IDEMPOTENT-04 | Crash giữa lúc lock; sau 90s NV retry | Worker crash khi đang processing; sau 90s lock expire | Lock expired → cho retry mới; lot trừ 1 lần (vì transaction lần đầu đã rollback do crash) | P0 |
| TC-IDEMPOTENT-05 | NV A submit; NV B submit cùng attempt_id (bypass FE) | 2 NV khác nhau dùng cùng payment_attempt_id | A thắng; B nhận 409 attempt_mismatch (cross-check user_id JWT không khớp record) | P0 |
| TC-IDEMPOTENT-06 | Submit khi 1 lot vừa hết hạn (race với cron 00:05) | Lot expired ở 23:59:59, NV submit 23:59:59.500 | Tx check expired_at > NOW() → skip lot expired, FIFO chuyển lot tiếp; idempotent_replay=false cho retry sau; deducted_amount có thể < requested_amount | P0 |
| TC-IDEMPOTENT-07 | Submit thành công; sau 8 ngày retry cùng attempt_id | Record TTL > 7 ngày, đã được cron cleanup | Record đã expired/cleanup → coi như attempt mới; trừ lần 2 (vì user thực sự muốn payment thứ 2) | P1 |
| TC-IDEMPOTENT-08 | Bypass scenario: đổi order_id nhưng giữ attempt_id | 2 order khác nhau dùng cùng payment_attempt_id | Lần 2 reject attempt_mismatch (cross-check order_id không khớp) | P0 |
| TC-IDEMPOTENT-09 | Bypass scenario: đổi customer_id nhưng giữ attempt_id | Cùng payment_attempt_id nhưng customer_id khác | Reject attempt_mismatch (cross-check customer_id không khớp) | P0 |
Acceptance: 9 test pass + BE unit test cho 9 scenario này. Đây là gate TL-001 → TG-003 pass.
TC-BALANCE-RACE: Action get_customer_km2_balance realtime (DEC-029)
Đảm bảo balance hiển thị cho UI luôn match với BE deduct, đóng race window scheduler miss giữa
wallet.amountcached và lot expired.
| TC | Scenario | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-BALANCE-RACE-01 | Lot vừa hết hạn race với cron 00:05 | Lot Gold balance 2tr, expired_at='2026-05-15 23:59:59'; tại 00:00:30 NV mở SCR-04 cho khách | Action trả available_amount=0 (lot expired bị filter expired_at > NOW()), expired_unswept_amount=2tr (admin có thể xem); chip KM 2 ẩn vì balance=0 | P0 |
| TC-BALANCE-RACE-02 | Wallet.amount cached lệch với SUM(lot.balance) | wallet.amount=5tr (cached cũ), thực tế 1 lot 2tr đã expired; FE call action mới | Action trả available_amount=3tr (realtime SUM, KHÔNG dùng wallet.amount); UI hiển thị 3tr, BE deduct tối đa 3tr — không lệch | P0 |
| TC-BALANCE-RACE-03 | Scheduler đang chạy lúc NV mở SCR-04 | Cron 00:05 đang update 1 lot expired; cùng lúc NV mở SCR-04 | Action chỉ đọc lot status='active' AND expired_at > NOW(), race không gây sai số (idempotent với scheduler); response < 100ms | P1 |
| TC-BALANCE-RACE-04 | Action có nearest_expiry_at cho banner cảnh báo | Khách có 3 lot: lot A expired_at +5 ngày, lot B +20 ngày, lot C +60 ngày | nearest_expiry_at = +5 ngày; UI render banner danger (≤ 7 ngày) | P1 |
| TC-BALANCE-RACE-05 | Permission no-leak qua action | Staff không có quyền xem customer X gọi action với customer_id=X | Action trả 403 no_permission_customer, không lộ balance | P0 |
TC-FR-005: Flag allow_promo_wallet_2 per sản phẩm/dịch vụ
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-005-01 | Admin bật KM2 cho dịch vụ | Service detail -> bật toggle KM2 | Lưu thành công, order eligible tính dịch vụ này | P0 |
| TC-005-02 | Dynamic Permission revoke update | User không có internal_configuration:update, vào product/service detail | Toggle KM2 ẩn hoặc disabled theo permission, không mutation; gọi mutation trực tiếp bị chặn | P1 |
| TC-005-03 | Tắt KM2 cho item đang bán | allow_promo_wallet_2=false | Đơn mới không tính item này vào eligible_total | P1 |
| TC-005-04 | KM1 không bị đổi | Bật/tắt KM2 | Trường allow_promo_wallet của KM1 không đổi | P1 |
TC-FR-006: Tab Ví KM 2 trong profile khách
Luồng đúng:
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-006-01 | Xem ví KM 2 có 2 Gói Ví KM2 đang hoạt động | Profile khách có 2 Gói Ví KM2 đã mua | Tổng số dư = tổng 2 bản ghi mua, bảng hiện 2 dòng đang hoạt động | P0 |
Trường hợp cá biệt / lỗi:
| TC | Loại | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|---|
| TC-006-02 | Trạng thái | Khách không có lot nào | Profile khách mới | Tổng = 0, bảng trạng thái rỗng "Chưa có Gói Ví KM2 nào" | P1 |
| TC-006-03 | Biên | Lot hết hạn trong 7 ngày | expired_at = +5 ngày | Cảnh báo màu đỏ | P1 |
| TC-006-04 | Biên | Lot hết hạn trong 25 ngày | expired_at = +25 ngày | Cảnh báo bình thường (không đỏ) | P2 |
| TC-006-05 | Quyền | Staff xem profile khách được phân quyền | Đăng nhập Staff, mở profile khách thuộc phạm vi được phép | Tab Ví KM 2 hiển thị, action get_customer_km2_lots trả đúng lot của khách đó | P1 |
| TC-006-06 | Bảo mật | Staff gọi action xem khách ngoài phạm vi | Đăng nhập Staff, gọi get_customer_km2_lots với customer ngoài quyền | Bị chặn 403/permission error, không trả lot/deduction | P0 |
| TC-006-07 | SCR-06-NEW-03 — Popup lịch sử giao dịch KM 2 hiển thị đúng | Khách có 5 giao dịch KM 2 (3 payment + 1 refund + 1 expired); mở popup StatisticWalletPromotion2Popup | Bảng hiển thị đủ 5 row với cột (Ngày / Loại / Số tiền / ĐH / Chi nhánh / Số dư sau); query filter wallet_type_id = 'VND_PROMOTION_2' (KHÔNG lẫn với KM 1) | P0 | |
| TC-006-08 | SCR-06-NEW-03 — Filter dropdown loại giao dịch | Chọn dropdown "Thanh toán" | Bảng chỉ hiện row payment (3 row), filter client-side hoặc refetch | P1 | |
| TC-006-09 | SCR-06-NEW-03 — Filter date range | Chọn date range 7 ngày gần nhất | Bảng hiện chỉ row trong range; pagination reset về page 1 | P1 | |
| TC-006-10 | SCR-06-NEW-03 — Backwards compat popup KM 1 | Mở popup KM 1 (StatisticWalletPromotionPopup) cho khách có cả KM 1 + KM 2 | Popup KM 1 hiện chỉ giao dịch KM 1 (filter VND_PROMOTION), KHÔNG lẫn KM 2 — DEC-031 Option α (popup riêng, không refactor) | P0 | |
| TC-006-11 | SCR-06-NEW-03 — Trigger từ CustomerKm2WalletPopup | Mở CustomerKm2WalletPopup (chi tiết lot), click nút "Xem lịch sử giao dịch" | StatisticWalletPromotion2Popup mở overlay xếp chồng đúng z-index | P2 |
TC-FR-007: Scheduler tự động hết hạn lot
Luồng đúng:
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-007-01 | Bản ghi mua hết hạn hôm nay | 1 bản ghi đang hoạt động, expired_at = hôm qua | Sau cron: status=expired, wallet.amount giảm, deduction record type=expiry_cancel | P0 |
Trường hợp cá biệt / lỗi:
| TC | Loại | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|---|
| TC-007-02 | Biên | Lot hết hạn nhưng balance = 0 | Lot exhausted (balance=0) | Không bị expire (đã exhausted rồi), không trừ wallet | P1 |
| TC-007-03 | Lỗi | Cron chạy nhưng không có bản ghi mua hết hạn | Tất cả bản ghi đang hoạt động, expired_at > NOW() | Không có thay đổi, log "0 lots expired" | P2 |
TC-FR-008: Refund đơn DV — phần Ví KM 2
Luồng đúng:
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-008-01 | Refund đơn dùng KM2 200k (1 lot) | Manager duyệt refund | Lot cộng lại 200k, wallet.amount tăng 200k, deduction type=refund | P0 |
Trường hợp cá biệt / lỗi:
| TC | Loại | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|---|
| TC-008-02 | Kết hợp | Refund đơn trừ 2 lots (FIFO: lot1=150k, lot2=50k) | Refund toàn bộ 200k | Lot1 +150k, lot2 +50k (query deduction records) | P1 |
| TC-008-03 | Biên | Refund vào lot đã hết hạn | Lot expired + refund | Lot gia hạn +30 ngày (config), status=active, cộng balance | P1 |
| TC-008-04 | Biên | Refund vào lot đã refunded | Lot refunded + refund đơn DV | Tạo lot MỚI, initial_amount = refund amount, expired_at = +30d | P1 |
TC-FR-009: Hoàn ví KM2 qua Yêu cầu hoàn tiền
Luồng đúng:
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-009-01 | Hoàn ví KM2 Gold: 500k→5tr, dùng 2tr, còn 3tr, phí 20% | User có quyền tạo request bấm "Hoàn ví KM2", approver duyệt request refund_km2_wallet | Request xuất hiện trong Yêu cầu hoàn tiền; khách nhận: 3tr × 10% - 100k = 200k. Lot=refunded, wallet -= 3tr | P0 |
Trường hợp cá biệt / lỗi:
| TC | Loại | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|---|
| TC-009-02 | Biên | Phí xử lý hoàn ví > tiền hoàn (phí 100k > hoàn 50k) | Gói Ví KM2 500k→5tr, còn 500k. Hoàn=50k-100k=-50k | Hiển thị "Khách nhận: 0đ", cho tạo request; khi duyệt, lot bị trừ nhưng không hoàn tiền thật | P1 |
| TC-009-03 | Lỗi | Hoàn ví KM2 đã hết hạn | Lot expired | Nút "Hoàn ví KM2" ẩn; gọi API trực tiếp bị chặn KM2_LOT_INVALID_STATUS | P1 |
| TC-009-04 | Lỗi | Hoàn ví KM2 đã dùng hết | Lot exhausted (balance=0) | Nút "Hoàn ví KM2" ẩn | P1 |
| TC-009-05 | Quyền revoke | User không có refund_request_management_submenu:access/create | Refresh/relogin, mở profile khách | Nút "Hoàn ví KM2" ẩn; gọi tạo request trực tiếp bị chặn, không leak lot | P0 |
| TC-009-06 | Quyền approve/payment | User có access/create nhưng không có approve/payment | Tạo request thành công, vào màn Yêu cầu hoàn tiền | Thấy request nếu thuộc scope nhưng không thấy nút duyệt/thanh toán; API approve/payment bị chặn | P0 |
| TC-009-07 | Portal split | Grant refund_request_management_submenu:approve ở Admin portal, revoke ở POS portal | Test cùng account ở Admin/POS | Quyền không bleed giữa portal; POS không duyệt nếu bị revoke | P1 |
| TC-009-08 | Branch scope | Approver branch A duyệt request customer/lot branch B | Có request refund_km2_wallet ngoài scope | Bị chặn bởi branch_mode; không đổi lot/wallet | P0 |
TC-PERM-001: Dynamic Permission v2 regression
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-PERM-01 | Grant/revoke internal_configuration:update | Grant rồi revoke user test, refresh/relogin | Menu/settings/flag KM2 hiện rồi ẩn đúng; mutation trực tiếp bị chặn sau revoke | P0 |
| TC-PERM-02 | Grant/revoke service_order:payment | Grant rồi revoke trên POS portal | Chip Ví KM2 hiện rồi ẩn trong payment DV; deduct_km2_payment no-leak khi revoke | P0 |
| TC-PERM-03 | Grant/revoke product_order:payment | Grant rồi revoke trên POS portal | Chip Ví KM2 hiện rồi ẩn trong payment mỹ phẩm/SP; API no-leak khi revoke | P1 |
| TC-PERM-04 | customer_management:access + branch scope | Staff có quyền xem customer A, không xem customer B | Action get_customer_km2_lots trả A, chặn B | P0 |
| TC-PERM-05 | view_all behavior | Role có/không có customer_management:view_all | Có view_all xem all-branch nếu branch_mode cho phép; không có thì branch scoped | P1 |
TC-FR-010: Report dashboard
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-010-01 | Report summary theo tháng | Có bản ghi mua active/exhausted/expired | KPI số Gói Ví KM2 bán, tổng nạp, đã dùng, tỉ lệ sử dụng Ví KM2 đúng | P1 |
| TC-010-02 | Manager branch scope | Đăng nhập Manager branch A | Chỉ thấy lot bán tại branch A trong report | P1 |
| TC-010-03 | Denominator = 0 | Không có bản ghi mua trong kỳ | Tỉ lệ hiển thị "—", không NaN/0% sai nghĩa | P2 |
TC-FR-011: ZNS thông báo
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-011-01 | FR-011 tắt ở Phase 1 | PO chưa bật ZNS | Không block bán Gói Ví KM2/thanh toán, không gửi ZNS | P1 |
| TC-011-02 | FR-011 bật và template đã đăng ký | Bán Gói Gold | Gửi đúng 1 ZNS activation với biến qty/package/amount/expired_at | P2 |
TC-FR-012: Hiển thị Ví KM 2 trong màn hiện có
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-012-01 | Prepaid order summary có KM2 | Mix thẻ VND + Gói Ví KM2 | Cột/dòng KM2 hiện conditional, flow VND cũ không đổi | P0 |
| TC-012-02 | Invoice print label | Đơn thanh toán wallet_promotion_2 | Hóa đơn/preview hiện label "Ví KM 2", không hiện nhầm "Ví khuyến mãi" KM1 | P0 |
| TC-012-03 | Fund không tạo incoming cho phần KM2 thanh toán | Đơn có cash + KM2 | Fund chỉ ghi phần cash/non-wallet; KM2 không tính actual revenue | P0 |
| TC-012-04 | Cosmetic order payment | Đơn mỹ phẩm có item eligible KM2 | Bảng payment hiện wallet_promotion_2_amount, total đúng | P1 |
| TC-012-05 | Product order eligibility | Product không eligible KM2 | Không tính vào eligible_total | P1 |
| TC-012-06 | Report DV/NV tách KM2 | Có invoice KM1 + KM2 | Report không gộp nhầm KM1/KM2, cột/segment KM2 đúng | P1 |
| TC-012-COMM-KM2-OFF | RULE-COMM-001 — Default config: bán Gói KM 2 có CTV thanh toán bằng KM 2 | Migration 3 đã seed affiliate_config(prepaid, wallet_promotion_2, FALSE). Khách có CTV wallet_receive_commission='promotion', mua Gói Gold 500k thanh toán bằng wallet_promotion_2 | SELECT * FROM invoice_affiliate WHERE invoice_id = ... → 0 row. Log BE có dòng "Affiliate not enable for payment method: wallet_promotion_2" | P0 |
| TC-012-COMM-KM2-CASH | RULE-COMM-001 — Default config: bán Gói KM 2 có CTV thanh toán tiền mặt | Khách có CTV, mua Gói Gold 500k thanh toán cash. affiliate_config(prepaid, cash)=TRUE (mặc định Diva) | SELECT * FROM invoice_affiliate WHERE invoice_id = ... → 1 row, commission_amount = 500.000 × policy_tag.promotion_wallet_percent / 100 (hành vi giống KM 1) | P0 |
| TC-012-COMM-KM2-MIX | RULE-COMM-001 — Default config: MIX payment | Khách có CTV, mua Gói Gold 500k thanh toán 60% cash + 40% wallet_promotion_2 | 2 sub-invoice tạo ra với payment_method_id khác nhau. Gate InsertInvoiceAffiliate chạy per sub-invoice; kết quả phụ thuộc ô matrix: sub-invoice cash → 1 row commission; sub-invoice ví → 0 row commission. Commission chỉ tính trên 300k cash | P0 |
| TC-012-COMM-KM2-ADMIN-OVERRIDE | RULE-COMM-001 — Admin bật override | Admin vào /s/internal-settings/affiliate, bật ô (service, wallet_promotion_2) → khách dùng KM 2 thanh toán đơn DV có CTV | invoice_affiliate có row commission với wallet_type_id='VND_PROMOTION_2'. Gate affiliate_config thật sự respect admin override (không có hard-code skip nào) | P1 |
| TC-012-COMM-DISPLAY | SCR-FR012-COMM-01 — OrderCommissionItem hiển thị row KM 2 | Tiếp tục từ TC-012-COMM-KM2-ADMIN-OVERRIDE: vào trang chi tiết đơn DV đó trong Admin/POS | Component OrderCommissionItem hiển thị row commission của VND_PROMOTION_2 (filter mở rộng); không lọc bỏ vì filter mới include cả 2 wallet type | P1 |
| TC-012-SMS-KM2-BALANCE | SCR-FR012-SMS-01 — SMS sau invoice complete bao gồm KM 2 | Khách có balance KM 1 = 100k + balance KM 2 = 5tr, thanh toán đơn DV 200k bằng KM 2 | SMS gửi đi có nội dung "Vi chinh: ..., Vi KM1: 100.000, Vi KM2: 4.800.000. ND: ..." (3 dòng balance) | P0 |
| TC-012-SMS-KM2-ONLY | SCR-FR012-SMS-01 — Khách chỉ có KM 2 | Khách balance KM 1 = 0, KM 2 = 3tr | SMS render: "Vi chinh: ..., Vi KM2: 2.800.000. ND: ..." (skip dòng KM 1 vì balance = 0) | P1 |
| TC-012-SMS-KM1-ONLY | SCR-FR012-SMS-01 — Backwards compat khách không có KM 2 | Khách balance KM 1 = 100k, không có KM 2 | SMS render giống cũ: "Vi chinh: ..., Vi KM1: 100.000. ND: ..." (skip dòng KM 2) | P1 |
| TC-012-NEG-INVOICE-KM2 | SCR-FR012-NEG-02 — Hoàn đơn DV thanh toán KM 2 | Tạo đơn DV 500k thanh toán KM 2, sau đó hoàn đơn | NegativeInvoicesTable hiện row với cột "Vào Ví KM 2" giá trị âm (-500.000); cột "Vào ví KM 1" trong row đó = 0 hoặc empty | P0 |
| TC-012-NEG-INVOICE-MIX | SCR-FR012-NEG-02 — Mix refund KM 1 + KM 2 | Đơn DV 1tr thanh toán 60% KM 1 + 40% KM 2, hoàn toàn bộ | NegativeInvoicesTable có 2 row hoặc 1 row với cả 2 cột: "Vào ví KM 1" = -600k, "Vào Ví KM 2" = -400k | P1 |
| TC-012-AFF-MATRIX-RENDER | SCR-FR012-AFF-01 — UI Admin Affiliate Configuration tự render cột mới | Sau Migration 3, vào /s/internal-settings/affiliate tab "Cấu Hình Chung" | Matrix có cột mới "VÍ KHUYẾN MÃI 2" giữa các cột hiện có; checkbox cho 3 row (DV/Mỹ phẩm/Nạp tiền) đều OFF mặc định | P0 |
| TC-012-AFF-MATRIX-TOGGLE | SCR-FR012-AFF-01 — Admin toggle ô KM 2 | Admin tick (service, wallet_promotion_2) → Lưu → khách dùng KM 2 thanh toán đơn DV có CTV | Save thành công; sau đó invoice_affiliate có row commission (kết hợp TC-012-COMM-KM2-ADMIN-OVERRIDE) | P1 |
TC-SCR-09: App khách Flutter — Tab Ví khuyến mãi 2 trong wallet_screen.dart + Home widget (PD-002, DEC-031)
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-SCR09-01 | Khách có lot active vào tab Ví khuyến mãi 2 | Khách có 2 lot active (Gold 1.2tr, Diamond 3.5tr); mở wallet_screen.dart, switch sang tab "Ví khuyến mãi 2" (tab thứ 3) | Tab Ví khuyến mãi 2 hiển thị balance = 4.7tr (realtime từ get_customer_km2_balance), 2 row lot trong bảng "Đang hoạt động"; balance card phía trên cập nhật giá trị 4.7tr | P0 |
| TC-SCR09-02 | Empty state — khách chưa có lot nào | Khách chưa mua Gói KM 2 bao giờ; switch tab Ví khuyến mãi 2 | Tab hiện empty state: "Bạn chưa có Gói Ví KM 2 nào. Đến spa để mua." | P0 |
| TC-SCR09-03 | Banner cảnh báo sắp hết hạn | Khách có lot Gold, expired_at còn 7 ngày; switch tab Ví khuyến mãi 2 | Banner ⚠️ hiện trên cùng tab; tap banner cuộn xuống row lot Gold | P0 |
| TC-SCR09-04 | Home widget conditional ẩn | Khách có balance KM 2 = 0 (đã dùng hết hoặc chưa có) | Home (home_wallet.dart) chỉ hiện 4 card hiện có (DIVA/Commission/Reward Point/Bonus Point); KHÔNG hiện card thứ 5 "Ví KM 2" | P1 |
| TC-SCR09-05 | Home widget hiện khi có balance | Khách có balance > 0 | Home hiện thêm card thứ 5 "Ví khuyến mãi 2: 4.7tr" + HSD gần nhất; tap → mở wallet_screen.dart với tab Ví khuyến mãi 2 active sẵn (initialTabIndex đúng) | P0 |
| TC-SCR09-06 | Drawer detail lot | Tap row Gold trong bảng tab Ví khuyến mãi 2 | Slide-up drawer hiện 2 block: Thông tin Gói + Lịch sử giao dịch (load wallet_km2_lot_deduction của lot đó) | P0 |
| TC-SCR09-07 | Pull-to-refresh tab | Sau khi NV tại POS bán thêm 1 Gói cho khách, khách pull-to-refresh trên tab Ví khuyến mãi 2 | Sau refresh balance + danh sách lot cập nhật giá trị mới (realtime qua action get_customer_km2_balance); 2 tab kia KHÔNG bị refetch | P0 |
| TC-SCR09-08 | Security — khách query lot khách khác | Khách A gọi action get_customer_km2_lots với input.customer_id = <khach_B_id> | Backend reject 403 (UNAUTHORIZED); UI hiện "Không thể tải. Tải lại" | P0 |
| TC-SCR09-09 | Feature flag tắt | Admin tắt KM 2 (wallet_km2_config.disabled=true) | Card home ẩn (conditional balance); switch tab Ví khuyến mãi 2 → hiển thị empty state | P1 |
| TC-SCR09-10 | Offline mode | Khách mở tab Ví khuyến mãi 2 offline | Hiển thị cached balance gần nhất + banner "Đang ngoại tuyến — số dư có thể chưa cập nhật" | P2 |
| TC-SCR09-11 | Lot vừa hết hạn (cron miss) | Lot có expired_at < NOW() nhưng scheduler chưa chạy; expired_unswept_amount > 0 | Action trả available_amount không bao gồm số này (realtime), UI hiển thị balance đúng; bảng "Đã hết hạn" hiện lot này sau scheduler chạy | P1 |
| TC-SCR09-12 | ZNS deep link mở tab | Khách tap ZNS km2_lot_activated (FR-011) | App mở wallet_screen.dart với tab Ví khuyến mãi 2 active sẵn (không phải 2 tab cũ) | P2 |
| TC-SCR09-13 | Regression — 2 tab hiện hữu KHÔNG bị vỡ | Mở wallet_screen.dart, switch giữa tab "Ví thẻ trả trước" + "Ví khuyến mãi" + "Ví khuyến mãi 2" | Cả 3 tab work bình thường, balance card switch đúng giá trị từng tab; KHÔNG có visual regression (folder shape / chip / TabBar tuỳ implementation chọn) | P0 |
| TC-SCR09-14 | DEC-032 — Affiliate radio button không có KM 2 | NV mở form tạo service order ở staff app, gắn 1 CTV (affiliate_user) | Component AffiliateFor chỉ hiện 2 radio option [Hoa hồng, Ví khuyến mãi], KHÔNG có option "Ví khuyến mãi 2" (DEC-032 Option α) | P0 |
| TC-SCR09-15 | Regression FolderTabs — KPI/Tet screens không bị vỡ (nếu dev chọn refactor FolderTabs) | Mở kpi_management_screen.dart, revenue_kpi_screen.dart, tet_holiday_gifting_screen.dart | 2 tab hiển thị đúng pattern hiện có; folder shape (nếu giữ widget gốc) hoặc widget mới (nếu swap) consistent | P0 |
TC-SCR09-ENUM: WalletType enum + l10n shared (SCR-09-MOBILE-03)
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-SCR09-ENUM-01 | Enum serialization | Gọi WalletType.promotion2.type (Dart enum) | Trả về string 'VND_PROMOTION_2' (đồng bộ với BE wallet_type.id) | P0 |
| TC-SCR09-ENUM-02 | Localization VI | Mở app với locale vi | WalletType.promotion2.walletLocalized(context) trả về "Ví khuyến mãi 2" | P0 |
| TC-SCR09-ENUM-03 | Localization EN | Mở app với locale en | WalletType.promotion2.walletLocalized(context) trả về tương đương EN (đồng bộ với ARB) | P1 |
| TC-SCR09-ENUM-04 | JSON deserialization | Response API có {"wallet_type_id": "VND_PROMOTION_2"} | Parse thành công thành WalletType.promotion2 (qua _$WalletTypeEnumMap) | P0 |
| TC-SCR09-ENUM-05 | Backwards compat | Response API có {"wallet_type_id": "VND_PROMOTION"} | Vẫn parse thành WalletType.promotion cũ (không bị ảnh hưởng) | P0 |
TC-SCR-10: Staff app Flutter — Customer detail balance KM 2 (SCR-10-STAFF-01)
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-SCR10-01 | Khách có balance KM 2 | Staff/Manager mở customer detail của khách có lot active (balance 4.7tr) | Section "Ví của khách" hiện đủ 4 dòng: Ví DIVA, Hoa hồng, Ví khuyến mãi (KM 1), Ví khuyến mãi 2 (mới — 4.700.000 đ) | P0 |
| TC-SCR10-02 | Khách KHÔNG có balance KM 2 (conditional ẩn) | Khách chưa mua KM 2; balance = 0 | Row "Ví khuyến mãi 2" KHÔNG hiện (conditional balance > 0); các row khác hiện như cũ | P0 |
| TC-SCR10-03 | API call song song (performance) | Network inspector log customer_repository.impl.dart:473-481 | Có 2+ call getBalances song song qua Future.wait (promotion + promotion2), KHÔNG tạo serial latency | P1 |
| TC-SCR10-04 | API fail silently | Mock getBalances(promotion2) trả error | Row KM 2 ẩn (KHÔNG hiện "Đang tải lỗi"); các row khác vẫn hiện bình thường | P1 |
| TC-SCR10-05 | Permission gating | Staff không có customer_management:access | Toàn bộ customer detail bị chặn từ trước (không vào được), nên row Ví KM 2 không reachable | P0 |
| TC-SCR10-06 | Branch scope | Manager branch A xem khách branch B | Bị chặn theo DEC-013 (đồng bộ với pattern Ví KM 1) | P1 |
| TC-SCR10-07 | Pull-to-refresh sau khi POS bán Gói mới | NV bán Gói Gold cho khách → Staff pull-to-refresh customer detail | Row "Ví khuyến mãi 2" hiện balance mới (hoặc xuất hiện nếu trước đó balance = 0) | P0 |
TC-IMPACT-001: Regression biên ảnh hưởng / không ảnh hưởng
| TC | Mô tả | Dữ liệu nhập | Kỳ vọng | Mức ưu tiên |
|---|---|---|---|---|
| TC-IMPACT-01 | KM1 không bị đổi nghĩa | Customer có VND_PROMOTION, không có KM2 | Số dư KM1, thanh toán KM1, report KM1 giữ nguyên; không bị cộng/gộp với KM2 | P0 |
| TC-IMPACT-02 | Yêu cầu hoàn tiền có thêm loại Hoàn ví KM2 | Tạo request refund_km2_wallet | List/detail/filter hiện "Hoàn ví KM2", đủ field lot/KM2/refund/policy; approve/payment đi qua màn hiện có | P0 |
| TC-IMPACT-03 | Hoàn đơn đã dùng KM2 khác Hoàn ví KM2 | Đơn DV đã dùng KM2 bị refund | Hệ thống hoàn về đúng lot theo deduction; không tạo request refund_km2_wallet nếu đây là refund đơn | P0 |
| TC-IMPACT-04 | Rank/loyalty không đổi | Đơn thanh toán một phần bằng KM2 | Phần KM2 không tính actual revenue/customer points; rank formula không thêm rule mới | P1 |
| TC-IMPACT-05 | Appointment/kho/CRM assignment không mở scope | Customer có appointment/order/KM2 | Lịch hẹn, stock movement, owner/pipeline không đổi; chỉ order payment/profile hiển thị KM2 | P1 |
| TC-IMPACT-06 | Notification không dùng nhầm biến KM1 | Template có wallet_promotion_amount, đơn KM2 | KM2 không ghi vào biến KM1; nếu bật thông báo KM2 thì dùng biến/template KM2 riêng | P1 |
| TC-IMPACT-07 | Export/report không gộp KM1/KM2 | Dataset có cash, wallet, KM1, KM2 | Export/report có label/field KM2 riêng; wallet_promotion_amount vẫn chỉ là KM1 | P1 |
D3) Dữ liệu seed
Dataset: DS-001 — Dữ liệu test Ví KM 2
Cách tạo: SQL Script
sql
-- 1. Config Ví KM 2: seed mặc định phải tắt. TC-001-01 sẽ bật disabled=false cho các test runtime.
INSERT INTO wallet_km2_config (id, max_percent_per_order, allow_combine_km1, allow_refund, refund_fee_percent, refund_deadline_days, refund_extend_days, disabled)
VALUES (gen_random_uuid(), 20, false, true, 20, 30, 30, true);
-- Sau khi verify TC-001-00, bật config cho các test payment/runtime:
UPDATE wallet_km2_config
SET disabled = false
WHERE branch_id IS NULL AND deleted_at IS NULL;
-- 2. Wallet type + Payment gateway (nếu chưa có)
-- Xem dev-spec C4 seed data
-- 3. Prepaid cards (Gói Ví KM2)
INSERT INTO prepaid_card (id, code, name, value, value_into_wallet, wallet_target, expiry_months, disabled)
VALUES
(gen_random_uuid(), '#TC-SILVER', 'Gói Silver', 300000, 2000000, 'VND_PROMOTION_2', 3, false),
(gen_random_uuid(), '#TC-GOLD', 'Gói Gold', 500000, 5000000, 'VND_PROMOTION_2', 6, false);
-- 4. Products với allow_promo_wallet_2
UPDATE product SET allow_promo_wallet_2 = true WHERE name ILIKE '%massage%' OR name ILIKE '%gội%';
UPDATE product SET allow_promo_wallet_2 = false WHERE name ILIKE '%kem%' OR name ILIKE '%serum%';
-- 5. Test customer với 2 lots
-- (Tạo qua flow bán Gói Ví KM2, hoặc INSERT trực tiếp cho test)
-- Verify: Kiểm tra dữ liệu
SELECT wt.id, wt.name FROM wallet_type wt WHERE id = 'VND_PROMOTION_2';
SELECT COUNT(*) FROM wallet_km2_config WHERE deleted_at IS NULL;
SELECT COUNT(*) FROM prepaid_card WHERE wallet_target = 'VND_PROMOTION_2' AND disabled = false;D4) Truy vết
| FR | TC-ID | Coverage | Trạng thái |
|---|---|---|---|
| FR-001 | TC-001-* | 7 ca kiểm thử (3 happy + 4 edge) | |
| FR-002 | TC-002-* | 5 ca kiểm thử (2 happy + 3 edge/regression) | |
| FR-003 | TC-003-* | 7 ca kiểm thử (2 happy + 5 edge) | |
| FR-004 | TC-004-* + TC-IDEMPOTENT-* + TC-BALANCE-RACE-* | 13 ca payment + 9 ca idempotency (TG-003) + 5 ca balance race (DEC-029) | |
| FR-005 | TC-005-* | 4 ca kiểm thử (permission + regression KM1) | |
| FR-006 | TC-006-* | 11 ca kiểm thử (1 happy + 5 edge/security + 5 popup lịch sử KM 2 SCR-06-NEW-03) | |
| FR-007 | TC-007-* | 3 ca kiểm thử (1 happy + 2 edge) | |
| FR-008 | TC-008-* | 4 ca kiểm thử (1 happy + 3 edge) | |
| FR-009 | TC-009-* | 8 ca kiểm thử (1 happy + 7 edge/permission) | |
| FR-010 | TC-010-* | Phase 3, 3 ca kiểm thử | |
| FR-011 | TC-011-* | Tuỳ phase, 2 ca kiểm thử | |
| FR-012 | TC-012-* | 17 ca: 6 hiện hữu + 5 commission (RULE-COMM-001, gồm TC-COMM-DISPLAY) + 3 SMS (SCR-FR012-SMS-01) + 2 negative invoice (SCR-FR012-NEG-02) + 2 affiliate matrix (SCR-FR012-AFF-01) | |
| SCR-09 (mobile customer) | TC-SCR09-* | 15 ca app khách Flutter (PD-002, DEC-031, DEC-032): tab Ví khuyến mãi 2 trong wallet_screen.dart (tab thứ 3), home widget, drawer, security, offline, ZNS deep link, regression 2 tab cũ, regression 3 màn dùng FolderTabs khác, affiliate radio button | |
| SCR-09-MOBILE-03 (enum + l10n) | TC-SCR09-ENUM-* | 5 ca WalletType.promotion2 enum + l10n key | |
| SCR-10 (mobile staff) | TC-SCR10-* | 7 ca staff app Flutter (SCR-10-STAFF-01): customer detail balance KM 2 conditional, API parallel, permission, branch scope | |
| Permission v2 | TC-PERM-* | 5 ca kiểm thử grant/revoke/portal/branch/API no-leak | |
| Impact boundary | TC-IMPACT-* | 7 ca regression biên ảnh hưởng / không ảnh hưởng | |
| Tổng | 137 ca kiểm thử (94 + 11 TC-012 mở rộng + 15 TC-SCR09 + 5 TC-SCR09-ENUM + 7 TC-SCR10 + 5 TC-006 mở rộng SCR-06-NEW-03) |
D5) Điều kiện vào/ra kiểm thử
Điều kiện vào (bắt đầu test)
- [ ] BE deploy xong (migrations wallet + ecommerce + Hasura metadata)
- [ ] FE deploy xong trên test env
- [ ] Seed data DS-001 có sẵn
- [ ] Test accounts: 1 Admin, 1 Manager, 1 Staff, 1 Customer (có wallet VND_PROMOTION_2)
- [ ] Đã verify TC-001-00: sau migration config KM2 ở trạng thái tắt
- [ ] Sau đó Admin bật config Ví KM 2 (max 20%) để chạy các test runtime/payment
- [ ] Ít nhất 2 Gói Ví KM2 tạo sẵn (Silver + Gold)
- [ ] Ít nhất 2 sản phẩm eligible + 2 non-eligible
Điều kiện ra (kết thúc test)
- [ ] Tất cả P0 ca kiểm thử trong D2 PASS
- [ ] Tất cả P1 ca kiểm thử PASS hoặc có waiver từ PO/TL
- [ ] P2 ca kiểm thử: document known issues nếu FAIL
- [ ] Gate kỹ thuật
TG-001..TG-007tronghandoff.mdcó bằng chứng pass hoặc waiver rõ owner/hạn xử lý - [ ] Performance: FIFO deduction < 500ms cho 10 lots
- [ ] Không còn bug critical/major đang mở