Appearance
v3.1 — 30/04/2026
| Thay đổi | Section | Ảnh hưởng |
|---|---|---|
| Đồng bộ wording cảnh báo lỗ theo warning banner/design token intent | FR-007 | PO + UI/UX + QA |
| Làm rõ đơn đã hủy hiển thị theo đúng quyền | FR-001 | FE + 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.md→Tóm tắt điều hành→Z) Nhật ký quyết định→A4) Yêu cầu chức năng→A8) Công thức nghiệp vụ. Văn phong: theotemplates/_LANGUAGE_RULES.md+_STYLE_GUIDE.md.
Lịch sử thay đổi
| Phiên bản | Ngày | Tác giả | Thay đổi |
|---|---|---|---|
| 3.0 | 30/04/2026 | PO/BA | Áp template po-ba-workflow v3 |
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 |
evidence-pack.md | Bằng chứng code/screen/config | Ưu tiên bằng chứng trước assumption |
decision-brief.md | Cửa vào package | Brief tóm tắt; PRD giữ contract chi tiết |
Quy tắc công thức: A8 là canonical;
dev-spec C3chỉ ghi triển khai SQL.
Hướng dẫn đọc
| Đối tượng | Section cần đọc |
|---|---|
| PO/BA | decision-brief.md → A0 → Z → A4 → A8 |
| Sếp / BOD | decision-brief.md → TL;DR → A0 |
| Tech Lead | decision-brief.md → A0 → Z → Dev Spec C1-C5 |
| UI/UX / FE | decision-brief.md → A4 → UI Spec |
| BE Dev | A4/A8 → Dev Spec C1-C11 |
| QA | A4 → 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ở
| PD | Câu hỏi | Lựa chọn / khuyến nghị | Phụ trách | Hạn | Trạ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ị: A | PO + BOD | 02/05/2026 | Open |
| PD-002 | Tour 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ị: A | TL + Kế toán | 03/05/2026 | Open |
Backlog giai đoạn 2 (ngoài phạm vi hiện tại)
| # | Tính năng | Lý do defer |
|---|---|---|
| 1 | Chi phí vật tư cộng vào P&L | Định lượng vật tư chưa khóa |
| 2 | Export Excel financial summary | Day-1 chỉ on-screen |
| 3 | Notification cảnh báo lỗ qua SMS/Email | Tránh spam khi vật tư chưa khóa |
| 4 | Per-branch fixed cost rate | Day-1 system-wide đủ dùng |
| 5 | Tài chính cho đơn product/prepaid | Day-1 chỉ service order |
Z) Nhật ký quyết định
| ID | Nhóm | Quyết định | Lý do | Ngày | Trạng thái |
|---|---|---|---|---|---|
| DEC-001 | Kiến trúc | Build action GetOrderFinancialSummary, không aggregate FE, không DB view | A) 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. → A | 30/04 | Locked |
| DEC-002 | UX | Section TÀI CHÍNH chèn trong sidebar trái, trước DỮ LIỆU | A) 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". → A | 30/04 | Locked |
| DEC-003 | Phân quyền | Dynamic 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. → A | 30/04 | Locked |
| DEC-006 | Nghiệ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 sai | 30/04 | Locked |
| DEC-012 | Kế toán | fixed_cost_amount là high-water mark, refund không giảm | A) 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. → A | 30/04 | Locked |
| DEC-015 | UX | Cả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ố sai | 30/04 | Locked |
| DEC-016 | UI | KHÔNG tạo màn permission riêng; reuse Dynamic Permission UI | Tránh duplicate UI permission management | 30/04 | Locked |
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ối | FR | SCR | Dev section |
|---|---|---|---|
Section sidebar TÀI CHÍNH | FR-001 | SCR-01 | C5 (action), C6 (component) |
| Action backend aggregate | FR-002 | — | C5 (action handler) |
| App Settings fixed cost | FR-003 | SCR-02 | C4 (settings struct), C6 (FE editor) |
| Snapshot khi tạo đơn | FR-004 | — | C5 (create_order.go) |
| Hook invoice_complete high-water | FR-005 | — | C5 (event handler) |
| Permission action + seed | FR-006 | — | C8 (security + migration) |
| Cảnh báo đơn lỗ | FR-007 | SCR-01 | C6 (component sub-section) |
Quyết định chính:
| Nhóm | Tóm tắt | Ref |
|---|---|---|
| Kiến trúc | Action backend, không FE aggregate | DEC-001 |
| Phân quyền | Dynamic Permission 2 action mới | DEC-003 |
| Kế toán | Fixed cost high-water mark | DEC-012 |
| Vật tư | Trạng thái pending, không cộng P&L | DEC-006 |
Bảng FR tóm tắt:
| FR | Mô tả | Ưu tiên | Giai đoạn |
|---|---|---|---|
| FR-001 | Section TÀI CHÍNH trong sidebar đơn dịch vụ | Must | 1 |
| FR-002 | Action GetOrderFinancialSummary aggregate cross-source | Must | 1 |
| FR-003 | App Settings nhóm fixed_cost.rate | Must | 1 |
| FR-004 | Snapshot fixed cost vào order mới | Must | 1 |
| FR-005 | Hook update fixed cost high-water | Must | 1 |
| FR-006 | 2 permission action: view_financial_summary, view_financial_pnl | Must | 1 |
| FR-007 | Cảnh báo đơn lỗ (lợi nhuận tạm tính âm) | Should | 1 |
Mô hình dữ liệu tóm tắt:
| Bảng | Loại | Mục đích |
|---|---|---|
ecommerce.order | SỬA | Thêm fixed_cost_rate NUMERIC, fixed_cost_amount BIGINT |
app_setting | SỬA | Thêm key order.fixed_cost.rate (nested JSON) |
module_permission_action | SEED | Insert 2 action mới |
role_module.actions | SEED | Default 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ường | Giá trị |
|---|---|
| Tính năng | Tổng hợp tài chính đơn hàng |
| Loại | Tính năng mới trên màn hiện hữu |
| Nền tảng | Web Admin (diva-admin), portal admin (Phase 1); POS/CRM có thể cấp quyền sau |
| Module | ecommerce (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ÍNHluô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ục | Lý do |
|---|---|---|
| 1 | Báo cáo tài chính tổng (BCTC kế toán) | Khác mục đích — đây là per-order |
| 2 | Tài chính cho đơn product/prepaid/internal | Day-1 chỉ service order |
| 3 | Export Excel financial summary | Phase 2 |
| 4 | Cảnh báo lỗ qua SMS/Email | Phase 2, tránh spam khi vật tư chưa khóa |
| 5 | Per-branch fixed cost rate | Day-1 system-wide; Phase 2 nếu cần |
| 6 | Backfill fixed_cost_rate cho order cũ | Tránh đảo P&L lịch sử |
| 7 | Chi 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êu | Cách đo | Mục tiêu đo |
|---|---|---|
| Giảm thời gian xem tài chính 1 đơn | Manual measure 20 đơn ngẫu nhiên trước/sau | Từ 3-5 phút xuống < 5 giây (giảm > 95%) |
| Tăng tỷ lệ Manager đốc thu nợ kịp thời | Manager dashboard: số đơn còn nợ tồn > 30 ngày | Giảm > 30% sau 60 ngày |
| Admin/BOD ra quyết định nhanh dựa trên tỷ suất đơn | Khảo sát BOD sau 30 ngày | ≥ 4/5 BOD đánh giá "có ích" |
Persona:
| Persona | Vai trò | JTBD | Tần suất |
|---|---|---|---|
| Lễ tân | Tạo + thu tiền đơn | Xem đơn còn nợ bao nhiêu để nhắc khách | 50-100 lần/ngày |
| Manager chi nhánh | Đốc thu + đối soát | Tìm đơn còn nợ > 30 ngày, biết doanh thu chi nhánh | 10-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 NH | 30-50 lần/ngày |
| Admin / BOD | Quản trị | Xem đơn nào lỗ → kiểm tra cost structure | 5-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 (xemview_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_summaryhoặcview_financial_pnlchoservice_orderở portal hiện tại - Hành động: User mở
/e/service-order/:id→ componentServiceOrderFinancialSummarymount trong sidebar trái, gọi actionGetOrderFinancialSummary({order_id}) - Kết quả:
- Có
view_financial_summary: hiện 3 chỉ số (Doanh thu / Đã thu / Còn nợ) - Có
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"
- Có
- 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_summarymở đơ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_pnlmở đơ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-0123của khách "Trần Mỹ Anh" → thấy sectionTÀI CHÍNH: Doanh thu3.500.000đ, Đã thu2.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_pnlmở đơ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)
- Field summary (luôn):
- 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
- Order không tồn tại: HTTP 404 + error code
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ảinull, 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_settingkeyorder.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→ settingorder.fixed_cost.rate = 15.50lưu vào DB - [ ] Đơn tạo sau lưu rate:
order.fixed_cost_rate = 15.50snapshot - [ ] Đơ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.gohandler) - Dữ liệu:
app_setting.order.fixed_cost.rate→order.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_ratesnapshot,fixed_cost_amount=0(sẽ update qua hook khi invoice complete) - Ngoại lệ:
- Rate NULL:
fixed_cost_rate=NULL,fixed_cost_amount=NULL
- Rate 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 sangstatus='invoice_completed' - Hành động: Tính
new_amount = ROUND(paid_amount × fixed_cost_rate / 100), updateorder.fixed_cost_amount = MAX(current, new_amount) - Kết quả:
fixed_cost_amountchỉ 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 updatefixed_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_pnlcho moduleservice_order, portaladmin. Seed default cho 4 role. - Kết quả:
| Role | view_financial_summary | view_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_estimatedtừ FORMULA-004 - Điều kiện:
profit_estimated < 0AND 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
- User chỉ có
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:
| ID | Giả định | Phụ trách xác nhận |
|---|---|---|
| ASM-001 | Action GetOrderFinancialSummary trả < 200ms cho 95% case (đơn ≤ 50 invoice) | TL + BE Dev |
| ASM-002 | Negative invoice customer_paid_amount có dấu âm khi refund | BE Dev (test blocker) |
| ASM-003 | Hook invoice_complete đã skip sub-invoice đúng (theo evidence-pack) | BE Dev |
Rủi ro:
| ID | Rủi ro | Ảnh hưởng | Xác suất | Cách giảm thiểu |
|---|---|---|---|---|
| RSK-001 | Negative invoice sign behavior chưa rõ → "Đã thu" tính sai khi có refund | Cao | Trung bình | QA blocker test 4 case: thanh toán → hoàn 1 phần → hoàn full → thanh toán lại |
| RSK-002 | Permission revoke không có hiệu lực ngay → user cũ vẫn thấy field | Trung bình | Cao | Refetch order detail sau permission change; UX rõ "cần refresh trang" |
| RSK-003 | Sidebar overflow trên màn nhỏ → section TÀI CHÍNH bị che | Trung bình | Trung bình | CSS max-height + overflow-y cho left side |
| RSK-004 | App Settings nested struct migration sai → mất tax setting cũ | Cao | Thấp | Migration 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 đo | Mục tiêu | Khi nào đo |
|---|---|---|---|
| Time-to-financial-info | Hotjar/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ời | Số đơn nợ > 30 ngày được note/contact / tổng đơn nợ > 30 ngày | Tăng > 30% so với baseline | 60 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áng | Hàng tháng |
| API performance | Grafana p95 latency GetOrderFinancialSummary | < 200ms | Liên tục |
| Permission leak | Audit log: user không quyền nhận field tài chính | 0 case | Hàng ngày |
A7) Bảng thuật ngữ
| Tiếng Việt | EN | Định nghĩa | Phân biệt với |
|---|---|---|---|
| Tài chính đơn hàng | Order Financial Summary | Tổ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ính | Profit Estimated | Doanh thu − chi phí (chưa gồm vật tư) | ≠ Lợi nhuận kế toán (đầy đủ) |
| Chi phí cố định | Fixed Cost | Overhead vận hành phân bổ theo % thực thu | ≠ Chi phí vật tư (variable, Phase 2) |
| High-water mark | High-water mark | fixed_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_summary | view_financial_summary | Permission action xem 3 chỉ số: doanh thu/đã thu/còn nợ | ≠ view_financial_pnl (đầy đủ 8 chỉ số) |
view_financial_pnl | view_financial_pnl | Permission action xem đầy đủ P&L | ≠ view_financial_summary |
| Đơn dịch vụ | Service Order | Đơn cho dịch vụ/liệu trình | ≠ Đơn product/prepaid/internal |
| Default seed | Default seed | Phâ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_amountchưa rõ dấu → QA blocker (RSK-001)
- Không có parent invoice complete:
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 fieldget_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:
debttheo computed field hiện tại (giữ nguyên behavior)
- Đơn đã hủy:
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)
- Không có commission:
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_moneyfield hiện có
- Đơn không có project task:
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_costvào công thức khi định lượng vật tư khóa
- Bất kỳ biến NULL: hiển thị
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.000→margin_estimated = 78,43% - Trường hợp cá biệt:
revenue = 0hoặcNULL: hiển thị—, không hiện0%hayNaNprofit_estimated < 0: hiển thị âm, ví dụ−15,67%