Appearance
QA Test Plan: Quản lý vật tư theo Subtask
Feature slug: subtask-materialVersion: 1.0 Ngày: 2026-03-26
D1) Coverage
| Loại | Số lượng | Chi tiết |
|---|---|---|
| FR | 8 | FR-001 → FR-008 |
| NFR | 2 | NFR-001, NFR-002 |
| Edge Cases | 7 | EC-01 → EC-07 |
| Test Cases | 22 | TC-FR-001 → TC-FR-008, TC-NFR-001 → TC-NFR-002, TC-EC-001 → TC-EC-007, TC-CP-001 → TC-CP-005 |
D2) Requirements Matrix
| FR/NFR | Test Cases | Priority |
|---|---|---|
| FR-001 Thêm vật tư | TC-FR-001a (tạo mới), TC-FR-001b (search thêm) | Critical |
| FR-002 Auto-fill | TC-FR-002a (có config), TC-FR-002b (không config) | Critical |
| FR-003 Sửa vật tư | TC-FR-003a (sửa SL), TC-FR-003b (xóa dòng), TC-FR-003c (thêm khi sửa) | Critical |
| FR-004 Readonly | TC-FR-004 | High |
| FR-005 Aggregate | TC-FR-005a (aggregate đúng), TC-FR-005b (cập nhật khi sửa subtask) | Critical |
| FR-006 Backward compat | TC-FR-006a (data cũ), TC-FR-006b (cả 2 data) | High |
| FR-007 Fix button + confirm | TC-FR-007a (label), TC-FR-007b (confirm message) | Medium |
| FR-008 Product disabled | TC-FR-008a (auto-fill filter), TC-FR-008b (badge) | High |
| NFR-001 Insert perf | TC-NFR-001 | High |
| NFR-002 Aggregate perf | TC-NFR-002 | High |
D3) Seed Data
Dataset SD-01: Task cha + 2 Subtask + Materials
Cách tạo:
Option A: SQL Script (khuyến nghị)
sql
-- 1. Tạo project (nếu chưa có test project)
-- Giả sử project_id = 'proj-test-001' đã tồn tại
-- 2. Tạo task cha
INSERT INTO project_task (id, project_id, name, status_id, created_by)
VALUES ('task-parent-001', 'proj-test-001', 'Laser Q-switch tàn nhang - Buổi 1 - KH Test', 'new_branch', 'test-user');
-- 3. Tạo 2 subtask
INSERT INTO project_task (id, project_id, parent_id, name, status_id, created_by)
VALUES
('task-sub-001', 'proj-test-001', 'task-parent-001', 'BS - Laser sắc tố nám', 'new_branch', 'test-user'),
('task-sub-002', 'proj-test-001', 'task-parent-001', 'Chăm sóc da cơ bản', 'new_branch', 'test-user');
-- 4. Tạo materials cho subtask 1
INSERT INTO project_task_material (task_id, product_id, product_name, product_sku, product_unit, quantity, created_by)
VALUES
('task-sub-001', 'prod-serum-001', 'Serum Laser X', 'SP001', 'ml', 2, 'test-user'),
('task-sub-001', 'prod-gel-042', 'Gel làm mát', 'SP042', 'tube', 1, 'test-user');
-- 5. Tạo materials cho subtask 2
INSERT INTO project_task_material (task_id, product_id, product_name, product_sku, product_unit, quantity, created_by)
VALUES
('task-sub-002', 'prod-serum-001', 'Serum Laser X', 'SP001', 'ml', 2, 'test-user'),
('task-sub-002', 'prod-mask-018', 'Mask dưỡng da', 'SP018', 'miếng', 1, 'test-user');Option B: UI Manual
- Login role Manager → Tab Công Việc → Chọn 1 task cha
- Click "Thêm công việc" → tạo subtask "BS - Laser sắc tố nám" → thêm 2 vật tư → Lưu
- Click "Thêm công việc" → tạo subtask "Chăm sóc da cơ bản" → thêm 2 vật tư → Lưu
Dataset SD-02: Task cha có order_materials cũ (backward compat)
sql
-- Task cha có order liên kết (data trước deploy)
-- Giả sử đã có order với order_kind = 'internal_material' và task_id = task cha
-- Dùng data sẵn có trên test env — không cần tạo mới
-- Verify: query project_task WHERE id = X → order_materials phải có dataDataset SD-03: Subtask done + product disabled
sql
-- 1. Subtask done
UPDATE project_task SET status_id = 'done_branch', done_at = now()
WHERE id = 'task-sub-001';
-- 2. Product disabled (giả sử product 'prod-serum-001' bị disable)
-- Chạy trên ecommerce DB:
UPDATE product SET disabled = true WHERE id = 'prod-serum-001';D4) Test Cases
Critical Path Tests
| TC-ID | Tên | Precondition | Steps | Expected | Dataset | Priority |
|---|---|---|---|---|---|---|
| TC-CP-001 | Tạo subtask với auto-fill vật tư + save | SD-01 (project + task cha) | 1. Mở task cha → Click "Thêm công việc" 2. Chọn subtask definition có product_relation 3. Verify auto-fill 4. Bấm Lưu | Subtask tạo thành công + 2 records trong project_task_material | SD-01 | Critical |
| TC-CP-002 | Sửa subtask: thay đổi SL + thêm vật tư mới | SD-01 (subtask có materials) | 1. Click sửa subtask 2. Đổi SL Serum 2→3 3. Search "Mask" → thêm 4. Lưu | SL updated = 3, thêm 1 record mới | SD-01 | Critical |
| TC-CP-003 | Aggregate view cập nhật đúng | SD-01 (2 subtask có materials) | 1. Mở task cha 2. Verify bảng VẬT TƯ DỰ KIẾN | Serum = 4ml (2+2), Gel = 1, Mask = 1 | SD-01 | Critical |
| TC-CP-004 | Xóa subtask → materials cascade + aggregate giảm | SD-01 | 1. Xóa subtask 1 (confirm dialog mention 2 vật tư) 2. Verify aggregate | Aggregate: Serum = 2 (chỉ từ subtask 2), Gel = 0 (không hiện) | SD-01 | Critical |
| TC-CP-005 | End-to-end: tạo + sửa + xóa + verify aggregate | SD-01 | Full flow | Aggregate luôn chính xác sau mỗi thao tác | SD-01 | Critical |
FR Test Cases
| TC-ID | FR | Tên | Steps | Expected | Priority |
|---|---|---|---|---|---|
| TC-FR-001a | FR-001 | Thêm vật tư khi tạo subtask | Mở form tạo subtask → search vật tư → chọn → nhập SL → Lưu | Record inserted, denormalized fields đúng | Critical |
| TC-FR-001b | FR-001 | Search vật tư theo SKU | Search "SP042" | Hiện "Gel làm mát" (SP042) | High |
| TC-FR-002a | FR-002 | Auto-fill có config | Chọn subtask def có 3 product_relation | Bảng fill 3 dòng với đúng product + quantity | Critical |
| TC-FR-002b | FR-002 | Auto-fill không có config | Chọn subtask def không có product_relation | Bảng trống + text "Chưa có vật tư" | High |
| TC-FR-003a | FR-003 | Sửa SL | Mở sửa subtask → đổi SL Serum 2→5 → Lưu | project_task_material.quantity = 5 | Critical |
| TC-FR-003b | FR-003 | Xóa dòng vật tư | Mở sửa subtask → click 🗑 Gel → Lưu | Record Gel bị xóa, chỉ còn Serum | High |
| TC-FR-003c | FR-003 | Thêm khi sửa | Mở sửa → search "Mask" → thêm → Lưu | Thêm 1 record mới, records cũ giữ nguyên | High |
| TC-FR-004 | FR-004 | Readonly khi done | SD-03 (subtask done) → mở sửa | Ẩn 🗑, disable SL input, ẩn search | High |
| TC-FR-005a | FR-005 | Aggregate cộng dồn | SD-01 (2 subtask cùng dùng Serum) | Serum aggregate = 4 (2+2) | Critical |
| TC-FR-005b | FR-005 | Aggregate cập nhật realtime | Sửa subtask 1 SL Serum 2→3 → save → xem task cha | Aggregate Serum = 5 (3+2) | High |
| TC-FR-006a | FR-006 | Backward data cũ | SD-02 (task có order_materials) | Hiện bảng "VẬT TƯ (ĐÃ XUẤT KHO)" với data cũ | High |
| TC-FR-006b | FR-006 | Cả 2 data | Task có order_materials + project_task_material | Hiện 2 sections riêng biệt | High |
| TC-FR-007a | FR-007 | Button label fix | Mở task detail (automate=true) | Button hiện "Thêm công việc" (không phải "Thêm vật tư") | Medium |
| TC-FR-007b | FR-007 | Delete confirm message | Xóa subtask có 3 materials | Dialog hiện "Sẽ xóa 3 vật tư đi kèm" | Medium |
| TC-FR-008a | FR-008 | Auto-fill filter disabled | Product "Serum cũ" disabled → chọn subtask def | Serum cũ KHÔNG xuất hiện trong auto-fill | High |
| TC-FR-008b | FR-008 | Badge "Ngưng KD" | SD-03 (product disabled sau khi lưu) → mở sửa | Badge "Ngưng KD" cạnh tên Serum | High |
NFR Test Cases
| TC-ID | NFR | Steps | Expected | Priority |
|---|---|---|---|---|
| TC-NFR-001 | NFR-001 | Insert 10 materials cho 1 subtask → measure time | Response < 500ms | High |
| TC-NFR-002 | NFR-002 | Task cha có 20 subtask × 5 materials = 100 records → load aggregate | Response < 200ms (dùng denormalized, zero remote join) | High |
Edge Case Test Cases
| TC-ID | EC | Steps | Expected | Priority |
|---|---|---|---|---|
| TC-EC-001 | EC-01 | Chọn subtask def không có product_relation | Bảng trống + "Chưa có vật tư. Tìm kiếm để thêm." | Medium |
| TC-EC-002 | EC-02 | Search "xyz123" (không tồn tại) | Dropdown trống + "Không tìm thấy" | Medium |
| TC-EC-003 | EC-03 | Nhập SL = 0 → Lưu | Validate error inline "SL phải > 0", không cho lưu | High |
| TC-EC-004 | EC-04 | Thêm product đã có trong bảng | Toast warning "Vật tư đã có trong danh sách" | Medium |
| TC-EC-005 | EC-05 | Task cha không có subtask nào có materials | Section "VẬT TƯ DỰ KIẾN" ẩn hoàn toàn | Medium |
| TC-EC-006 | EC-06 | Task cha chỉ có order_materials cũ, không có data mới | Chỉ hiện "VẬT TƯ (ĐÃ XUẤT KHO)", không hiện "VẬT TƯ DỰ KIẾN" | Medium |
| TC-EC-007 | EC-07 | Manual board (automate=false): tạo subtask | MaterialForm hiện, auto-fill không chạy, search hoạt động | Medium |
D5) Entry / Exit Criteria
Entry Criteria
- [ ] Migration
create_project_task_materialđã apply thành công trên test env - [ ] Hasura metadata reload thành công (schema + permissions)
- [ ]
pnpm codegenpass (TypeScript types generated) - [ ] MaterialForm component render được (không error)
- [ ] Test accounts sẵn sàng: Staff, Manager, Admin, Call Center
Exit Criteria
- [ ] 5/5 Critical Path tests pass (TC-CP-001 → TC-CP-005)
- [ ] 16/16 FR tests pass
- [ ] 2/2 NFR tests pass
- [ ] 7/7 Edge case tests pass
- [ ] Backward compat verified (data cũ hiển thị đúng)
- [ ] Tiền tour KHÔNG bị ảnh hưởng (verify trên 1 subtask có materials + tour_money)
- [ ] No P1/P2 bugs open