Skip to content

v3.1 — 30/04/2026

Thay đổiSectionẢnh hưởng
Đồng bộ oracle QA cho đơn đã hủy, badge thanh toán đủ và warning bannerTC-FR-001 / TC-FR-007QA

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.mdD1) Phạm vi kiểm thửD2) Ca kiểm thửD3) Dữ liệu seedD5) Tiêu chí vào / ra. Văn phong: theo templates/_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

FileVai tròNếu xung đột
source-of-truth.mdTruth chuẩn + Phương án đã chốtƯu tiên cao nhất
decision-brief.mdTóm tắt scope/riskĐịnh hướng độ ưu tiên test
prd.mdFR, AC, công thứcƯu tiên ngữ nghĩa nghiệp vụ
ui-spec.md / dev-spec.mdUI contract, API contract, traceabilityTheo truth đã khóa

D1) Phạm vi kiểm thử

FRMô tảƯu tiênTest types
FR-001Section TÀI CHÍNH renderMustFunctional + Permission + Performance + Responsive
FR-002Action backend aggregateMustFunctional + Performance + Edge case
FR-003App Settings field rateMustFunctional + Validation
FR-004Snapshot fixed_cost vào order mớiMustIntegration
FR-005Hook update high-water markMustIntegration + Edge case (refund sign)
FR-006Permission action seedMustMigration + Permission matrix
FR-007Cảnh báo đơn lỗShouldFunctional + 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 đầuKhi thao tácDữ liệu testKỳ vọngƯu tiên
TC-001-01Lễ 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-02Admin 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.000Mở chi tiết đơnSection 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-03Performance test: 100 user concurrent mở 100 đơn khác nhauĐo P95 latencyAPI GetOrderFinancialSummary < 200ms (P95). FE first render < 1.5s P95.P0

Ngoài luồng thành công (BẮT BUỘC):

TCLoạiĐiều kiện ban đầuKhi thao tácDữ liệu testKỳ vọngƯu tiên
TC-001-04Permission deniedUser kt_vien không có cả 2 actionMở chi tiết đơnSection 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-05Permission denied — leak checkUser chỉ có view_financial_summary, gọi API trực tiếp qua DevToolsDevTools → Network → re-issue API requestResponse 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-06API timeoutMock backend timeout > 5sMở đơnSection 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=cancelledMở đơnSection 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=NULLAdmin 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-09Refund làm paid âmĐơn ORD-202604-0150: paid 2tr → hoàn 2.5tr (paid=−500.000)Lễ tân mở đơnHiển thị "Đã thu: −500.000đ" với prefix và negative-value state. KHÔNG ẩn dòng.P1
TC-001-10revenue=0 (đơn lỗi)Đơn ORD-202604-0099 amount=0Admin mở đơnDoanh 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=0Manager mở đơnDòng "Còn nợ" hiện + badge trạng thái "Đã thanh toán" bên cạnh.P1
TC-001-12Permission revoke giữa sessionManager đ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 đầuThao tácKỳ vọng UIKỳ vọng API/dataƯu tiên
TC-PERM-01Lễ tân không có cả 2 actionMở đơnSection ẩnAPI trả 403P0
TC-PERM-02Manager có view_financial_summaryMở đơnHiện 3 dòng summary, KHÔNG hiện 5 dòng PnLResponse chứa revenue/paid/debt, KHÔNG có commission/tour_cost/fixed_cost/profit_estimated/margin_estimatedP0
TC-PERM-03Admin có view_financial_pnlMở đơnHiện đủ 8 dòng + note pendingResponse chứa đủ 8 fieldP0
TC-PERM-04Admin cấp view_financial_pnl cho Manager qua Dynamic Permission UIManager refresh sau cấp quyềnHiện đủ 8 dòngAPI trả đủ 8 fieldP0
TC-PERM-05Admin thu hồi view_financial_pnl của ManagerManager refreshTrở về 3 dòng summaryAPI không trả PnL fieldsP0
TC-PERM-06Manager có quyền ở admin nhưng không ở POSMở đơn ở POS portalSection ẩnAPI check portal hiện tại, không lấy quyền cross-portalP0
TC-PERM-07Manager branch_mode='self_branch' mở đơn của branch khácMở /e/service-order/{id-of-other-branch}(route guard chặn trước)API trả 403P0

TC-FR-002: Action backend GetOrderFinancialSummary

TCLoạiĐiều kiện ban đầuKhi thao tácDữ liệu testKỳ vọngƯu tiên
TC-002-01Aggregate đúngĐơn có 3 parent invoice (1tr, 800k, 200k complete) + 2 sub-invoice (50k, 30k complete)Gọi actionpaid=2.000.000 (chỉ parent), 200.080.000 không tínhP0
TC-002-02Boundary: 0 invoiceĐơn mới chưa thanh toánGọi actionpaid=0 (không lỗi NULL)P0
TC-002-03Boundary: 50 invoiceĐơn lớn có 50 parent invoiceGọi actionAggregate đúng tổng, latency < 200msP1
TC-002-04Negative invoiceĐơn có 1 invoice 2tr complete + 1 invoice -500k complete (refund)Gọi actionpaid=1.500.000 (cộng đại số)P0 (RSK-001 blocker)
TC-002-05Cross-DB tour costĐơn có 2 order_items, mỗi item có 2 KTV với tour_moneyGọi actionAggregate tour_cost đúng SUM, JOIN cross-DB ecommerce ↔ project hoạt độngP0
TC-002-06Order không tồn tạiorder_id ngẫu nhiênGọi actionHTTP 404, error code ORDER_NOT_FOUNDP0

TC-FR-003: App Settings rate

TCLoạiĐiều kiện ban đầuKhi thao tácDữ liệu testKỳ vọngƯu tiên
TC-003-01HappyAdmin đã đăng nhậpLưu rate=15.50input "15.50"DB có app_setting.order.fixed_cost.rate = 15.50. Toast "Đã lưu thay đổi".P0
TC-003-02Boundary lowAdmin lưu rate=0input "0"Lưu OK. Đơn mới có fixed_cost_rate=0. UI hiện "0đ" cho fixed_cost.P1
TC-003-03Boundary highAdmin lưu rate=100input "100"Lưu OK.P1
TC-003-04NegativeAdmin nhập rate=-1input "-1" → bấm LưuChặn lưu, hiện inline error "Tỷ lệ phải trong khoảng 0-100%". KHÔNG ghi DB.P0
TC-003-05OverAdmin nhập rate=101input "101"Chặn lưu, error tương tự.P0
TC-003-06Decimals overflowAdmin 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

TCLoạiĐiều kiện ban đầuKhi thao tácKỳ vọngƯu tiên
TC-004-01HappySetting rate=15.50Lễ tân tạo đơn mớiDB: order.fixed_cost_rate=15.50, fixed_cost_amount=0 (chưa thu)P0
TC-004-02NULLSetting NULL (chưa cấu hình)Tạo đơnDB: order.fixed_cost_rate=NULL, fixed_cost_amount=NULLP0
TC-004-03Race conditionSetting đang đổi từ 15→20 trong khi tạo đơnTạo đơn 100 lần đồng thời với raceMỗ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ửaP1

TC-FR-005: Hook invoice_complete update high-water

TCLoạiĐiều kiện ban đầuKhi thao tácKỳ vọngƯu tiên
TC-005-01Happy: 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ạyDB: fixed_cost_amount=150.000 (15% × 1tr)P0
TC-005-02High-water mark: thanh toán thêmTiếp TC-005-01. Khách thanh toán thêm parent invoice 500.000 (complete, paid total = 1.5tr)Hook chạyDB: fixed_cost_amount=225.000 (15% × 1.5tr, > 150k)P0
TC-005-03High-water mark: refund KHÔNG giảmTiếp TC-005-02. Khách hoàn parent invoice -500.000 (complete, paid total = 1tr)Hook chạyDB: fixed_cost_amount=225.000 (giữ high-water, KHÔNG giảm về 150k)P0 (DEC-012)
TC-005-04Skip nếu rate=NULLĐơn cũ rate=NULL. Thanh toán completeHook chạyDB: fixed_cost_amount=NULL (không update)P0
TC-005-05Skip sub-invoiceĐơn rate=15. Sub-invoice (parent_id IS NOT NULL) completeHookDB: fixed_cost_amount KHÔNG đổi (chỉ parent trigger)P1
TC-005-06IdempotentHook chạy lại với cùng paidRun 2 lầnDB: fixed_cost_amount không đổiP1

TC-FR-006: Permission action seed

TCLoạiĐiều kiện ban đầuKhi thao tácKỳ vọngƯu tiên
TC-006-01Migration upFresh DBRun migration upDB: module_permission_action có 2 dòng view_financial_summary + view_financial_pnl cho service_order. role_module có seed default cho 5 roleP0
TC-006-02IdempotentDB đã có 2 actionRun migration up lần 2KHÔNG insert trùng (ON CONFLICT skip)P0
TC-006-03Migration downDB đã cóRun migration down2 action bị xóa, role_module revertP0
TC-006-04Dynamic Permission UI2 action đã seedAdmin mở Permission MatrixThấy 2 action mới trong tab service_order của portal adminP0

TC-FR-007: Cảnh báo đơn lỗ

TCLoạiĐiều kiện ban đầuKhi thao tácKỳ vọngƯu tiên
TC-007-01HappyAdmin có pnl. Đơn có profit_estimated=-250.000 (revenue 1tr, commission 800k, tour 300k, fixed 150k)Mở đơnWarning 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 đóngP0
TC-007-02Permission deniedManager chỉ có summary, đơn lỗ trênMở đơnKHÔNG hiện banner (Manager không có pnl)P0
TC-007-03Profit = 0Admin mở đơn profit_estimated=0Mở đơnKHÔNG hiện bannerP1
TC-007-04Đóng bannerAdmin bấm ×Banner fade-out 150ms, ẩn cho sessionP1
TC-007-05Reload đơn sau đóngAdmin đóng banner → mở đơn khác → mở lại đơn lỗBanner hiện lại (không persist localStorage)P1
TC-007-06Banner copy chứa "vật tư"Admin mở đơn lỗVerify textCopy phải có "Chưa gồm chi phí vật tư" để tránh user hiểu nhầmP0 (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 | 225000

Dataset 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 = -250000

Dataset 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) = -500000

Dataset 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

AccountRolePermissionBranch
letan_q1@diva.testreceptionistview_financial_summaryCN Quận 1
manager_q1@diva.testmanagerview_financial_summaryCN Quận 1 (self_branch)
manager_q2@diva.testmanagerview_financial_summaryCN Quận 2
ketoan@diva.testaccountantview_financial_summaryAll
admin@diva.testadminview_financial_summary + view_financial_pnlAll
bod@diva.testbodview_financial_summary + view_financial_pnlAll
kt_vien@diva.testtechnician(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.

FRTC-IDCoverageTrạng thái
FR-001TC-001-01 đến TC-001-1212 TC (3 happy + 9 negative/edge)
FR-001TC-PERM-01 đến TC-PERM-077 TC permission
FR-002TC-002-01 đến TC-002-066 TC
FR-003TC-003-01 đến TC-003-066 TC
FR-004TC-004-01 đến TC-004-033 TC
FR-005TC-005-01 đến TC-005-066 TC
FR-006TC-006-01 đến TC-006-044 TC
FR-007TC-007-01 đến TC-007-066 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 GetOrderFinancialSummary P95 < 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