Skip to content

v3.1 — 30/04/2026

Thay đổiSectionẢnh hưởng
Đồng bộ wording cảnh báo lỗ theo warning banner/design token intentFR-007PO + UI/UX + QA
Làm rõ đơn đã hủy hiển thị theo đúng quyềnFR-001FE + QA

Tổng hợp tài chính đơn hàng

Phiên bản: 3.0 Ngày: 30/04/2026 Tác giả: PO/BA Loại: Tính năng mới (trên màn hiện hữu) Độ phức tạp: M Module: ecommerce + settings + permission


Mục đích: chốt cam kết nghiệp vụ — phạm vi, FR/AC, công thức, rủi ro cho feature tổng hợp tài chính 1 đơn dịch vụ. Đọc trước: decision-brief.mdTóm tắt điều hànhZ) Nhật ký quyết địnhA4) Yêu cầu chức năngA8) Công thức nghiệp vụ. Văn phong: theo templates/_LANGUAGE_RULES.md + _STYLE_GUIDE.md.

Lịch sử thay đổi

Phiên bảnNgàyTác giảThay đổi
3.030/04/2026PO/BAÁp template po-ba-workflow v3

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
evidence-pack.mdBằng chứng code/screen/configƯu tiên bằng chứng trước assumption
decision-brief.mdCửa vào packageBrief tóm tắt; PRD giữ contract chi tiết

Quy tắc công thức: A8 là canonical; dev-spec C3 chỉ ghi triển khai SQL.

Hướng dẫn đọc

Đối tượngSection cần đọc
PO/BAdecision-brief.md → A0 → Z → A4 → A8
Sếp / BODdecision-brief.md → TL;DR → A0
Tech Leaddecision-brief.md → A0 → Z → Dev Spec C1-C5
UI/UX / FEdecision-brief.md → A4 → UI Spec
BE DevA4/A8 → Dev Spec C1-C11
QAA4 → QA Plan D1-D5

Tóm tắt điều hành (TL;DR)

Lễ tân/Manager/Kế toán xem tổng quan tài chính 1 đơn dịch vụ (doanh thu, đã thu, còn nợ, chi phí, lợi nhuận tạm tính) ngay trong sidebar trái màn chi tiết đơn /e/service-order/:id, giảm thời gian tra cứu từ 3-5 phút xuống dưới 5 giây. Phân quyền field-level qua Dynamic Permission: summary view cho Manager, full P&L cho Admin/BOD.

Quyết định còn mở

PDCâu hỏiLựa chọn / khuyến nghịPhụ tráchHạnTrạng thái
PD-001Đơn đã hủy có cộng tỷ suất lợi nhuận chi nhánh?A) Không cộng (default) B) Cộng theo trạng thái pre-cancel. Khuyến nghị: APO + BOD02/05/2026Open
PD-002Tour cost include phần đã trả KTV chưa?A) Include theo tour_money field B) Chỉ include khi tour_paid_at IS NOT NULL. Khuyến nghị: ATL + Kế toán03/05/2026Open

Backlog giai đoạn 2 (ngoài phạm vi hiện tại)

#Tính năngLý do defer
1Chi phí vật tư cộng vào P&LĐịnh lượng vật tư chưa khóa
2Export Excel financial summaryDay-1 chỉ on-screen
3Notification cảnh báo lỗ qua SMS/EmailTránh spam khi vật tư chưa khóa
4Per-branch fixed cost rateDay-1 system-wide đủ dùng
5Tài chính cho đơn product/prepaidDay-1 chỉ service order

Z) Nhật ký quyết định

IDNhómQuyết địnhLý doNgàyTrạng thái
DEC-001Kiến trúcBuild action GetOrderFinancialSummary, không aggregate FE, không DB viewA) Action: cross-DB OK, performance kiểm soát ở BE. B) FE aggregate: thiếu context, không scale. C) DB view: cross-DB phức tạp, khó migrate. → A30/04Locked
DEC-002UXSection TÀI CHÍNH chèn trong sidebar trái, trước DỮ LIỆUA) Sidebar trái: luôn nhìn thấy khi đổi tab. B) Tab mới: tăng số tab, lệch khỏi vận hành "scan nhanh". → A30/04Locked
DEC-003Phân quyềnDynamic Permission: view_financial_summary (summary) + view_financial_pnl (full P&L)A) Dynamic Permission: linh hoạt cấp/thu hồi qua UI. B) Hard-code role: phải sửa code mỗi lần thay đổi. → A30/04Locked
DEC-006Nghiệp vụChi phí vật tư trạng thái Đang phát triển, không cộng P&LĐịnh lượng vật tư chưa khóa; cộng số sai làm Manager/Admin ra quyết định sai30/04Locked
DEC-012Kế toánfixed_cost_amount là high-water mark, refund không giảmA) High-water: phản ánh đúng overhead đã phát sinh. B) Tỷ lệ với thực thu thời điểm: refund làm giảm overhead — sai bản chất kế toán. → A30/04Locked
DEC-015UXCảnh báo lỗ chỉ cho user có view_financial_pnl; copy ghi rõ "Chưa gồm chi phí vật tư"Tránh user không có quyền đầy đủ thấy số sai30/04Locked
DEC-016UIKHÔNG tạo màn permission riêng; reuse Dynamic Permission UITránh duplicate UI permission management30/04Locked

A) PRD

A0) Tổng quan tính năng

Ý tưởng cốt lõi: Lễ tân/Manager mở 1 đơn dịch vụ → thấy ngay 8 chỉ số tài chính cốt lõi (doanh thu, đã thu, còn nợ, chi phí cố định, hoa hồng, tour cost, lợi nhuận tạm tính, tỷ suất) trong sidebar trái — không phải tự cộng từ nhiều tab.

Kiến trúc tổng thể:

[Frontend - OrderDetail Page]

       ├── OrderReceiverInfo (sidebar 404px)
       │   ├── Header / status (giữ nguyên)
       │   ├── ACCOUNT / CONTACT (giữ nguyên)
       │   ├── HOA HỒNG / BRANCH / PROMOTION (giữ nguyên)
       │   ├── 🆕 SECTION TÀI CHÍNH ──────┐
       │   │       (Permission gating)    │
       │   └── DỮ LIỆU / LIÊN KẾT (giữ)  │
       │                                   │
       └── Tabs (Thanh toán, Dịch vụ...)   │

[Backend - ecommerce-api]                  │
   GetOrderFinancialSummary action ◀──────┘

       ├── Aggregate parent invoices (paid)
       ├── order.debt_amount (computed)
       ├── order_commissions (commission)
       ├── project_task_assignee.tour_money (tour cost)
       ├── order.fixed_cost_amount (high-water mark)
       └── Pending: inventory capture (vật tư - Phase 2)

[App Settings - SystemTable]
   AppSettingGroups.order
       └── 🆕 fixed_cost { rate: NUMERIC }
              snapshot vào order.fixed_cost_rate khi tạo đơn

[Hook: invoice_complete event]
   Update order.fixed_cost_amount = max(current, new_calc)

Mapping FR ↔ SCR ↔ Dev:

KhốiFRSCRDev section
Section sidebar TÀI CHÍNHFR-001SCR-01C5 (action), C6 (component)
Action backend aggregateFR-002C5 (action handler)
App Settings fixed costFR-003SCR-02C4 (settings struct), C6 (FE editor)
Snapshot khi tạo đơnFR-004C5 (create_order.go)
Hook invoice_complete high-waterFR-005C5 (event handler)
Permission action + seedFR-006C8 (security + migration)
Cảnh báo đơn lỗFR-007SCR-01C6 (component sub-section)

Quyết định chính:

NhómTóm tắtRef
Kiến trúcAction backend, không FE aggregateDEC-001
Phân quyềnDynamic Permission 2 action mớiDEC-003
Kế toánFixed cost high-water markDEC-012
Vật tưTrạng thái pending, không cộng P&LDEC-006

Bảng FR tóm tắt:

FRMô tảƯu tiênGiai đoạn
FR-001Section TÀI CHÍNH trong sidebar đơn dịch vụMust1
FR-002Action GetOrderFinancialSummary aggregate cross-sourceMust1
FR-003App Settings nhóm fixed_cost.rateMust1
FR-004Snapshot fixed cost vào order mớiMust1
FR-005Hook update fixed cost high-waterMust1
FR-0062 permission action: view_financial_summary, view_financial_pnlMust1
FR-007Cảnh báo đơn lỗ (lợi nhuận tạm tính âm)Should1

Mô hình dữ liệu tóm tắt:

BảngLoạiMục đích
ecommerce.orderSỬAThêm fixed_cost_rate NUMERIC, fixed_cost_amount BIGINT
app_settingSỬAThêm key order.fixed_cost.rate (nested JSON)
module_permission_actionSEEDInsert 2 action mới
role_module.actionsSEEDDefault role Admin/Manager/Kế toán

Ảnh hưởng code hiện tại: OrderReceiverInfo.tsx, SystemTable.tsx (FE); create_order.go, invoice_complete.go, action.go (BE). Chi tiết → dev-spec C2.


A1) Bản thiết kế tóm tắt

TrườngGiá trị
Tính năngTổng hợp tài chính đơn hàng
LoạiTính năng mới trên màn hiện hữu
Nền tảngWeb Admin (diva-admin), portal admin (Phase 1); POS/CRM có thể cấp quyền sau
Moduleecommerce (FE) + ecommerce-api (BE) + settings + permission

A2) Bối cảnh

Hiện trạng (As-Is):

  • Lễ tân/Manager mở chi tiết đơn dịch vụ → tài chính rải ở 3-4 tab: Thanh toán (đã thu), Hoa hồng, Vật tư, Ghi chú
  • Để biết "đơn này còn nợ bao nhiêu?" + "lợi nhuận tạm tính?": phải mở từng tab + tự cộng
  • Trung bình mất 3-5 phút/đơn chỉ để xem tài chính
  • Manager đốc thu công nợ thường bỏ sót đơn nhỏ; BOD không thể xem nhanh tỷ suất 1 đơn
  • Chưa có cấu hình tỷ lệ chi phí cố định cho đơn

Kỳ vọng (To-Be):

  • Sidebar trái có section TÀI CHÍNH luôn nhìn thấy khi đổi tab
  • 8 chỉ số: doanh thu / đã thu / còn nợ / chi phí cố định / hoa hồng / tour cost / lợi nhuận tạm tính / tỷ suất
  • Phân quyền field-level: Manager xem 3 chỉ số đầu, Admin/BOD xem đủ 8
  • Cảnh báo trực quan khi đơn lỗ
  • Admin cấu hình tỷ lệ chi phí cố định 1 lần, áp dụng tự động cho mọi đơn mới

Không thuộc phạm vi (Non-goals):

#MụcLý do
1Báo cáo tài chính tổng (BCTC kế toán)Khác mục đích — đây là per-order
2Tài chính cho đơn product/prepaid/internalDay-1 chỉ service order
3Export Excel financial summaryPhase 2
4Cảnh báo lỗ qua SMS/EmailPhase 2, tránh spam khi vật tư chưa khóa
5Per-branch fixed cost rateDay-1 system-wide; Phase 2 nếu cần
6Backfill fixed_cost_rate cho order cũTránh đảo P&L lịch sử
7Chi phí vật tư cộng vào P&LĐịnh lượng vật tư chưa khóa, Phase 2

A3) Mục tiêu, persona và chỉ số thành công

Mục tiêu:

Mục tiêuCách đoMục tiêu đo
Giảm thời gian xem tài chính 1 đơnManual measure 20 đơn ngẫu nhiên trước/sauTừ 3-5 phút xuống < 5 giây (giảm > 95%)
Tăng tỷ lệ Manager đốc thu nợ kịp thờiManager dashboard: số đơn còn nợ tồn > 30 ngàyGiảm > 30% sau 60 ngày
Admin/BOD ra quyết định nhanh dựa trên tỷ suất đơnKhảo sát BOD sau 30 ngày≥ 4/5 BOD đánh giá "có ích"

Persona:

PersonaVai tròJTBDTần suất
Lễ tânTạo + thu tiền đơnXem đơn còn nợ bao nhiêu để nhắc khách50-100 lần/ngày
Manager chi nhánhĐốc thu + đối soátTìm đơn còn nợ > 30 ngày, biết doanh thu chi nhánh10-20 lần/ngày
Kế toánĐối soát công nợCross-check đã thu / còn nợ với báo cáo NH30-50 lần/ngày
Admin / BODQuản trịXem đơn nào lỗ → kiểm tra cost structure5-10 lần/ngày

A4) Yêu cầu chức năng

FR-001 — Section TÀI CHÍNH trong sidebar chi tiết đơn dịch vụ

Tham chiếu: DEC-002, DEC-003 | Ưu tiên: Must | SCR: SCR-01

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Lễ tân, Manager chi nhánh, Kế toán (xem view_financial_summary); Admin, BOD (xem view_financial_pnl đầy đủ)
  • Dữ liệu: order.id, order.debt_amount, aggregate parent invoice paid, order_commissions.amount, project_task_assignee.tour_money, order.fixed_cost_amount, status đơn
  • Điều kiện: User có ít nhất 1 trong 2 action view_financial_summary hoặc view_financial_pnl cho service_order ở portal hiện tại
  • Hành động: User mở /e/service-order/:id → component ServiceOrderFinancialSummary mount trong sidebar trái, gọi action GetOrderFinancialSummary({order_id})
  • Kết quả:
    • view_financial_summary: hiện 3 chỉ số (Doanh thu / Đã thu / Còn nợ)
    • view_financial_pnl: hiện 8 chỉ số đầy đủ + cảnh báo lỗ nếu profit_estimated < 0
    • Đơn đã hủy: vẫn hiện section theo đúng quyền + badge "Đơn đã hủy - chỉ đối soát"
    • Đơn cũ (fixed_cost_rate=NULL): ẩn dòng "Chi phí cố định"
  • Ngoại lệ:
    • Không có cả 2 action: ẨN section hoàn toàn (không hiện skeleton, không leak field)
    • Action API lỗi: hiện inline error + nút "Thử lại"
    • Order không tồn tại: section không mount (parent guard)
    • Permission revoke giữa session: refetch sau permission change → ẩn section ngay request kế tiếp

AC:

  • [ ] User có view_financial_summary mở đơn → thấy section với đúng 3 dòng (Doanh thu / Đã thu / Còn nợ) trong < 1.5 giây (P95)
  • [ ] User có view_financial_pnl mở đơn → thấy 8 dòng đầy đủ + cảnh báo lỗ nếu áp dụng
  • [ ] User không có 2 action: API request KHÔNG trả field tài chính (BE least-data); FE không render section
  • [ ] Đơn đã hủy: section hiện theo đúng quyền với badge "Đơn đã hủy - chỉ đối soát" + tất cả số readonly
  • [ ] API trả timeout > 5s: hiện "Không thể tải tài chính. Vui lòng thử lại." + nút Thử lại
  • [ ] Sidebar overflow trên màn cao < 800px: section nằm trong vùng overflow-y: auto, không che block dưới

Ví dụ nghiệp vụ:

  • Lễ tân Nguyễn Thị Lan mở đơn ORD-202604-0123 của khách "Trần Mỹ Anh" → thấy section TÀI CHÍNH: Doanh thu 3.500.000đ, Đã thu 2.000.000đ, Còn nợ 1.500.000đ → gọi khách nhắc thanh toán nốt
  • Admin/BOD hoặc user được cấp view_financial_pnl mở đơn lỗ → thấy warning banner "Đơn lỗ tạm tính: −250.000đ. Chưa gồm chi phí vật tư."

FR-002 — Action backend GetOrderFinancialSummary

Tham chiếu: DEC-001 | Ưu tiên: Must | SCR:

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Hệ thống (action handler)
  • Dữ liệu: order, parent invoices, order_commissions, project_task_assignee.tour_money, order.fixed_cost_amount, status
  • Điều kiện: User authenticated + có ít nhất 1 action tài chính cho service_order ở portal hiện tại
  • Hành động: Aggregate cross-source theo PRD A8 FORMULA-001 → FORMULA-005, return JSON
  • Kết quả: Trả về:
    • Field summary (luôn): revenue, paid, debt
    • Field P&L (chỉ khi có view_financial_pnl): commission, tour_cost, fixed_cost, profit_estimated, margin_estimated
    • Field metadata: currency='VND', last_updated_at, note_pending_material (boolean)
  • Ngoại lệ:
    • Order không tồn tại: HTTP 404 + error code ORDER_NOT_FOUND
    • Không có quyền: HTTP 403 + error code UNAUTHORIZED
    • Internal error: HTTP 500 + log

AC:

  • [ ] Aggregate đúng theo PRD A8: 5 formula (paid / debt / commission / tour cost / fixed cost / profit / margin)
  • [ ] Performance: < 200ms (P95) cho đơn có ≤ 50 invoice + ≤ 20 order_items + ≤ 10 commission
  • [ ] Least-data response: user không có view_financial_pnl → response KHÔNG chứa field cost/profit/margin (không phải null, mà KHÔNG có key)
  • [ ] Sub-invoice (parent_id IS NOT NULL) loại khỏi paid aggregate
  • [ ] Đơn đã hủy: trả đầy đủ data + flag is_cancelled=true

FR-003 — App Settings: tỷ lệ chi phí cố định

Tham chiếu: DEC-010, DEC-013 | Ưu tiên: Must | SCR: SCR-02

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Admin
  • Dữ liệu: app_setting key order.fixed_cost.rate (nested JSON)
  • Điều kiện: Admin có quyền truy cập /s/app-settings/order
  • Hành động: Admin nhập giá trị tỷ lệ % (0-100, max 2 chữ số thập phân) → bấm Lưu → setting cập nhật
  • Kết quả: Mọi đơn mới tạo SAU thời điểm lưu sẽ snapshot rate này vào order.fixed_cost_rate
  • Ngoại lệ:
    • Rate < 0 hoặc > 100: chặn lưu, hiện "Tỷ lệ phải trong khoảng 0-100%"
    • Rate = NULL (chưa cấu hình): order mới có fixed_cost_rate=NULL, không hiện dòng fixed cost
    • Rate = 0: order mới có fixed_cost_rate=0, vẫn hiện "Chi phí cố định: 0đ"

AC:

  • [ ] Admin lưu rate 15.50 → setting order.fixed_cost.rate = 15.50 lưu vào DB
  • [ ] Đơn tạo sau lưu rate: order.fixed_cost_rate = 15.50 snapshot
  • [ ] Đơn tạo TRƯỚC lưu rate (rate cũ NULL): order.fixed_cost_rate = NULL, KHÔNG backfill
  • [ ] Validation chặn input -1, 101, 15.555 (3 decimal)

FR-004 — Snapshot fixed_cost_rate vào order mới

Tham chiếu: DEC-010, DEC-011 | Ưu tiên: Must | SCR:

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Hệ thống (create_order.go handler)
  • Dữ liệu: app_setting.order.fixed_cost.rateorder.fixed_cost_rate, order.fixed_cost_amount=0
  • Điều kiện: Tạo đơn dịch vụ mới
  • Hành động: Đọc rate từ App Settings → set vào order object trước insert
  • Kết quả: Order mới có fixed_cost_rate snapshot, fixed_cost_amount=0 (sẽ update qua hook khi invoice complete)
  • Ngoại lệ:
    • Rate NULL: fixed_cost_rate=NULL, fixed_cost_amount=NULL

AC:

  • [ ] Đơn mới tạo khi setting rate=15: order.fixed_cost_rate=15, order.fixed_cost_amount=0
  • [ ] Đơn mới tạo khi setting NULL: order.fixed_cost_rate=NULL, order.fixed_cost_amount=NULL

FR-005 — Hook invoice_complete cập nhật fixed cost high-water mark

Tham chiếu: DEC-012 | Ưu tiên: Must | SCR:

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Hệ thống (event handler)
  • Dữ liệu: order.fixed_cost_amount, order.fixed_cost_rate, paid amount aggregate
  • Điều kiện: Parent invoice (parent_id IS NULL) chuyển sang status='invoice_completed'
  • Hành động: Tính new_amount = ROUND(paid_amount × fixed_cost_rate / 100), update order.fixed_cost_amount = MAX(current, new_amount)
  • Kết quả: fixed_cost_amount chỉ tăng, không giảm khi refund (high-water mark theo DEC-012)
  • Ngoại lệ:
    • fixed_cost_rate=NULL: skip update, không phát sinh fixed cost
    • Sub-invoice complete: skip (chỉ parent invoice trigger)

AC:

  • [ ] Đơn fixed_cost_rate=15, paid 1.000.000đ → fixed_cost_amount=150.000đ
  • [ ] Đơn trên thanh toán thêm 500k (paid=1.5M) → fixed_cost_amount=225.000đ (15% × 1.5M)
  • [ ] Đơn trên hoàn 500k (paid=1M) → fixed_cost_amount=225.000đ (high-water giữ, KHÔNG giảm về 150k)
  • [ ] Đơn fixed_cost_rate=NULL: hook KHÔNG update fixed_cost_amount

FR-006 — Permission action seed

Tham chiếu: DEC-003 | Ưu tiên: Must | SCR:

Mô tả nghiệp vụ phần mềm:

  • Vai trò: Hệ thống (migration) + Admin (cấu hình qua Dynamic Permission UI)
  • Dữ liệu: module_permission_action (insert 2 action), role_module.actions (seed default)
  • Điều kiện: Migration chạy lần đầu khi deploy
  • Hành động: Insert 2 action view_financial_summary + view_financial_pnl cho module service_order, portal admin. Seed default cho 4 role.
  • Kết quả:
Roleview_financial_summaryview_financial_pnl
Admin✓ default✓ default
BOD✓ default✓ default
Manager chi nhánh✓ default
Kế toán✓ default
Lễ tân✓ default
Khác(chưa cấp)(chưa cấp)
  • Ngoại lệ:
    • Migration đã chạy: skip (idempotent)
    • Role không có trong default seed list: không cấp action

AC:

  • [ ] Migration up: 2 action xuất hiện trong module_permission_action
  • [ ] Default seed Admin/BOD có cả 2 action; Manager/Kế toán/Lễ tân chỉ có summary
  • [ ] Migration down: rollback xóa 2 action + revert seed
  • [ ] Sau seed, Admin có thể vào Dynamic Permission UI cấp/thu hồi action cho role/portal khác

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

Tham chiếu: DEC-015, DEC-006 | Ưu tiên: Should | SCR: SCR-01

Mô tả nghiệp vụ phần mềm:

  • Vai trò: User có view_financial_pnl
  • Dữ liệu: profit_estimated từ FORMULA-004
  • Điều kiện: profit_estimated < 0 AND user có view_financial_pnl
  • Hành động: Hệ thống render warning banner trong section TÀI CHÍNH, dưới tiêu đề và trên dòng "Doanh thu"
  • Kết quả: Banner hiện "Đơn lỗ tạm tính: −{amount}đ. Chưa gồm chi phí vật tư." + icon cảnh báo theo UI kit
  • Ngoại lệ:
    • User chỉ có view_financial_summary: KHÔNG hiện banner (không leak P&L)
    • profit_estimated >= 0: KHÔNG hiện banner

AC:

  • [ ] User Admin xem đơn có profit_estimated=-250000: thấy banner "Đơn lỗ tạm tính: −250.000đ. Chưa gồm chi phí vật tư."
  • [ ] User Manager (chỉ summary) xem cùng đơn: KHÔNG thấy banner
  • [ ] Đơn profit_estimated=0: KHÔNG hiện banner
  • [ ] Banner dùng warning severity/token hiện có + icon cảnh báo + có thể đóng (nhưng tự hiện lại khi mở lại đơn)

A5) Giả định và rủi ro

Giả định:

IDGiả địnhPhụ trách xác nhận
ASM-001Action GetOrderFinancialSummary trả < 200ms cho 95% case (đơn ≤ 50 invoice)TL + BE Dev
ASM-002Negative invoice customer_paid_amount có dấu âm khi refundBE Dev (test blocker)
ASM-003Hook invoice_complete đã skip sub-invoice đúng (theo evidence-pack)BE Dev

Rủi ro:

IDRủi roẢnh hưởngXác suấtCách giảm thiểu
RSK-001Negative invoice sign behavior chưa rõ → "Đã thu" tính sai khi có refundCaoTrung bìnhQA blocker test 4 case: thanh toán → hoàn 1 phần → hoàn full → thanh toán lại
RSK-002Permission revoke không có hiệu lực ngay → user cũ vẫn thấy fieldTrung bìnhCaoRefetch order detail sau permission change; UX rõ "cần refresh trang"
RSK-003Sidebar overflow trên màn nhỏ → section TÀI CHÍNH bị cheTrung bìnhTrung bìnhCSS max-height + overflow-y cho left side
RSK-004App Settings nested struct migration sai → mất tax setting cũCaoThấpMigration test trên staging với data thật, có rollback down.sql

A6) Chỉ số đo lường sau phát hành

Chỉ sốCách đoMục tiêuKhi nào đo
Time-to-financial-infoHotjar/manual measure 20 đơn ngẫu nhiên< 5 giây (P95)7 ngày sau go-live
Tỷ lệ Manager đốc thu kịp thờiSố đơn nợ > 30 ngày được note/contact / tổng đơn nợ > 30 ngàyTăng > 30% so với baseline60 ngày sau go-live
Số case BOD phát hiện đơn lỗĐếm cảnh báo lỗ được bấm "Đã kiểm tra"≥ 5 case/thángHàng tháng
API performanceGrafana p95 latency GetOrderFinancialSummary< 200msLiên tục
Permission leakAudit log: user không quyền nhận field tài chính0 caseHàng ngày

A7) Bảng thuật ngữ

Tiếng ViệtENĐịnh nghĩaPhân biệt với
Tài chính đơn hàngOrder Financial SummaryTổng hợp 8 chỉ số tài chính của 1 đơn dịch vụ≠ Báo cáo tài chính tổng (BCTC kế toán)
Lợi nhuận tạm tínhProfit EstimatedDoanh thu − chi phí (chưa gồm vật tư)≠ Lợi nhuận kế toán (đầy đủ)
Chi phí cố địnhFixed CostOverhead vận hành phân bổ theo % thực thu≠ Chi phí vật tư (variable, Phase 2)
High-water markHigh-water markfixed_cost_amount chỉ tăng theo mức cao nhất, không giảm khi refund≠ Tỷ lệ với thực thu thời điểm
view_financial_summaryview_financial_summaryPermission action xem 3 chỉ số: doanh thu/đã thu/còn nợview_financial_pnl (đầy đủ 8 chỉ số)
view_financial_pnlview_financial_pnlPermission action xem đầy đủ P&Lview_financial_summary
Đơn dịch vụService OrderĐơn cho dịch vụ/liệu trình≠ Đơn product/prepaid/internal
Default seedDefault seedPhân quyền mặc định khi migration≠ Invariant (có thể đổi qua Dynamic Permission UI)

A8) Công thức nghiệp vụ

FORMULA-001: Doanh thu (revenue)

  • Mô tả: Tổng giá trị đơn hàng (chưa trừ giảm giá đã ghi vào order).
  • Công thức: revenue = order.amount (đã có sẵn, không cần tính)
  • Biến số: order.amount — nguồn: ecommerce.order.amount
  • Đơn vị: VND (integer)
  • Ví dụ: Đơn liệu trình trị mụn 5 buổi 3.500.000đ → revenue = 3.500.000đ
  • Trường hợp cá biệt:
    • order.amount = NULL (đơn lỗi): hiển thị , không hiện 0đ
    • Đơn đã hủy: vẫn hiện amount, badge "Đơn đã hủy"

FORMULA-002: Đã thu (paid)

  • Mô tả: Tổng tiền khách đã thanh toán, dựa trên parent invoice đã complete.
  • Công thức: paid = SUM(parent_invoice.customer_paid_amount WHERE status='invoice_completed' AND parent_id IS NULL)
  • Biến số:
    • parent_invoice.customer_paid_amount — nguồn: ecommerce.invoice.customer_paid_amount
    • Filter: parent_id IS NULL (loại sub-invoice), status='invoice_completed'
  • Đơn vị: VND (integer, dấu có thể âm khi refund)
  • Ví dụ: Đơn 3.500.000đ, khách trả 2.000.000đ rồi hoàn 500.000đ → paid = 2.000.000 + (−500.000) = 1.500.000đ
  • Trường hợp cá biệt:
    • Không có parent invoice complete: paid = 0
    • Sub-invoice (parent_id != NULL) bị skip
    • Negative invoice với customer_paid_amount chưa rõ dấu → QA blocker (RSK-001)

FORMULA-003: Còn nợ (debt)

  • Mô tả: Reuse computed field hiện có order.debt_amount.
  • Công thức: debt = order.debt_amount (Hasura computed field get_order_debt)
  • Biến số: order.debt_amount — nguồn: Hasura computed field
  • Đơn vị: VND (integer)
  • Ví dụ: Đơn 3.500.000đ, đã thu 1.500.000đ → debt = 2.000.000đ
  • Trường hợp cá biệt:
    • Đơn đã hủy: debt theo computed field hiện tại (giữ nguyên behavior)

FORMULA-004: Hoa hồng (commission)

  • Mô tả: Tổng hoa hồng phải trả cho nhân viên của đơn này.
  • Công thức: commission = SUM(order_commissions.amount)
  • Biến số: order_commissions.amount — nguồn: ecommerce.order_commission.amount
  • Đơn vị: VND (integer)
  • Ví dụ: Đơn có 2 KTV nhận hoa hồng 200k + 150k → commission = 350.000đ
  • Trường hợp cá biệt:
    • Không có commission: commission = 0
    • Commission đã void/cancel: cần check field voided_at (TBD trong dev-spec)

FORMULA-005: Tour cost

  • Mô tả: Tổng tiền tour của đơn (theo pattern GetListTourLimit).
  • Công thức: tour_cost = SUM(project_task_assignee.tour_money WHERE order_item.id IN order.items)
  • Biến số:
    • project_task_assignee.tour_money — nguồn: project.project_task_assignee.tour_money
    • Join qua order_item.id
  • Đơn vị: VND (integer)
  • Ví dụ: Đơn có 2 KTV đi tour: 100k + 80k → tour_cost = 180.000đ
  • Trường hợp cá biệt:
    • Đơn không có project task: tour_cost = 0
    • PD-002: include phần đã trả KTV chưa? Default: include theo tour_money field hiện có

FORMULA-006: Chi phí cố định (fixed cost)

  • Mô tả: Reuse order.fixed_cost_amount (đã được hook update theo high-water mark).
  • Công thức: fixed_cost = order.fixed_cost_amount (đã pre-compute)
  • Biến số: order.fixed_cost_amount — nguồn: ecommerce.order.fixed_cost_amount
  • Đơn vị: VND (integer)
  • Ví dụ: Đơn fixed_cost_rate=15, max paid trong lịch sử 1.500.000đ → fixed_cost = 225.000đ
  • Trường hợp cá biệt:
    • fixed_cost_amount = NULL (đơn cũ): UI ẩn dòng "Chi phí cố định"
    • fixed_cost_amount = 0 (rate=0): UI hiện "Chi phí cố định: 0đ"

FORMULA-007: Lợi nhuận tạm tính (profit_estimated)

  • Mô tả: Doanh thu − tổng chi phí đã chốt (chưa gồm vật tư).
  • Công thức: profit_estimated = revenue − commission − tour_cost − fixed_cost
  • Biến số: Tham chiếu FORMULA-001, 004, 005, 006
  • Đơn vị: VND (integer, có thể âm)
  • Ví dụ: Đơn 3.500.000đ, commission 350k, tour 180k, fixed 225k → profit_estimated = 3.500.000 − 350.000 − 180.000 − 225.000 = 2.745.000đ
  • Trường hợp cá biệt:
    • Bất kỳ biến NULL: hiển thị , không tính
    • profit_estimated < 0: trigger cảnh báo lỗ (FR-007)
    • Phase 2: thêm material_cost vào công thức khi định lượng vật tư khóa

FORMULA-008: Tỷ suất lợi nhuận tạm tính (margin_estimated)

  • Mô tả: Tỷ lệ lợi nhuận tạm tính / doanh thu.
  • Công thức: margin_estimated = (profit_estimated / revenue) × 100
  • Đơn vị: % (2 chữ số thập phân, dấu phẩy: 78,43%)
  • Ví dụ: profit_estimated=2.745.000, revenue=3.500.000margin_estimated = 78,43%
  • Trường hợp cá biệt:
    • revenue = 0 hoặc NULL: hiển thị , không hiện 0% hay NaN
    • profit_estimated < 0: hiển thị âm, ví dụ −15,67%