Appearance
v3.1 — 30/04/2026
| Thay đổi | Section | Ảnh hưởng |
|---|---|---|
Thêm B2.1A nguyên tắc UX bắt buộc | B2.1A | UI/UX + FE |
| Thêm variant wireframes A-F và ma trận Role × Variant | B2.2 / B2.8A | UI/UX + FE + QA |
Đổi phần rà soát rủi ro sang ngôn ngữ public-friendly B-QUALITY | B-QUALITY | All |
Đặc tả giao diện (UI Spec) — Tổng hợp tài chính đơn hàng
Tham chiếu: PRD v3.0 | Phiên bản UI Spec: v3.1 | Ngày: 30/04/2026
Mục đích: mô tả màn hình, thay đổi, trạng thái, tương tác, copy hiển thị cho FE/UI/QA, đủ căn cứ triển khai và kiểm thử thống nhất. Đọc trước:
decision-brief.md→B-PRE→B0→B1→B2 SCR-01/02→B-POST→B-QUALITY. Văn phong: theotemplates/_LANGUAGE_RULES.md+_STYLE_GUIDE.md. CTA dùng tiếng Việt; format VN.
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 | Layout UI hiện tại, ứng viên reuse | Ưu tiên bằng chứng code/screen |
prd.md | FR, lifecycle, công thức | Theo truth đã khóa |
B-PRE) Discovery checklist
B-PRE.1) Tìm component/page hiện có liên quan
bash
# Tìm OrderReceiverInfo và XCard pattern
grep -rE "OrderReceiverInfo|XCard.*section" diva-admin/src/modules/ecommerce/components/order/
# Tìm App Settings Order pattern
grep -rE "AppSettingGroups\.order|SystemTable" diva-admin/src/modules/settings/
# Tìm permission pattern
grep -rE "useGlobalStore|hasPermission|view_financial" diva-admin/src/modules/ecommerce/
# Tìm GraphQL action query pattern
grep -rE "GetListTourLimit|action.*query" diva-admin/src/modules/ecommerce/graphql/B-PRE.2) Bảng inventory đã check
| Hạng mục | Đã check? | File / màn hiện có | Ghi chú |
|---|---|---|---|
| Page / route hiện có | ✓ | /e/service-order/:id qua OrderDetail.tsx | Reuse |
| Component reusable | ✓ | OrderReceiverInfo.tsx, XCard, XDetailLayout | Mount section mới vào sidebar |
| Form/dialog liên quan | ✓ | App Settings Order: SystemTable.tsx, SettingItemEditor | Extend group order.fixed_cost |
| Field/cột hiện có | ✓ | Sidebar không có dòng tài chính riêng; Tabs có Thanh toán + Hoa hồng | Tổng hợp vào sidebar |
| CTA hiện có | ✓ | Sidebar có CTA chuyển tab; không có CTA tài chính | Thêm "Thử lại" cho error state |
| Filter / search | ✓ | Sidebar không filter | N/A |
| State hiện có | ✓ | OrderDetail có loading skeleton + error toast | Section tài chính tự có state riêng |
| Permission gating | ✓ | useGlobalStore.hasPermission(moduleId, actionId) | Reuse cho 2 action mới |
| Notification | ✓ | Order detail không có notification kèm | N/A day-1 |
| Export columns | ✓ | Không có export sidebar | N/A day-1 |
| Tooltip/hint | ✓ | Sidebar có tooltip cho BRANCH, PROMOTION | Thêm tooltip cho 8 chỉ số |
| Mobile/responsive | ✓ | .detail.is-inside 100% width | Section mới phải responsive |
| Analytics events | ✓ | Order detail có order_detail_viewed event | Thêm financial_summary_viewed, loss_warning_shown |
B-PRE.3) Phân loại reuse
| Phần feature | Phân loại | Evidence | Delta cần |
|---|---|---|---|
| Section sidebar mới | 🔧 Extend | OrderReceiverInfo.tsx:569-1452 đã có cấu trúc XCard + section | Mount ServiceOrderFinancialSummary trước section DỮ LIỆU |
| App Settings group | 🔧 Extend | AppSettingGroups.order chỉ có tax | Thêm fixed_cost group vào enum + render |
| Permission gating | ✅ Reuse | useGlobalStore.hasPermission() đã có | Gọi với 2 action mới |
| Error/loading state | ✅ Reuse | XCard có sẵn skeleton + error pattern | Apply cho section mới |
| Tooltip pattern | ✅ Reuse | q-tooltip + i18n key | Thêm 8 tooltip mới |
| Cảnh báo lỗ banner | 🆕 Build mới | Chưa có pattern banner cảnh báo trong sidebar | Component mới LossWarningBanner |
B0) Hiện trạng UI và quy ước thay đổi (As-Is + Delta Contract)
B0.1) Bảng kiểm kê đầy đủ
| UI ID | Màn / route | Section | Block / field / action | Thứ tự | Hành vi hiện tại | Permission | Mobile? | Delta Status | Hành vi đích | Evidence |
|---|---|---|---|---|---|---|---|---|---|---|
| SCR-01-BLK-01 | /e/service-order/:id | Sidebar trái → Header | Order code + status + customer name | 1 | Hiển thị ORD-{code}, status badge, tên khách | All authenticated | 100% width | KEEP | Không đổi | OrderReceiverInfo.tsx:569-650 |
| SCR-01-BLK-02 | /e/service-order/:id | Sidebar trái → POS create/refund | CTA refund (POS only) | 2 | Hiện CTA "Hoàn tiền" cho user POS | POS role | Như cũ | KEEP | Không đổi | OrderReceiverInfo.tsx:680-720 |
| SCR-01-BLK-03 | /e/service-order/:id | Sidebar trái → ACCOUNT | Field tài khoản KH | 3 | Hiện account info + edit | All | Như cũ | KEEP | Không đổi | OrderReceiverInfo.tsx:780-830 |
| SCR-01-BLK-04 | /e/service-order/:id | Sidebar trái → CONTACT | Số điện thoại + địa chỉ | 4 | Hiển thị + click-to-call | All | Như cũ | KEEP | Không đổi | OrderReceiverInfo.tsx:870-920 |
| SCR-01-BLK-05 | /e/service-order/:id | Sidebar trái → HOA HỒNG | Field hoa hồng tổng | 5 | Hiển thị tổng commission | commission.view | Như cũ | KEEP | Không đổi | OrderReceiverInfo.tsx:980-1020 |
| SCR-01-BLK-06 | /e/service-order/:id | Sidebar trái → BRANCH | Tên chi nhánh | 6 | Hiện branch name + tooltip | All | Như cũ | KEEP | Không đổi | OrderReceiverInfo.tsx:1080-1120 |
| SCR-01-BLK-07 | /e/service-order/:id | Sidebar trái → PROMOTION | Khuyến mãi áp dụng | 7 | Hiển thị voucher/discount | All | Như cũ | KEEP | Không đổi | OrderReceiverInfo.tsx:1180-1220 |
| SCR-01-BLK-FIN | /e/service-order/:id | Sidebar trái → TÀI CHÍNH (MỚI) | 8 chỉ số tài chính + cảnh báo lỗ | 8 (mới) | — | view_financial_summary ∨ view_financial_pnl | Responsive | NEW | Mount ServiceOrderFinancialSummary | (mới) |
| SCR-01-BLK-08 | /e/service-order/:id | Sidebar trái → DỮ LIỆU | Created at, updated at | 9 (cũ là 8) | Metadata audit | All | Như cũ | MOVE | Vẫn KEEP nội dung, đẩy thứ tự xuống 1 vị trí | OrderReceiverInfo.tsx:1320-1360 |
| SCR-01-BLK-09 | /e/service-order/:id | Sidebar trái → LIÊN KẾT | Related orders | 10 (cũ là 9) | Click navigate | All | Như cũ | MOVE | Vẫn KEEP nội dung, đẩy thứ tự xuống 1 vị trí | OrderReceiverInfo.tsx:1400-1440 |
| SCR-02-BLK-01 | /s/app-settings/order | Settings table | Field "Thuế VAT" | 1 | Editor cho tax | Admin | Mobile drawer | KEEP | Không đổi | SystemTable.tsx:36-55 |
| SCR-02-BLK-FIN | /s/app-settings/order | Settings table | Field "Tỷ lệ chi phí cố định (%)" (MỚI) | 2 (mới) | — | Admin | Mobile drawer | NEW | Editor cho order.fixed_cost.rate, validate 0-100 | (mới) |
B0.2) Từ điển Delta Status
| Status | Ý nghĩa | Evidence bắt buộc |
|---|---|---|
| KEEP | Giữ nguyên | Ghi rõ vẫn hiển thị ở target wireframe |
| MOVE | Đổi vị trí | UX flow + ghi rõ trước/sau |
| NEW | Thêm mới | PRD FR/AC ref |
B0.3) Tiêu chí hoàn thành B0
- [x] 100% UI hiện hữu của 2 màn đã inventory ở B0.1 (11 dòng)
- [x] Mọi UI ID có Delta Status
- [x] Mọi dòng có Evidence file:line
- [x] 2 dòng MOVE (BLK-08, BLK-09) ghi rõ chỉ đổi thứ tự, không đổi behavior
- [x] Target wireframe ở B2 vẽ cả vùng KEEP xung quanh
B0.4) Ma trận Field × Surface (BẮT BUỘC)
Mọi chỉ số tài chính + setting fixed cost phải xuất hiện đủ surfaces.
| Field | List/Section | Detail/Form | Popup/Modal | Export | Search | Filter | Permission | Mobile | Notification | Default | Validation | Tooltip |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
revenue (Doanh thu) | Có (sidebar dòng 1) | N/A | N/A | N/A day-1 | N/A | N/A | summary ∨ pnl | Hiển thị | N/A | Từ order.amount | NULL → — | "Tổng giá trị đơn hàng (chưa trừ giảm giá)" |
paid (Đã thu) | Có (sidebar dòng 2) | N/A | N/A | N/A day-1 | N/A | N/A | summary ∨ pnl | Hiển thị | N/A | Aggregate parent invoice | Có thể âm khi refund | "Tổng tiền khách đã thanh toán, gồm cả hoàn tiền" |
debt (Còn nợ) | Có (sidebar dòng 3) | N/A | N/A | N/A day-1 | N/A | N/A | summary ∨ pnl | Hiển thị | N/A | Từ debt_amount computed | NULL → 0đ | "Số tiền khách còn nợ" |
commission (Hoa hồng) | Có (sidebar dòng 4 — PnL only) | N/A | N/A | N/A | N/A | N/A | pnl only | Hiển thị | N/A | SUM order_commissions.amount | NULL → 0đ | "Tổng hoa hồng phải trả nhân viên" |
tour_cost (Tour) | Có (sidebar dòng 5 — PnL only) | N/A | N/A | N/A | N/A | N/A | pnl only | Hiển thị | N/A | SUM tour_money | NULL → 0đ | "Tổng tiền tour KTV" |
fixed_cost (Chi phí cố định) | Có (sidebar dòng 6 — PnL only, ẩn nếu rate=NULL) | N/A | N/A | N/A | N/A | N/A | pnl only | Hiển thị | N/A | order.fixed_cost_amount | NULL → ẩn dòng | "Overhead vận hành phân bổ theo tỷ lệ thực thu cao nhất" |
profit_estimated (Lợi nhuận tạm tính) | Có (sidebar dòng 7 — PnL only) | N/A | N/A | N/A | N/A | N/A | pnl only | Hiển thị | N/A | FORMULA-007 | NULL → — | "Doanh thu − chi phí đã chốt. Chưa gồm chi phí vật tư." |
margin_estimated (Tỷ suất) | Có (sidebar dòng 8 — PnL only) | N/A | N/A | N/A | N/A | N/A | pnl only | Hiển thị | N/A | FORMULA-008 | revenue=0 → — | "Lợi nhuận tạm tính / Doanh thu × 100%" |
loss_warning (Banner cảnh báo lỗ) | Có (banner trong section, dưới tiêu đề) | N/A | N/A | N/A | N/A | N/A | pnl only | Responsive (banner stacked) | N/A day-1 | Auto hiện khi profit_estimated < 0 | N/A | N/A (icon + copy đầy đủ) |
fixed_cost_rate (Setting App) | N/A | Có (App Settings Order) | N/A | N/A | N/A | N/A | Admin (settings.update) | Drawer trên mobile | N/A | NULL (chưa cấu hình) | NUMERIC 0-100, max 2 decimals | "Tỷ lệ % thực thu phân bổ cho chi phí cố định (mặt bằng, điện nước...)" |
B0.5) Ma trận State × Screen
| Màn / khối | Default | Loading | Empty | Error (retry) | No permission | Partial / pending |
|---|---|---|---|---|---|---|
SCR-01 Section TÀI CHÍNH | Hiện 3-8 dòng tùy permission | Skeleton 3 dòng (placeholder pulsing) | N/A (đơn luôn có data) | "Không thể tải tài chính. Vui lòng thử lại." + nút Thử lại | ẨN section hoàn toàn (không leak) | Muted note "Chi phí vật tư đang phát triển — chưa cộng vào lợi nhuận tạm tính" cuối section |
SCR-01 Banner cảnh báo lỗ | Hiện warning banner "Đơn lỗ tạm tính: −250.000đ. Chưa gồm chi phí vật tư." | N/A | Ẩn banner khi profit_estimated >= 0 | N/A | Ẩn banner (không có pnl permission) | N/A |
SCR-02 Field "Tỷ lệ chi phí cố định (%)" | Hiện input số với value hiện tại hoặc placeholder "Chưa cấu hình" | Spinner trong input khi save | Placeholder "Chưa cấu hình" khi value=NULL | Error banner "Không thể lưu. Vui lòng thử lại." | Ẩn field (Admin only) | N/A |
B1) Bản đồ màn hình và hành trình
B1.1) Danh sách màn
| SCR | Tên | Route | Loại | Mô tả |
|---|---|---|---|---|
| SCR-01 | Chi tiết đơn dịch vụ | /e/service-order/:id | 🔧 Extend | Mount section TÀI CHÍNH trong sidebar |
| SCR-02 | App Settings Order | /s/app-settings/order | 🔧 Extend | Thêm field "Tỷ lệ chi phí cố định (%)" |
| SCR-03 | Dynamic Permission Matrix | /s/permission-matrix | ✅ Reuse | Cấp/thu hồi 2 action mới (không đổi UI) |
B1.2) Hành trình end-to-end
Hành trình theo vai trò:
| Vai trò | Entry point | Đường màn | Kết quả |
|---|---|---|---|
| Admin | /s/app-settings/order | SCR-02 → lưu setting → đơn mới có rate | Cấu hình áp dụng cho đơn mới |
| Lễ tân | /e/service-order/:id | SCR-01 Variant A (3 dòng summary) | Biết doanh thu/đã thu/còn nợ |
| Manager | /e/service-order/:id | SCR-01 Variant A mặc định; Variant B nếu được cấp view_financial_pnl | Đốc thu công nợ; chỉ xem PnL khi được cấp quyền |
| Kế toán | /e/service-order/:id | SCR-01 Variant A mặc định; Variant B nếu được cấp view_financial_pnl | Đối soát thu/nợ; không mặc định thấy chi phí/lợi nhuận |
| Admin/BOD | /e/service-order/:id | SCR-01 Variant B (8 dòng + cảnh báo lỗ nếu có) | Phát hiện đơn lỗ |
B1.3) Phạm vi thay đổi tối thiểu
Đụng 2 màn hiện hữu (SCR-01, SCR-02) — không phải full new screen.
| Màn hiện có | Bằng chứng hiện trạng | Thay đổi | Vị trí update | Vì sao đủ |
|---|---|---|---|---|
/e/service-order/:id | OrderReceiverInfo.tsx:569-1452 | Mount component sidebar mới | Trước section DỮ LIỆU (sau PROMOTION) | Sidebar đã có cấu trúc section, chỉ thêm 1 section, không cần redesign |
/s/app-settings/order | SystemTable.tsx:36-55 | Thêm 1 field input số trong group order | Sau field "Thuế VAT" | Settings table đã render dynamic theo enum, chỉ thêm enum + editor |
B2) SCR-01: Chi tiết đơn dịch vụ — Section TÀI CHÍNH
B2.1) Ngữ cảnh nghiệp vụ
| Câu hỏi | Cần chốt |
|---|---|
| Ai dùng? | Lễ tân + Manager + Kế toán mặc định xem 3 dòng summary; Admin + BOD và user được cấp view_financial_pnl xem 8 dòng + cảnh báo lỗ |
| Vào màn để quyết định gì? | Lễ tân: nhắc khách thanh toán; Manager: đốc thu công nợ; BOD: kiểm tra đơn lỗ |
| Dữ liệu chính | 8 chỉ số từ GetOrderFinancialSummary action |
| CTA chính / phụ | Không CTA (read-only); CTA phụ "Thử lại" khi error |
| Điều không hiểu nhầm | "Lợi nhuận tạm tính" ≠ "Lợi nhuận kế toán" (phải có copy "Chưa gồm chi phí vật tư") |
B2.1A) Nguyên tắc UX bắt buộc
| Nguyên tắc | Quyết định cho UI/UX |
|---|---|
| Mục tiêu 5 giây | Khi mở đơn, user phải biết ngay: đơn còn nợ không, đã thu bao nhiêu, có rủi ro lỗ tạm tính không. |
| Thứ tự ưu tiên thông tin | Còn nợ → Lợi nhuận tạm tính → Đã thu → Doanh thu → nhóm chi phí. |
| Dữ liệu nhạy cảm | Không hiển thị placeholder khóa, blur, hoặc dòng bị che cho cost/profit/margin. Nếu không có view_financial_pnl, các dòng PnL không render. |
| Vật tư pending | Không coi chi phí vật tư là 0đ. Với user PnL, luôn hiện note "Chi phí vật tư đang phát triển..." khi API trả note_pending_material=true. |
| Tên gọi lợi nhuận | Luôn ghi "Lợi nhuận tạm tính"; không rút gọn thành "Lợi nhuận" trong label, tooltip, analytics hay test case. |
| Đơn đã hủy | Vẫn hiển thị theo đúng permission để đối soát, kèm trạng thái "Đơn đã hủy - chỉ đối soát"; không thêm CTA thao tác tài chính. |
| Thiết kế thị giác | UI spec chỉ khóa intent: summary, breakdown, warning, muted note, emphasis. Màu, font, spacing dùng design token hiện có của Quasar/Diva UI. |
B2.2) Bố cục (wireframe)
Variant B - User có PnL (view_financial_pnl) gắn vào UI hiện tại:
text
/e/service-order/:id
┌────────────────────────────────────────────────────────────────────────┐
│ [Header — Order code + status + customer name] (giữ nguyên) │
│ [POS create/refund block] (giữ nguyên) │
│ [ACCOUNT / CONTACT / HOA HỒNG / BRANCH / PROMOTION] (giữ nguyên) │
│ │
│ >>> SECTION TÀI CHÍNH MỚI <<< │
│ ┌──────────────────────────────────────────────┐ │
│ │ TÀI CHÍNH ⓘ │ │
│ │ ⚠ Đơn lỗ tạm tính: −250.000đ. │ ← banner (pnl only, │
│ │ Chưa gồm chi phí vật tư. [×] │ khi profit < 0) │
│ │ │ │
│ │ Doanh thu 3.500.000đ │ ← summary (3 dòng) │
│ │ Đã thu 1.500.000đ │ │
│ │ Còn nợ 2.000.000đ │ │
│ │ ──────────────────────────────────── │ │
│ │ Hoa hồng 350.000đ │ ← pnl (5 dòng tiếp) │
│ │ Chi phí tour 180.000đ │ │
│ │ Chi phí cố định 225.000đ ⓘ │ │
│ │ Lợi nhuận tạm tính 2.745.000đ │ │
│ │ Tỷ suất 78,43% │ │
│ │ │ │
│ │ ℹ Chi phí vật tư đang phát triển │ ← muted note (pnl) │
│ │ — chưa cộng vào lợi nhuận tạm tính. │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ [DỮ LIỆU] (giữ nguyên, đẩy xuống) │
│ [LIÊN KẾT] (giữ nguyên, đẩy xuống) │
└────────────────────────────────────────────────────────────────────────┘Variant A - User chỉ có summary (view_financial_summary):
text
┌──────────────────────────────────────────────┐
│ TÀI CHÍNH ⓘ │
│ Doanh thu 3.500.000đ │
│ Đã thu 1.500.000đ │
│ Còn nợ 2.000.000đ │
└──────────────────────────────────────────────┘Variant C - Không có quyền tài chính:
text
[PROMOTION] (giữ nguyên)
[DỮ LIỆU] (giữ nguyên)
[LIÊN KẾT] (giữ nguyên)
Ghi chú: Section TÀI CHÍNH không mount, không skeleton, không CTA xin quyền.Variant D - Đang tải / lỗi API:
text
┌──────────────────────────────────────────────┐
│ TÀI CHÍNH ⓘ │
│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │
│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │
│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │
└──────────────────────────────────────────────┘
Khi lỗi:
┌──────────────────────────────────────────────┐
│ TÀI CHÍNH ⓘ │
│ Không thể tải tài chính. Vui lòng thử lại. │
│ [Thử lại] │
└──────────────────────────────────────────────┘Variant E - Đơn đã hủy:
text
┌──────────────────────────────────────────────┐
│ TÀI CHÍNH ⓘ │
│ [Icon trạng thái] Đơn đã hủy - chỉ đối soát │ ← badge trạng thái
│ │
│ Doanh thu 3.500.000đ │ ← readonly
│ Đã thu 1.500.000đ │
│ Còn nợ 0đ │
│ ... │
└──────────────────────────────────────────────┘Variant F - Đơn cũ chưa có fixed cost rate:
text
┌──────────────────────────────────────────────┐
│ TÀI CHÍNH ⓘ │
│ Doanh thu 3.500.000đ │
│ Đã thu 1.500.000đ │
│ Còn nợ 2.000.000đ │
│ ──────────────────────────────────── │
│ Hoa hồng 350.000đ │
│ Chi phí tour 180.000đ │
│ Lợi nhuận tạm tính 2.970.000đ │
│ Tỷ suất 84,86% │
│ │
│ ℹ Đơn này tạo trước khi cấu hình chi phí cố │
│ định nên không có dòng "Chi phí cố định". │
└──────────────────────────────────────────────┘B2.3) Phân loại reuse + điểm update
| Phân loại | File hiện có | Vị trí update | Lý do |
|---|---|---|---|
| 🔧 Extend | OrderReceiverInfo.tsx:569-1452 | Trong cùng XCard lớn, sau PROMOTION (line ~1220), trước DỮ LIỆU (line ~1320) | Sidebar đã có chuỗi section, chèn vào đúng vị trí logic — sau scope nghiệp vụ (promotion), trước metadata (dữ liệu/liên kết) |
B2.4) Quy ước field / cột / CTA mới hoặc thay đổi
Ref về B0.4 Field × Surface matrix.
| Field/CTA | Loại | Hiển thị ở đâu | Mặc định | Validation | Điều kiện hiển thị | Tooltip | Ảnh hưởng export/search |
|---|---|---|---|---|---|---|---|
revenue | Read-only số | Sidebar dòng 1 | Từ order.amount | NULL → — | Có ≥1 financial action | "Tổng giá trị đơn hàng (chưa trừ giảm giá)" | N/A day-1 |
paid | Read-only số | Sidebar dòng 2 | Aggregate parent invoice | Có thể âm khi refund | Có ≥1 financial action | "Tổng tiền khách đã thanh toán" | N/A |
debt | Read-only số | Sidebar dòng 3 | order.debt_amount | NULL → 0đ | Có ≥1 financial action | "Số tiền khách còn nợ" | N/A |
commission | Read-only số | Sidebar dòng 4 | SUM order_commissions.amount | NULL → 0đ | Có view_financial_pnl | "Tổng hoa hồng phải trả nhân viên" | N/A |
tour_cost | Read-only số | Sidebar dòng 5 | SUM tour_money | NULL → 0đ | Có view_financial_pnl | "Tổng chi phí tour KTV" | N/A |
fixed_cost | Read-only số | Sidebar dòng 6 | order.fixed_cost_amount | NULL → ẩn dòng; 0 → "0đ" | Có view_financial_pnl AND fixed_cost_amount IS NOT NULL | "Overhead vận hành theo tỷ lệ thực thu cao nhất (high-water mark)" | N/A |
profit_estimated | Read-only số | Sidebar dòng 7 | FORMULA-007 | NULL → —; <0 → dùng negative-value state | Có view_financial_pnl | "Doanh thu − chi phí đã chốt. Chưa gồm chi phí vật tư." | N/A |
margin_estimated | Read-only % | Sidebar dòng 8 | FORMULA-008 | revenue=0 → — | Có view_financial_pnl | "Lợi nhuận tạm tính / Doanh thu × 100%" | N/A |
| Nút "Thử lại" | CTA secondary | Trong error state | — | — | API lỗi | "Thử lại tải tài chính" | — |
B2.5) Thanh lọc
| # | Component | Loại | Mặc định | Hành vi | Reset dây chuyền |
|---|---|---|---|---|---|
| — | Không có filter trong section (read-only theo order) | — | — | — | — |
B2.6) Bảng / nội dung
Section này không phải table-first → dùng "Content blocks": 1 banner cảnh báo (optional) + 8 rows label/value + 1 note pending (optional).
| Block | Style | Nội dung |
|---|---|---|
| Tiêu đề "TÀI CHÍNH" | Header section theo style section hiện có, có icon ⓘ tooltip | Cố định |
| Banner cảnh báo lỗ | Dùng warning severity/token hiện có, có icon cảnh báo và nút đóng | Animation ngắn khi mount; không dùng màu hard-code trong spec |
| Row label-value | Label trái, value phải, số tiền dễ scan và không làm vỡ layout sidebar | 3 rows với summary; 8 rows với PnL |
| Divider | Tách nhóm summary và nhóm chi phí/PnL | Chỉ hiện khi user có pnl |
| Note pending | Muted note/token phụ, có icon thông tin | Chỉ hiện khi user có pnl và note_pending_material=true |
B2.7) Quy ước tương tác
| Tình huống | Hành vi |
|---|---|
| Mount section | Tự động fetch action GetOrderFinancialSummary({order_id}) khi component mount |
| Refresh đơn (edit/payment) | Refetch action khi route.query.refresh thay đổi (theo pattern OrderDetail hiện có) |
| Đóng banner cảnh báo lỗ | Bấm × → banner ẩn cho session hiện tại; mở lại đơn → banner hiện lại (không persist localStorage) |
| Tooltip hover | Hover ⓘ icon 300ms delay → hiện tooltip |
| Permission revoke giữa session | Lần fetch tiếp theo → BE trả 403 → FE ẩn section, KHÔNG show error toast |
| Click vào số | Không có click action (read-only) |
B2.8) Ma trận phân quyền
| Portal | Vai trò | Action cần | Hiển thị | Khi thu hồi quyền |
|---|---|---|---|---|
| admin | Lễ tân (mặc định seed) | view_financial_summary | 3 dòng summary | Section ẩn sau refetch tiếp theo |
| admin | Manager chi nhánh (mặc định seed) | view_financial_summary | 3 dòng summary | Section ẩn sau refetch |
| admin | Kế toán (mặc định seed) | view_financial_summary | 3 dòng summary | Section ẩn sau refetch |
| admin | Admin (mặc định seed) | view_financial_pnl | 8 dòng đầy đủ + banner lỗ + note pending | Section ẩn sau refetch |
| admin | BOD (mặc định seed) | view_financial_pnl | 8 dòng đầy đủ + banner lỗ + note pending | Section ẩn sau refetch |
| admin | Khác (chưa cấp) | — | Ẩn section hoàn toàn (không skeleton, không leak) | — |
| pos/crm/staff | Tất cả role | (chưa cấp day-1) | Ẩn section | Có thể cấp sau qua Dynamic Permission |
B2.8A) Ma trận Role × Variant
| Role / nhóm user | Permission mặc định | Variant UI | Ghi chú bắt buộc |
|---|---|---|---|
| Lễ tân | view_financial_summary | Variant A | Không thấy chi phí, lợi nhuận, tỷ suất, banner lỗ |
| Manager chi nhánh | view_financial_summary | Variant A | Chỉ xem PnL nếu Admin cấp thêm view_financial_pnl |
| Kế toán | view_financial_summary | Variant A | Chỉ xem PnL nếu Admin cấp thêm view_financial_pnl |
| Admin | view_financial_summary + view_financial_pnl | Variant B | Thấy đủ PnL, banner lỗ, note vật tư pending |
| BOD | view_financial_summary + view_financial_pnl | Variant B | Thấy đủ PnL, banner lỗ, note vật tư pending |
| User không có quyền tài chính | — | Variant C | Section không mount, không skeleton, không toast |
User PnL mở đơn cũ fixed_cost_rate=NULL | view_financial_pnl | Variant F | Ẩn dòng "Chi phí cố định", hiện note giải thích |
B2.9) Ma trận trạng thái (ref B0.5)
| Trạng thái | Hiển thị |
|---|---|
| Đang tải | Skeleton 3 rows pulsing (skeleton match summary layout) |
| Rỗng | N/A (đơn luôn có data financial) |
| Lỗi API | "Không thể tải tài chính. Vui lòng thử lại." + nút "Thử lại" |
| Không quyền | Ẩn section hoàn toàn (route guard + permission check ở FE) |
| Đơn đã hủy | Section hiện theo đúng quyền + badge "Đơn đã hủy - chỉ đối soát" |
| Vật tư pending | Muted note "Chi phí vật tư đang phát triển..." cuối section |
B2.10) Phản hồi sau thao tác
| Hành động | Phản hồi UI | Copy mẫu | Hành động tiếp |
|---|---|---|---|
| Mở đơn (mount) | Skeleton → fade-in data | — | Hiển thị section |
| API lỗi | Inline error trong section | "Không thể tải tài chính. Vui lòng thử lại." | Bấm "Thử lại" → refetch |
| Đóng banner lỗ | Animation fade-out 150ms | — | Banner ẩn cho session |
| Hover tooltip | Tooltip popover | (theo dictionary B9) | — |
| Permission thay đổi (admin cấp/thu hồi) | Refetch order detail tự động | — | Section hiện/ẩn theo quyền mới |
B2.11) Mapping UI theo vòng đời
Đơn dịch vụ có lifecycle:
Bản nháp → Đã xác nhận → Đang phục vụ → Hoàn thành → Đã thanh toánhoặc→ Đã hủy. Section TÀI CHÍNH hiển thị mọi state.
| Trạng thái đơn | CTA hiển thị | Field sửa được | Badge / copy |
|---|---|---|---|
Bản nháp (status='draft') | Không (read-only) | Không | Badge trạng thái "Đơn chưa xác nhận" dùng muted state |
Đã xác nhận (status='confirmed') | Không | Không | Không badge |
Hoàn thành (status='completed') | Không | Không | Không badge |
Đã thanh toán đủ (debt=0) | Không | Không | Badge trạng thái "Đã thanh toán" trên dòng "Còn nợ" |
Đã hủy (status='cancelled') | Không | Không | Badge trạng thái "Đơn đã hủy - chỉ đối soát" trên đầu section |
B3) Luồng người dùng
Luồng 1 — Lễ tân nhắc khách thanh toán (đường thành công)
Lễ tân mở /e/service-order/ORD-202604-0123
→ Section TÀI CHÍNH skeleton 1 giây
→ Hiện: Doanh thu 3.500.000đ / Đã thu 1.500.000đ / Còn nợ 2.000.000đ
→ Lễ tân thấy còn nợ → bấm CONTACT block → click số ĐT
→ Gọi khách nhắc thanh toánLuồng 2 — Admin/BOD phát hiện đơn lỗ
BOD hoặc user có `view_financial_pnl` mở đơn → section hiện đầy đủ 8 dòng
→ Warning banner "Đơn lỗ tạm tính: −250.000đ. Chưa gồm chi phí vật tư."
→ BOD bấm tab "Hoa hồng" để check chi tiết
→ Phát hiện hoa hồng KTV cao bất thường → kiểm tra với ManagerLuồng 3 — Permission revoked giữa session (lỗi)
Manager đang xem đơn (3 dòng summary)
→ Admin vào Permission Matrix thu hồi `view_financial_summary` của Manager
→ Manager refresh đơn (F5 hoặc đổi tab)
→ API trả 403 → FE ẩn section hoàn toàn (không error toast)
→ Manager hỏi Admin → Admin cấp lại quyền → refresh → section hiện lạiLuồng 4 — Đơn đã hủy
Manager mở đơn ORD-202604-0099 (đã hủy)
→ Section TÀI CHÍNH hiện theo quyền hiện tại của Manager
→ Nếu chỉ có summary: hiện 3 dòng summary + badge "Đơn đã hủy - chỉ đối soát"
→ Nếu được cấp pnl: hiện đủ 8 dòng + badge "Đơn đã hủy - chỉ đối soát"
→ Tất cả số readonly, không có CTA actionB4) Đặc tả thông báo
| Điều kiện kích hoạt | Kênh | Mẫu nội dung | Chống trùng |
|---|---|---|---|
| (Day-1: KHÔNG có notification) | — | — | — |
| Phase 2: đơn lỗ phát hiện | Email gửi BOD | "Đơn {code} có lợi nhuận tạm tính âm: {amount}đ" | 1 email/đơn/ngày |
B5) Đặc tả xuất dữ liệu
Day-1: KHÔNG export. Phase 2: export Excel financial summary cho Manager (báo cáo theo tháng).
B6) Từ điển nội dung hiển thị
| Key | Tiếng Việt | EN (i18n) | Ngữ cảnh |
|---|---|---|---|
financial.section.title | Tài chính | Financial | Header section sidebar |
financial.row.revenue | Doanh thu | Revenue | Sidebar row 1 |
financial.row.paid | Đã thu | Paid | Sidebar row 2 |
financial.row.debt | Còn nợ | Debt | Sidebar row 3 |
financial.row.commission | Hoa hồng | Commission | Sidebar row 4 (pnl) |
financial.row.tour_cost | Chi phí tour | Tour cost | Sidebar row 5 (pnl) |
financial.row.fixed_cost | Chi phí cố định | Fixed cost | Sidebar row 6 (pnl) |
financial.row.profit_estimated | Lợi nhuận tạm tính | Profit estimated | Sidebar row 7 (pnl) |
financial.row.margin_estimated | Tỷ suất | Margin | Sidebar row 8 (pnl) |
financial.banner.loss | Đơn lỗ tạm tính: −{amount}đ. Chưa gồm chi phí vật tư. | Estimated loss order: −{amount}. Material cost not included. | Banner cảnh báo |
financial.note.pending_material | Chi phí vật tư đang phát triển — chưa cộng vào lợi nhuận tạm tính. | Material cost in development — not included in estimated profit. | Note cuối section (pnl) |
financial.badge.cancelled | Đơn đã hủy - chỉ đối soát | Cancelled - for reconciliation only | Badge đầu section; icon trạng thái do UI kit render |
financial.error.fetch_failed | Không thể tải tài chính. Vui lòng thử lại. | Failed to load financials. Please retry. | Error state |
financial.cta.retry | Thử lại | Retry | CTA error |
settings.field.fixed_cost.label | Tỷ lệ chi phí cố định (%) | Fixed cost rate (%) | App Settings field |
settings.field.fixed_cost.placeholder | Chưa cấu hình | Not configured | Input placeholder |
settings.field.fixed_cost.hint | Tỷ lệ % thực thu phân bổ cho chi phí cố định | Percentage of paid amount allocated for fixed cost | Hint dưới input |
settings.field.fixed_cost.error.range | Tỷ lệ phải trong khoảng 0-100% | Rate must be between 0-100% | Validation error |
B7) Sự kiện phân tích
| Sự kiện | Điều kiện kích hoạt | Thuộc tính | KPI liên quan |
|---|---|---|---|
financial_summary_viewed | User mở SCR-01 và section TÀI CHÍNH render thành công | user_role, permission_level (summary/pnl), order_id, is_cancelled | Time-to-financial-info |
financial_loss_warning_shown | Banner cảnh báo lỗ render | order_id, loss_amount, user_role | Số case BOD phát hiện đơn lỗ |
financial_loss_warning_dismissed | User bấm × đóng banner | order_id, time_visible_ms | UX feedback |
financial_summary_fetch_failed | API trả error | order_id, error_code | API performance |
settings_fixed_cost_rate_updated | Admin lưu rate mới | old_value, new_value | Audit |
B8) Quy tắc responsive và khả năng truy cập
Responsive
| Breakpoint | Hành vi |
|---|---|
| Desktop (≥1024px) | Sidebar 404px width, section TÀI CHÍNH nằm trong scroll-y nếu sidebar overflow |
| Tablet (768-1023px) | Sidebar thu hẹp theo layout hiện có; typography dùng responsive token, layout không đổi |
| Mobile (<768px) | .detail.is-inside 100% width, section TÀI CHÍNH stack full-width, label-value vẫn flex space-between (không stack vertical) |
Accessibility / keyboard
| Vấn đề | Quy tắc |
|---|---|
| Thứ tự focus | Section nằm trong tab order tự nhiên của sidebar, sau PROMOTION |
| Banner cảnh báo lỗ | role="alert" + aria-live="polite" |
| Tooltip ⓘ | aria-label="Xem giải thích {chỉ số}" cho icon |
| Nút × đóng banner | aria-label="Đóng cảnh báo" |
| Số tiền | Đọc bằng screen reader: "Doanh thu, ba triệu năm trăm ngàn đồng" (i18n locale vi-VN) |
| Skeleton loading | aria-busy="true" trong khi loading |
B9) Từ điển tooltip
| Màn | Field/Icon | Nội dung tooltip | Điều kiện hiện |
|---|---|---|---|
| SCR-01 | Tiêu đề "TÀI CHÍNH" ⓘ | "Tổng hợp tài chính của 1 đơn dịch vụ. Chưa gồm chi phí vật tư." | Hover icon ⓘ |
| SCR-01 | Doanh thu ⓘ (nếu cần) | "Tổng giá trị đơn hàng (chưa trừ giảm giá đã ghi vào order)" | Hover hàng |
| SCR-01 | Đã thu ⓘ | "Tổng tiền khách đã thanh toán, gồm cả hoàn tiền đã ghi nhận" | Hover hàng |
| SCR-01 | Còn nợ ⓘ | "Số tiền khách còn nợ theo order.debt_amount" | Hover hàng |
| SCR-01 | Hoa hồng ⓘ | "Tổng hoa hồng phải trả nhân viên đã ghi nhận" | Hover hàng (pnl) |
| SCR-01 | Chi phí tour ⓘ | "Tổng chi phí tour KTV theo project_task_assignee.tour_money" | Hover hàng (pnl) |
| SCR-01 | Chi phí cố định ⓘ | "Overhead vận hành phân bổ theo tỷ lệ % thực thu CAO NHẤT trong lịch sử (high-water mark — không giảm khi refund)" | Hover hàng (pnl) |
| SCR-01 | Lợi nhuận tạm tính ⓘ | "Doanh thu − Hoa hồng − Chi phí tour − Chi phí cố định. CHƯA gồm chi phí vật tư." | Hover hàng (pnl) |
| SCR-01 | Tỷ suất ⓘ | "Lợi nhuận tạm tính / Doanh thu × 100%" | Hover hàng (pnl) |
| SCR-02 | Tỷ lệ chi phí cố định (%) ⓘ | "Tỷ lệ % thực thu phân bổ cho chi phí cố định (mặt bằng, điện nước, vận hành). Chỉ áp dụng cho đơn mới tạo SAU khi lưu." | Hover icon ⓘ |
B-Trường hợp cá biệt
| # | Trường hợp | Hành vi kỳ vọng |
|---|---|---|
| 1 | Đơn cũ có fixed_cost_rate=NULL (tạo trước khi config) | Ẩn dòng "Chi phí cố định" trong section. Tooltip ⓘ tiêu đề ghi "Đơn này tạo trước khi cấu hình chi phí cố định." |
| 2 | Setting rate=0 (Admin set chủ động) | Hiện "Chi phí cố định: 0đ" (không ẩn) |
| 3 | Đơn có refund làm paid âm | Vẫn hiển thị paid âm với prefix −, ví dụ "Đã thu: −500.000đ" |
| 4 | Permission revoke đột ngột (admin cấu hình) | Lần fetch tiếp theo → ẩn section. KHÔNG show error toast |
| 5 | API timeout > 5s | Hiện error state + nút "Thử lại". Không loop retry tự động |
| 6 | Sidebar nhiều block → overflow trên màn nhỏ | CSS max-height: calc(100vh - 64px) + overflow-y: auto cho sidebar |
| 7 | Đơn có 0 commission, 0 tour, 0 vật tư | Vẫn render đầy đủ với "0đ" cho từng dòng (không ẩn) |
| 8 | revenue=0 (đơn lỗi) | Hiển thị Doanh thu: —, Tỷ suất: — (không tính được) |
| 9 | Khách đã thanh toán đủ (debt=0) | Badge trạng thái "Đã thanh toán" trên dòng "Còn nợ" |
| 10 | User vừa được cấp view_financial_pnl (refetch) | Section render lại với 8 dòng, có animation fade-in các row mới |
B-POST) Verification
B-POST.1) 12 Checkpoint completeness
- [x] As-Is đầy đủ: B0.1 inventory bao phủ 11 UI ID (3 cũ + 2 NEW + 6 KEEP/MOVE) với Evidence file:line
- [x] Delta status đầy đủ: mọi UI ID có Delta Status (KEEP/MOVE/NEW)
- [x] Field × Surface ma trận: 10 field/CTA mới đều có dòng B0.4 với 12 cột đầy đủ
- [x] State × Screen ma trận: 3 màn × 6 state đã có ở B0.5
- [x] Reuse classification: SCR-01 = 🔧 Extend, SCR-02 = 🔧 Extend, SCR-03 = ✅ Reuse + file path + delta
- [x] Wireframe context: B2.2 vẽ đầy đủ vùng KEEP (Header → POS → ACCOUNT → CONTACT → HOA HỒNG → BRANCH → PROMOTION → DỮ LIỆU → LIÊN KẾT) xung quanh section mới
- [x] CTA hierarchy: Section read-only → primary CTA = không có; secondary "Thử lại" cho error state
- [x] Validation đầy đủ: Field
fixed_cost_ratecó rule 0-100, max 2 decimals; format VN cho mọi số - [x] Permission matrix: B2.8 có 7 dòng phân quyền per portal × role
- [x] Confirm modal: N/A (section read-only, không có destructive action)
- [x] Empty với CTA: Section TÀI CHÍNH không có empty state (đơn luôn có data); SCR-02 placeholder "Chưa cấu hình"
- [x] Format VN: mọi tiền
1.250.000đ,−250.000đ; tỷ lệ78,43%,15,50%
B-POST.2) Cross-spec consistency check
- [x] Mọi field B0.4 có trong PRD A4 FR (revenue→FR-001, paid→FR-001/002, debt→FR-001/002, commission→FR-001 PnL, tour_cost→FR-001 PnL, fixed_cost→FR-001/004/005 PnL, profit_estimated→FR-001/007 PnL, margin_estimated→FR-001 PnL, fixed_cost_rate→FR-003)
- [x] Mọi field B0.4 có trong Dev Spec C4:
order.fixed_cost_rate,order.fixed_cost_amount, app_setting key - [x] Mọi action mới có TC trong QA:
financial_summary_viewed,loss_warning_shown, permission revoke - [x] Mọi state B0.5 có TC: loading, error, no-permission, partial pending
- [ ] Điền
_consistency-matrix.md→ tasking sau
B-QUALITY) Rà soát rủi ro thiếu sót
Rủi ro QA thường gặp
| # | Rủi ro | Đã cover ở |
|---|---|---|
| 1 | Thiếu test field mới | B0.4 ép trace 8 chỉ số + setting; QA ép TC theo từng field |
| 2 | Thiếu state empty/loading/error | B0.5 ma trận có 6 state cho 3 màn |
| 3 | Thiếu permission denied test | B2.8 + ma trận 7 dòng portal × role; QA TC-PERM-* |
| 4 | Thiếu confirm modal | N/A — section read-only, KHÔNG destructive (đã ghi rõ) |
| 5 | Thiếu format VN | B-POST.1 checkpoint #12 + Tooltip B9 dùng format VN |
| 6 | Boundary value không cụ thể | Validation fixed_cost_rate ∈ [0, 100] + 2 decimals; QA ép boundary -1, 0, 100, 100.01, 99.999 |
Rủi ro UI/UX thường gặp
| # | Rủi ro | Đã cover ở |
|---|---|---|
| 7 | Thêm field không nói nằm đâu | B0.4 cột "Hiển thị ở đâu" + B2.6 layout chi tiết với thứ tự rows |
| 8 | Wireframe không vẽ vùng KEEP | B2.2 vẽ đầy đủ Header → POS → ACCOUNT → CONTACT → HOA HỒNG → BRANCH → PROMOTION → SECTION MỚI → DỮ LIỆU → LIÊN KẾT |
| 9 | Field không có default | B2.4 cột "Mặc định" có giá trị cho mọi field |
| 10 | Empty state không có CTA | SCR-02 placeholder "Chưa cấu hình" + Section TÀI CHÍNH N/A (đơn luôn có data) |
| 11 | Modal không nói trigger | N/A — không dùng modal |
| 12 | Filter không nói default | N/A — không có filter trong section read-only |
Rủi ro PO/BA thường gặp
| # | Rủi ro | Đã cover ở |
|---|---|---|
| 13 | Spec ghi đè behavior cũ | B0.1 ghi rõ KEEP cho 9 block hiện hữu (Header/POS/ACCOUNT/...); MOVE cho 2 block (DỮ LIỆU, LIÊN KẾT) chỉ đẩy thứ tự |
| 14 | Out-of-scope không rõ | PRD A2 Non-goals 7 dòng có cột Lý do |
| 15 | Decision không có ≥2 phương án | PRD Z) DEC-001/002/003/012 đều có ≥2 phương án + lý do |
Cách dùng
- ✅ Mọi rủi ro thiếu sót đã có ghi nhận
- ⏳ Còn 1 gap:
_consistency-matrix.mdchưa sinh → next task