Appearance
v3.1 — 30/04/2026
| Thay đổi | Section | Ảnh hưởng |
|---|---|---|
| Đồng bộ oracle QA cho đơn đã hủy, badge thanh toán đủ và warning banner | TC-FR-001 / TC-FR-007 | QA |
Kế hoạch kiểm thử (QA Test Plan) — Tổng hợp tài chính đơn hàng
Tham chiếu: PRD v3.0 | Ngày: 30/04/2026
Mục đích: chuyển FR thành phạm vi test, dữ liệu seed và test case để QA/UAT bám đúng. Đọc trước:
decision-brief.md→D1) Phạm vi kiểm thử→D2) Ca kiểm thử→D3) Dữ liệu seed→D5) Tiêu chí vào / ra. Văn phong: theotemplates/_LANGUAGE_RULES.md+_STYLE_GUIDE.md. Test case rõ điều kiện ban đầu + thao tác + dữ liệu test + kỳ vọng + ngoại lệ. Ngữ cảnh thẩm mỹ viện.
Tài liệu đầu vào chuẩn
| File | Vai trò | Nếu xung đột |
|---|---|---|
source-of-truth.md | Truth chuẩn + Phương án đã chốt | Ưu tiên cao nhất |
decision-brief.md | Tóm tắt scope/risk | Định hướng độ ưu tiên test |
prd.md | FR, AC, công thức | Ưu tiên ngữ nghĩa nghiệp vụ |
ui-spec.md / dev-spec.md | UI contract, API contract, traceability | Theo truth đã khóa |
D1) Phạm vi kiểm thử
| FR | Mô tả | Ưu tiên | Test types |
|---|---|---|---|
| FR-001 | Section TÀI CHÍNH render | Must | Functional + Permission + Performance + Responsive |
| FR-002 | Action backend aggregate | Must | Functional + Performance + Edge case |
| FR-003 | App Settings field rate | Must | Functional + Validation |
| FR-004 | Snapshot fixed_cost vào order mới | Must | Integration |
| FR-005 | Hook update high-water mark | Must | Integration + Edge case (refund sign) |
| FR-006 | Permission action seed | Must | Migration + Permission matrix |
| FR-007 | Cảnh báo đơn lỗ | Should | Functional + Permission |
D2) Ca kiểm thử
Mỗi FR có ≥1 TC ngoài luồng thành công. Format VN cho mọi expected value.
TC-FR-001: Section TÀI CHÍNH render
Luồng thành công:
| TC | Điều kiện ban đầu | Khi thao tác | Dữ liệu test | Kỳ vọng | Ưu tiên |
|---|---|---|---|---|---|
| TC-001-01 | Lễ tân Nguyễn Thị Lan (role receptionist, branch CN Quận 1, có view_financial_summary). Đơn ORD-202604-0123 ở CN Quận 1: amount=3.500.000, paid=1.500.000, debt=2.000.000. | Mở /e/service-order/ORD-202604-0123 | (đã có data) | Section "TÀI CHÍNH" render trong sidebar trái với 3 dòng: Doanh thu 3.500.000đ, Đã thu 1.500.000đ, Còn nợ 2.000.000đ. KHÔNG hiện 5 dòng PnL. | P0 |
| TC-001-02 | Admin Trần Quốc Anh (role admin, có view_financial_pnl). Đơn trên + commission 350.000, tour_money 180.000, fixed_cost_amount=225.000 | Mở chi tiết đơn | — | Section render đủ 8 dòng + note pending vật tư cuối section. KHÔNG có banner cảnh báo lỗ (profit > 0). Tỷ suất 78,43%. | P0 |
| TC-001-03 | Performance test: 100 user concurrent mở 100 đơn khác nhau | Đo P95 latency | — | API GetOrderFinancialSummary < 200ms (P95). FE first render < 1.5s P95. | P0 |
Ngoài luồng thành công (BẮT BUỘC):
| TC | Loại | Điều kiện ban đầu | Khi thao tác | Dữ liệu test | Kỳ vọng | Ưu tiên |
|---|---|---|---|---|---|---|
| TC-001-04 | Permission denied | User kt_vien không có cả 2 action | Mở chi tiết đơn | — | Section TÀI CHÍNH KHÔNG mount (DOM không có element .financial-summary-section). API request trả 403, FE KHÔNG show error toast. | P0 |
| TC-001-05 | Permission denied — leak check | User chỉ có view_financial_summary, gọi API trực tiếp qua DevTools | DevTools → Network → re-issue API request | — | Response JSON KHÔNG chứa các key commission, tour_cost, fixed_cost, profit_estimated, margin_estimated (least-data). Verify bằng JSON.stringify. | P0 |
| TC-001-06 | API timeout | Mock backend timeout > 5s | Mở đơn | — | Section hiện inline error "Không thể tải tài chính. Vui lòng thử lại." + nút "Thử lại". KHÔNG có error toast popup. | P1 |
| TC-001-07 | Đơn đã hủy | Đơn ORD-202604-0099 status=cancelled | Mở đơn | — | Section render theo quyền hiện tại với badge trạng thái "Đơn đã hủy - chỉ đối soát" trên đầu section. Mọi số readonly. | P1 |
| TC-001-08 | Đơn cũ (rate=NULL) | Đơn ORD-202504-0001 (tạo trước cấu hình rate), fixed_cost_rate=NULL | Admin mở đơn (có pnl) | — | Section render 7 dòng (ẩn dòng "Chi phí cố định"). Tooltip ⓘ tiêu đề ghi "Đơn này tạo trước khi cấu hình chi phí cố định." | P1 |
| TC-001-09 | Refund làm paid âm | Đơn ORD-202604-0150: paid 2tr → hoàn 2.5tr (paid=−500.000) | Lễ tân mở đơn | — | Hiển thị "Đã thu: −500.000đ" với prefix − và negative-value state. KHÔNG ẩn dòng. | P1 |
| TC-001-10 | revenue=0 (đơn lỗi) | Đơn ORD-202604-0099 amount=0 | Admin mở đơn | — | Doanh thu hiện —, Tỷ suất hiện — (không tính được). KHÔNG hiện 0% hay NaN. | P2 |
| TC-001-11 | Đơn đã thanh toán đủ (debt=0) | Đơn ORD-202604-0200, paid = amount = 5.000.000, debt=0 | Manager mở đơn | — | Dòng "Còn nợ" hiện 0đ + badge trạng thái "Đã thanh toán" bên cạnh. | P1 |
| TC-001-12 | Permission revoke giữa session | Manager đang xem đơn (có summary). Admin thu hồi action ở Permission Matrix. | Manager refresh trang (F5) | — | Section ẨN sau refetch tiếp theo. KHÔNG có error toast hay banner thông báo. | P1 |
Coverage Dynamic Permission:
| TC | Điều kiện ban đầu | Thao tác | Kỳ vọng UI | Kỳ vọng API/data | Ưu tiên |
|---|---|---|---|---|---|
| TC-PERM-01 | Lễ tân không có cả 2 action | Mở đơn | Section ẩn | API trả 403 | P0 |
| TC-PERM-02 | Manager có view_financial_summary | Mở đơn | Hiện 3 dòng summary, KHÔNG hiện 5 dòng PnL | Response chứa revenue/paid/debt, KHÔNG có commission/tour_cost/fixed_cost/profit_estimated/margin_estimated | P0 |
| TC-PERM-03 | Admin có view_financial_pnl | Mở đơn | Hiện đủ 8 dòng + note pending | Response chứa đủ 8 field | P0 |
| TC-PERM-04 | Admin cấp view_financial_pnl cho Manager qua Dynamic Permission UI | Manager refresh sau cấp quyền | Hiện đủ 8 dòng | API trả đủ 8 field | P0 |
| TC-PERM-05 | Admin thu hồi view_financial_pnl của Manager | Manager refresh | Trở về 3 dòng summary | API không trả PnL fields | P0 |
| TC-PERM-06 | Manager có quyền ở admin nhưng không ở POS | Mở đơn ở POS portal | Section ẩn | API check portal hiện tại, không lấy quyền cross-portal | P0 |
| TC-PERM-07 | Manager branch_mode='self_branch' mở đơn của branch khác | Mở /e/service-order/{id-of-other-branch} | (route guard chặn trước) | API trả 403 | P0 |
TC-FR-002: Action backend GetOrderFinancialSummary
| TC | Loại | Điều kiện ban đầu | Khi thao tác | Dữ liệu test | Kỳ vọng | Ưu tiên |
|---|---|---|---|---|---|---|
| TC-002-01 | Aggregate đúng | Đơn có 3 parent invoice (1tr, 800k, 200k complete) + 2 sub-invoice (50k, 30k complete) | Gọi action | — | paid=2.000.000 (chỉ parent), 200.080.000 không tính | P0 |
| TC-002-02 | Boundary: 0 invoice | Đơn mới chưa thanh toán | Gọi action | — | paid=0 (không lỗi NULL) | P0 |
| TC-002-03 | Boundary: 50 invoice | Đơn lớn có 50 parent invoice | Gọi action | — | Aggregate đúng tổng, latency < 200ms | P1 |
| TC-002-04 | Negative invoice | Đơn có 1 invoice 2tr complete + 1 invoice -500k complete (refund) | Gọi action | — | paid=1.500.000 (cộng đại số) | P0 (RSK-001 blocker) |
| TC-002-05 | Cross-DB tour cost | Đơn có 2 order_items, mỗi item có 2 KTV với tour_money | Gọi action | — | Aggregate tour_cost đúng SUM, JOIN cross-DB ecommerce ↔ project hoạt động | P0 |
| TC-002-06 | Order không tồn tại | order_id ngẫu nhiên | Gọi action | — | HTTP 404, error code ORDER_NOT_FOUND | P0 |
TC-FR-003: App Settings rate
| TC | Loại | Điều kiện ban đầu | Khi thao tác | Dữ liệu test | Kỳ vọng | Ưu tiên |
|---|---|---|---|---|---|---|
| TC-003-01 | Happy | Admin đã đăng nhập | Lưu rate=15.50 | input "15.50" | DB có app_setting.order.fixed_cost.rate = 15.50. Toast "Đã lưu thay đổi". | P0 |
| TC-003-02 | Boundary low | Admin lưu rate=0 | input "0" | — | Lưu OK. Đơn mới có fixed_cost_rate=0. UI hiện "0đ" cho fixed_cost. | P1 |
| TC-003-03 | Boundary high | Admin lưu rate=100 | input "100" | — | Lưu OK. | P1 |
| TC-003-04 | Negative | Admin nhập rate=-1 | input "-1" → bấm Lưu | — | Chặn lưu, hiện inline error "Tỷ lệ phải trong khoảng 0-100%". KHÔNG ghi DB. | P0 |
| TC-003-05 | Over | Admin nhập rate=101 | input "101" | — | Chặn lưu, error tương tự. | P0 |
| TC-003-06 | Decimals overflow | Admin nhập rate=15.555 (3 decimal) | — | Chặn input ở mức 2 decimal hoặc reject khi save: "Chỉ được tối đa 2 chữ số thập phân" | P1 |
TC-FR-004: Snapshot fixed_cost_rate vào order mới
| TC | Loại | Điều kiện ban đầu | Khi thao tác | Kỳ vọng | Ưu tiên |
|---|---|---|---|---|---|
| TC-004-01 | Happy | Setting rate=15.50 | Lễ tân tạo đơn mới | DB: order.fixed_cost_rate=15.50, fixed_cost_amount=0 (chưa thu) | P0 |
| TC-004-02 | NULL | Setting NULL (chưa cấu hình) | Tạo đơn | DB: order.fixed_cost_rate=NULL, fixed_cost_amount=NULL | P0 |
| TC-004-03 | Race condition | Setting đang đổi từ 15→20 trong khi tạo đơn | Tạo đơn 100 lần đồng thời với race | Mỗi đơn snapshot value đọc tại thời điểm INSERT (không inconsistent). Acceptable: hoặc 15 hoặc 20, không nửa nửa | P1 |
TC-FR-005: Hook invoice_complete update high-water
| TC | Loại | Điều kiện ban đầu | Khi thao tác | Kỳ vọng | Ưu tiên |
|---|---|---|---|---|---|
| TC-005-01 | Happy: thanh toán lần đầu | Đơn rate=15, fixed_cost_amount=0. Khách thanh toán parent invoice 1.000.000 (complete) | Hook chạy | DB: fixed_cost_amount=150.000 (15% × 1tr) | P0 |
| TC-005-02 | High-water mark: thanh toán thêm | Tiếp TC-005-01. Khách thanh toán thêm parent invoice 500.000 (complete, paid total = 1.5tr) | Hook chạy | DB: fixed_cost_amount=225.000 (15% × 1.5tr, > 150k) | P0 |
| TC-005-03 | High-water mark: refund KHÔNG giảm | Tiếp TC-005-02. Khách hoàn parent invoice -500.000 (complete, paid total = 1tr) | Hook chạy | DB: fixed_cost_amount=225.000 (giữ high-water, KHÔNG giảm về 150k) | P0 (DEC-012) |
| TC-005-04 | Skip nếu rate=NULL | Đơn cũ rate=NULL. Thanh toán complete | Hook chạy | DB: fixed_cost_amount=NULL (không update) | P0 |
| TC-005-05 | Skip sub-invoice | Đơn rate=15. Sub-invoice (parent_id IS NOT NULL) complete | Hook | DB: fixed_cost_amount KHÔNG đổi (chỉ parent trigger) | P1 |
| TC-005-06 | Idempotent | Hook chạy lại với cùng paid | Run 2 lần | DB: fixed_cost_amount không đổi | P1 |
TC-FR-006: Permission action seed
| TC | Loại | Điều kiện ban đầu | Khi thao tác | Kỳ vọng | Ưu tiên |
|---|---|---|---|---|---|
| TC-006-01 | Migration up | Fresh DB | Run migration up | DB: module_permission_action có 2 dòng view_financial_summary + view_financial_pnl cho service_order. role_module có seed default cho 5 role | P0 |
| TC-006-02 | Idempotent | DB đã có 2 action | Run migration up lần 2 | KHÔNG insert trùng (ON CONFLICT skip) | P0 |
| TC-006-03 | Migration down | DB đã có | Run migration down | 2 action bị xóa, role_module revert | P0 |
| TC-006-04 | Dynamic Permission UI | 2 action đã seed | Admin mở Permission Matrix | Thấy 2 action mới trong tab service_order của portal admin | P0 |
TC-FR-007: Cảnh báo đơn lỗ
| TC | Loại | Điều kiện ban đầu | Khi thao tác | Kỳ vọng | Ưu tiên |
|---|---|---|---|---|---|
| TC-007-01 | Happy | Admin có pnl. Đơn có profit_estimated=-250.000 (revenue 1tr, commission 800k, tour 300k, fixed 150k) | Mở đơn | Warning banner trong section TÀI CHÍNH, dưới tiêu đề, trên dòng Doanh thu: "Đơn lỗ tạm tính: −250.000đ. Chưa gồm chi phí vật tư." + icon cảnh báo + nút đóng | P0 |
| TC-007-02 | Permission denied | Manager chỉ có summary, đơn lỗ trên | Mở đơn | KHÔNG hiện banner (Manager không có pnl) | P0 |
| TC-007-03 | Profit = 0 | Admin mở đơn profit_estimated=0 | Mở đơn | KHÔNG hiện banner | P1 |
| TC-007-04 | Đóng banner | Admin bấm × | — | Banner fade-out 150ms, ẩn cho session | P1 |
| TC-007-05 | Reload đơn sau đóng | Admin đóng banner → mở đơn khác → mở lại đơn lỗ | — | Banner hiện lại (không persist localStorage) | P1 |
| TC-007-06 | Banner copy chứa "vật tư" | Admin mở đơn lỗ | Verify text | Copy phải có "Chưa gồm chi phí vật tư" để tránh user hiểu nhầm | P0 (DEC-015) |
D3) Dữ liệu seed kiểm thử
Dataset DS-001: Khách + đơn căn bản
sql
-- Khách hàng
INSERT INTO customer (id, name, phone, branch_id) VALUES
('11111111-1111-1111-1111-111111111111', 'Nguyễn Thị Lan', '0901234567', 'cn-quan-1'),
('22222222-2222-2222-2222-222222222222', 'Trần Mỹ Anh', '0902345678', 'cn-quan-1');
-- Đơn dịch vụ ORD-202604-0123 (3.5tr, đã thu 1.5tr, còn nợ 2tr)
INSERT INTO ecommerce.order (id, code, customer_id, branch_id, amount, debt_amount, status, fixed_cost_rate, fixed_cost_amount) VALUES
('aaaa1111-...', 'ORD-202604-0123', '11111111-...', 'cn-quan-1', 3500000, 2000000, 'confirmed', 15.50, 225000);
-- Parent invoice complete: 1tr + 500k = 1.5tr
INSERT INTO ecommerce.invoice (id, order_id, parent_id, customer_paid_amount, status) VALUES
('inv-001', 'aaaa1111-...', NULL, 1000000, 'invoice_completed'),
('inv-002', 'aaaa1111-...', NULL, 500000, 'invoice_completed');
-- Sub-invoice (sẽ bị skip): 50k
INSERT INTO ecommerce.invoice (id, order_id, parent_id, customer_paid_amount, status) VALUES
('inv-003', 'aaaa1111-...', 'inv-001', 50000, 'invoice_completed');
-- Order commissions: 200k + 150k = 350k
INSERT INTO ecommerce.order_commission (order_id, amount) VALUES
('aaaa1111-...', 200000),
('aaaa1111-...', 150000);
-- Order items + KTV tour: 100k + 80k = 180k
INSERT INTO ecommerce.order_item (id, order_id) VALUES
('item-001', 'aaaa1111-...'),
('item-002', 'aaaa1111-...');
INSERT INTO project.project_task_assignee (order_item_id, tour_money) VALUES
('item-001', 100000),
('item-002', 80000);
-- Verify
SELECT
o.code, o.amount, o.debt_amount,
(SELECT SUM(customer_paid_amount) FROM ecommerce.invoice WHERE order_id=o.id AND parent_id IS NULL AND status='invoice_completed') AS paid,
(SELECT SUM(amount) FROM ecommerce.order_commission WHERE order_id=o.id) AS commission,
o.fixed_cost_amount
FROM ecommerce.order o WHERE o.code='ORD-202604-0123';
-- Expected: 3500000 | 2000000 | 1500000 | 350000 | 225000Dataset DS-002: Đơn lỗ (cho TC-007-01)
sql
-- Đơn 1tr, commission 800k, tour 300k, fixed 150k → profit = -250k
INSERT INTO ecommerce.order (id, code, amount, fixed_cost_rate, fixed_cost_amount, ...) VALUES
('bbbb2222-...', 'ORD-202604-0150', 1000000, 15.00, 150000, ...);
INSERT INTO ecommerce.order_commission (order_id, amount) VALUES ('bbbb2222-...', 800000);
INSERT INTO ecommerce.order_item (id, order_id) VALUES ('item-201', 'bbbb2222-...');
INSERT INTO project.project_task_assignee (order_item_id, tour_money) VALUES ('item-201', 300000);
-- Verify profit_estimated = 1000000 - 800000 - 300000 - 150000 = -250000Dataset DS-003: Negative invoice (cho TC-001-09, TC-002-04)
sql
-- Đơn ORD-202604-0150b: thanh toán 2tr rồi hoàn 2.5tr → paid = -500k
INSERT INTO ecommerce.invoice (order_id, parent_id, customer_paid_amount, status) VALUES
('cccc3333-...', NULL, 2000000, 'invoice_completed'),
('cccc3333-...', NULL, -2500000, 'invoice_completed'); -- refund
-- Verify: paid aggregate = 2000000 + (-2500000) = -500000Dataset DS-004: Đơn cũ rate=NULL (cho TC-001-08, TC-005-04)
sql
-- Đơn tạo trước khi cấu hình rate
INSERT INTO ecommerce.order (id, code, amount, fixed_cost_rate, fixed_cost_amount, ...) VALUES
('dddd4444-...', 'ORD-202504-0001', 2000000, NULL, NULL, ...);Test accounts
| Account | Role | Permission | Branch |
|---|---|---|---|
letan_q1@diva.test | receptionist | view_financial_summary | CN Quận 1 |
manager_q1@diva.test | manager | view_financial_summary | CN Quận 1 (self_branch) |
manager_q2@diva.test | manager | view_financial_summary | CN Quận 2 |
ketoan@diva.test | accountant | view_financial_summary | All |
admin@diva.test | admin | view_financial_summary + view_financial_pnl | All |
bod@diva.test | bod | view_financial_summary + view_financial_pnl | All |
kt_vien@diva.test | technician | (chưa cấp 2 action) | CN Quận 1 |
D4) Truy vết
Canonical traceability ở Dev Spec C11. Bảng dưới là view QA.
| FR | TC-ID | Coverage | Trạng thái |
|---|---|---|---|
| FR-001 | TC-001-01 đến TC-001-12 | 12 TC (3 happy + 9 negative/edge) | |
| FR-001 | TC-PERM-01 đến TC-PERM-07 | 7 TC permission | |
| FR-002 | TC-002-01 đến TC-002-06 | 6 TC | |
| FR-003 | TC-003-01 đến TC-003-06 | 6 TC | |
| FR-004 | TC-004-01 đến TC-004-03 | 3 TC | |
| FR-005 | TC-005-01 đến TC-005-06 | 6 TC | |
| FR-006 | TC-006-01 đến TC-006-04 | 4 TC | |
| FR-007 | TC-007-01 đến TC-007-06 | 6 TC |
Tổng: 50 test case (8 FR × ~6 TC trung bình)
D5) Tiêu chí vào / ra
Tiêu chí vào (bắt đầu test)
- [ ] BE deploy xong: migration up + Hasura metadata applied
- [ ] FE deploy xong staging
- [ ] Seed data DS-001 đến DS-004 đã insert
- [ ] Test accounts đã tạo + cấu hình permission đúng default seed
- [ ] App Settings rate set 15.50 (cho TC-004-01)
- [ ] Negative invoice handler đã verify sign behavior (RSK-001) — BLOCKER
- [ ] UI Spec B0 As-Is Inventory + Delta Contract đã có (đã pass G3.7)
- [ ]
_consistency-matrix.mdđã sinh và 7 ma trận pass
Tiêu chí ra (kết thúc test)
- [ ] Tất cả P0 test case PASS (không exception)
- [ ] Tất cả P1 test case PASS hoặc có waiver từ PO
- [ ] P2 test case: document known issue nếu FAIL
- [ ] Performance: API
GetOrderFinancialSummaryP95 < 200ms (load test 100 concurrent) - [ ] No leak data: TC-001-05 verify response không chứa PnL field cho user summary-only
- [ ] No regression: smoke test các tab cũ (Thanh toán/Hoa hồng/Ghi chú) hoạt động bình thường
- [ ] No critical/major bug đang open