Appearance
Sales-CSKH Debt Follow-up & Handover — UI Spec
v2.0 — 15/05/2026
| Thay đổi | Section | Ảnh hưởng |
|---|---|---|
| KPI cards đổi đơn vị "Triệu" → VND đầy đủ + suffix "đ" (Ref BUG-PROD-002) | Spec KPI Cards | FE, QA |
| Tooltip "Doanh thu ròng" rewrite — phân biệt "ghi nhận kế toán ≠ tiền thực thu" (Ref DEC-032) | B9 Web Dashboard + KPI Cards table | FE, QA |
SCR-02 cột "Cuộc gọi" LOCKED — source ecommerce_user.last_contacted_at từ app mobile (Ref DEC-028) | SCR-02 columns + B9 SCR-02 tooltip | FE, QA |
| SCR-02 cột "% tham gia" — weighted avg multi-order + tooltip drill-down (Ref DEC-029 + FORMULA-008) | SCR-02 columns + B9 tooltip | FE, QA |
| SCR-02 cột "Dịch vụ" — tie-break rule + multi-DV format (Ref DEC-027 + FORMULA-007) | SCR-02 representative tie-break | FE, QA |
SCR-03 cột "Nhóm nợ"/"Quá hạn" — customer-level rollup max(overdue_days) (Ref DEC-023 + FORMULA-006B) | SCR-03 columns | FE, BE |
B4 Notification — operator >= + 4 re-trigger rules + NTF-DEBT-BUCKET-ESCALATE mới (Ref DEC-025/026) | B4 Notification Spec | BE, QA |
| Banner "Ưu tiên xử lý nợ" — as-of NOW + visual cue cam + sub-label cảnh báo (Ref DEC-033) | Spec Priority Card Strip | FE, BE |
| KPI Edge Cases bổ sung 5 cases (multi-order bucket, Manager view, Manager+Sale toggle, BOD no branch, DV null) | B-EdgeCases KPI | QA |
| Scheduler Edge Cases bổ sung 5 cases (cross-day resend, bucket transition, partial pay, settled drop, exact threshold) | B-EdgeCases Scheduler | BE, QA |
Canonical Inputs: Source-of-Truth là
SOURCE_OF_TRUTH.md(DEC-001 — codebase hiện tại là ground truth, HTML là intent). Slug: sales-cskh-debt-handover Version: v2.0 hardening pass · Updated: 2026-05-15 Spec Index: prd.md · dev-spec.md · qa-test-plan.md · go-live-checklist.md · handoff.md · _prod-issues.md
ⓘ Cách đọc UI Spec này
Đây là UI hardening spec cho feature đã build sẵn trong Diva, KHÔNG phải spec greenfield. Mỗi màn hình/component được mô tả với 3 lớp:
- Existing surface — route, file, screen tên thực trong codebase
- Spec target — ASCII wireframe, copy text, hành vi đầy đủ (từ HTML intent v4.12)
- Delta — phần khác biệt giữa hiện tại và target (Reuse 🟢 / Extend 🟡 / Build mới 🔴)
Quy tắc: Khi spec target khác với codebase hiện tại, codebase thắng (DEC-001). Nếu cần thay đổi codebase, ghi rõ trong "Delta" để dev biết chính xác phải sửa file nào.
§0. Codebase Mapping — Mỗi SCR/MOB ánh xạ tới surface nào
Bảng này là single source of mapping giữa SCR/MOB-IDs trong spec và file thực tế trong codebase. Dev đọc bảng này TRƯỚC khi đọc các section dưới.
Web (diva-admin) — Route /dm/debt
| SCR-ID | Tab/Route | Existing component | File path | Delta |
|---|---|---|---|---|
| SCR-01 | Layout /dm/debt (parent) | DebtManager.tsx | src/modules/debt-manager/pages/DebtManager.tsx | 🟢 Reuse — layout shell + tab nav |
| SCR-01 (Hiệu suất tư vấn tab) | /dm/debt/consulting-performance | DebtManagerConsultingPerformance.tsx | src/modules/debt-manager/component/consulting-performance/ | 🟡 Extend — bổ sung KPI cards mới (Khách mua trong kỳ, Tỉ lệ chuyển đổi) nếu thiếu |
| SCR-01-TAB-ANALYTICS | /dm/debt/statistics | DebtManagerStatistics.tsx | src/modules/debt-manager/component/statistics/ | 🟡 Extend — leaderboard + drawer chi tiết NV nếu chưa có |
| SCR-01-ANALYTICS-DRAWER | Drawer phải bên trong tab statistics | DebtManagerUserItem.tsx (anchor) | src/modules/debt-manager/component/ | 🟡 Extend — drawer 720px chi tiết KPI + Top 5 khách |
| SCR-02 | Drill-down từ KPI card | DebtManagerConsultingPerformance.tsx (drilldown handler) | src/modules/debt-manager/component/consulting-performance/ | 🟡 Extend — bảng chi tiết tư vấn theo lượt/khách |
| SCR-03 | /dm/debt/debt | DebtManagerDebt.tsx | src/modules/debt-manager/component/debt/ | 🟡 Extend — bổ sung scope chip vai trò + summary banner + daily focus |
| SCR-03-DRAWER | Drawer phải bên trong tab debt | DebtManagerActionViewCalendar.tsx + DebtManagerReminderScheduleHistory.tsx + DebtManagerItemReminderOrderList.tsx | src/modules/debt-manager/component/ | 🟢 Reuse — drawer logic đã có |
| SCR-03 Popup tạo lịch nhắc | Popup overlay | DebtManagerCreateReminderSchedule.tsx | src/modules/debt-manager/component/ | 🟢 Reuse — popup đã có |
| SCR-04 | Settings menu | DebtManagerSetting.tsx | src/modules/settings/pages/DebtManagerSetting.tsx | 🟢 Reuse — config form global/branch + history đã có |
| SCR-05 (Bước 1: chọn nguồn) | Wizard handover | CustomerHandoverSelectSource.tsx + CustomerHandoverSelectScope.tsx | src/modules/user/components/customer-handover/ | 🟢 Reuse — wizard sẵn |
| SCR-06 (Bước 2: chọn đích) | Wizard | CustomerHandoverSelectDestination.tsx + CustomerHandoverSelectStaffReception.tsx + CustomerHandoverSelectItemStaff.tsx | src/modules/user/components/customer-handover/ | 🟢 Reuse — preview wizard sẵn |
| SCR-07 (Bước 3: xác nhận) | Wizard | CustomerHandoverConfirmRecheck.tsx + CustomerHandoverConfirm.tsx | src/modules/user/components/customer-handover/ | 🟢 Reuse — confirm wizard sẵn |
| SCR-08 (Kết quả + audit) | Detail page | CustomerHandoverDetail.tsx + CustomerHandoverHistoryTable.tsx | src/modules/user/components/customer-handover/customer-handover-detail/ + customer-handover-history/ | 🟢 Reuse — detail + history sẵn; rollback action call BE rollback_customer_handover |
| Filter bar (date + staff + branch) — chung | Header tất cả tab | DebtManagerFilter.tsx | src/modules/debt-manager/component/ | 🟢 Reuse |
| Charts (aging, line trend) | Bên trong tabs | DebtLineChart.tsx, DebtProgressBar.tsx | src/modules/debt-manager/component/ | 🟢 Reuse |
| Export XLSX | CTA toàn page | XExcel.tsx core + XTable.tsx export feature | src/components/core/file/, src/components/core/table/ | 🟢 Reuse |
Mobile (diva-flutter staff) — Module debt_management
| MOB-ID | Existing screen | File path | Delta |
|---|---|---|---|
| MOB-01 | debt_management_screen.dart (2 tabs: Consultation + Debt) | lib/presentation/modules/debt_management/debt_management/views/ | 🟡 Extend — thêm KPI cards layout cho tab Consultation overview |
| MOB-02 | debt_management_debt_tab.extension.dart (debt tab body, ListView khách nợ) | lib/presentation/modules/debt_management/debt_management/views/ | 🟡 Extend — pre-filter overdue + scope chip vai trò |
| MOB-03 | debt_customer_detail_screen.dart (chi tiết khách nợ + collapsible sections) | lib/presentation/modules/debt_management/debt_customer_detail/views/ | 🟡 Extend — thêm section Lịch sử LH + lịch nhắc collapsible |
| MOB-04 | debt_create_reminder_sheet.dart (bottom sheet 1038 dòng, form đầy đủ) | lib/presentation/modules/debt_management/widgets/ | 🟢 Reuse |
| MOB-05 | notification_screen.dart + AppNotificationRoute.dart (deep link handler) | lib/presentation/modules/notification/views/, lib/presentation/route/ | 🟡 Extend — thêm notification types: isDebtHandoverCompleted, action card |
| Reusable widget — khách nợ card | debt_customer_summary_card.dart | lib/presentation/modules/debt_management/widgets/ | 🟢 Reuse |
| Reusable widget — aging badge | debt_overdue_badge.dart + debt_status_pill.dart | lib/presentation/modules/debt_management/widgets/ | 🟢 Reuse |
| Reusable widget — click-to-call | debt_click2call.dart | lib/presentation/modules/debt_management/widgets/ | 🟢 Reuse |
Backend surfaces được FE/Mobile tham chiếu
| Surface | Backend file/action | Tham chiếu trong UI |
|---|---|---|
| Function aging buckets (0-29/30-59/60-89/≥90) | customer_debt_dashboard_order_scope() — migration 1776054869987_add_customer_debt_statistics_dashboard_apis | SCR-01 KPI cards · SCR-03 badge · MOB-01/02 |
| Result table dashboard | dashboard_customer_debt_overview_result, dashboard_customer_debt_ranking_result, dashboard_customer_debt_trend_result | SCR-01 KPI · SCR-01-TAB-ANALYTICS leaderboard |
| Search function | search_report_customer_debt_management | SCR-03 table · MOB-02 list |
| Daily alert scheduler | services/ecommerce-api/scheduler/daily_debt_alert.go + debt_alert_config + debt_alert_log | NTF-DEBT-DAILY-001 + SCR-04 config |
| Handover action | customer_handover_support (action), rollback_customer_handover (action) | SCR-05→08 |
| Followup task lifecycle | debt_followup_task table + debt_followup_notification_schedule table + event trigger debt_followup_task_notification | SCR-03-DRAWER · Popup tạo lịch · MOB-04 |
| Contact log | debt_contact_log (record liên hệ + optional mark task done) | Nút [✓ Đã LH] |
Nguồn evidence chi tiết: Xem
EVIDENCE_PACK.mdđể có quote SQL/Go/TS với line numbers.
§1. Trạng thái hardening — Map FR → Delta
Bảng này tóm tắt FR-001 đến FR-020 và phần nào cần build/sửa cho hardening pass:
| FR-ID | Mô tả ngắn | Codebase status | Delta cần làm |
|---|---|---|---|
| FR-001 | Bộ lọc thời gian + nhân sự | 🟢 Reuse DebtManagerFilter.tsx | Đảm bảo filter dùng branch_id từ Hasura header + scope chip |
| FR-002 | DS khách đã tư vấn | 🟢 Reuse DebtManagerConsultingPerformance.tsx | Bổ sung toggle "Theo lượt / Theo khách" nếu chưa có |
| FR-003 | KPI tiếp khách | 🟢 Reuse aging function + result table | KPI card layout — extend nếu thiếu |
| FR-004 | KPI mua + chuyển đổi | 🟡 Extend formulas (xem prd.md A10 FORMULA-002) | Đảm bảo formula dùng đúng cột |
| FR-005 | KPI công nợ | 🟢 Reuse dashboard_customer_debt_overview_result | — |
| FR-006 | KPI thời gian thu nợ | 🟡 Extend formula FORMULA-005 (avg_days_to_collect) | Validate SQL |
| FR-007 | Aging buckets | 🟢 Reuse function customer_debt_dashboard_order_scope() | — |
| FR-008 | Cấu hình ngưỡng cảnh báo | 🟢 Reuse DebtManagerSetting.tsx + debt_alert_config | Validate UI hardening (history view + branch override) |
| FR-009 | Cảnh báo nợ hằng ngày | 🟢 Reuse scheduler daily_debt_alert + planner + debt_alert_notification_schedule | Validate runbook + monitor |
| FR-010 | Workflow bàn giao khách | 🟢 Reuse CustomerHandover* wizard | Validate limit 200 khách/lần + rollback 24h |
| FR-011 | Audit handover | 🟢 Reuse customer_handover_log + CustomerHandoverHistoryTable.tsx | — |
| FR-012 | Export XLSX | 🟢 Reuse XExcel.tsx + XTable.tsx | Đảm bảo respect filter + permission |
| FR-013 | Quản lý lịch nhắc follow-up | 🟢 Reuse debt_followup_task + DebtManagerCreateReminderSchedule.tsx + drawer | Validate notification flow + permission |
| FR-014…020 | Tab Thống kê (Admin/Manager) | 🟡 Extend DebtManagerStatistics.tsx | Thêm leaderboard, segment so sánh, drawer NV |
Marker convention: 🟢 = code đã đủ, chỉ cần test/doc · 🟡 = code có ~80%, cần extend nhỏ · 🔴 = phải build mới (KHÔNG có FR nào ở mức này — đã verify trong discovery).
§2. Nguyên tắc UI hardening
- Không tạo route mới —
/dm/debt/{consulting-performance|debt|statistics}đã có; thay đổi label/copy text trong i18n nếu cần khớp HTML target. - Không tạo component mới khi component có hơn 80% logic giống — chỉ extend.
- Permission ground truth là Dynamic Permission v2 (DEC-011 —
module_id=debt_manager, actionaccess|create|update|view_all|export). KHÔNG hard-code theo role name. Wireframe permission matrix dưới đây mô tả intent — implementation phải resolve qua permission action. - Branch scoping qua
X-Hasura-Branch-Idheader — Manager chọn 1 trong N branch đang quản lý (URQL header phải set khi đổi branch picker). - Sensitive field least-data (DEC-012) — số điện thoại, debt_amount: BE phải apply permission, FE chỉ là defense-in-depth.
- Wireframe ASCII trong spec dưới đây mô tả TARGET STATE sau hardening. Codebase hiện tại có thể đã match, hoặc cần extend nhỏ. Dev check side-by-side trước khi sửa.
§3. Phần chính: chi tiết UI/UX (B1 → B-Onboarding)
Từ section dưới đây trở đi là nội dung UI spec đầy đủ chuyển hóa từ HTML intent v4.12. Mọi SCR/MOB đều ánh xạ tới existing component theo §0 ở trên.
B1. Screen Map
WEB (Desktop)
├── 📊 Báo cáo
│ ├── SCR-01 Báo cáo hiệu suất tư vấn & công nợ
│ │ ├── SCR-01-TAB-ANALYTICS Tab Thống kê (Admin/Manager only)
│ │ │ └── SCR-01-ANALYTICS-DRAWER Chi tiết nhân viên (drawer phải)
│ │ ├── SCR-02 Chi tiết danh sách tư vấn
│ │ ├── SCR-03 Danh sách nợ quá hạn cần xử lý
│ │ │ └── SCR-03-DRAWER Quản lý lịch nhắc chăm sóc (drawer phải)
│ │ └── SCR-04 Cài đặt cảnh báo nợ quá hạn
│ │
│ └── 👥 Bàn giao khách hàng
│ ├── SCR-05 Bước 1 — Chọn nhân viên nghỉ & phạm vi bàn giao
│ ├── SCR-06 Bước 2 — Chọn người tiếp nhận & xem trước
│ ├── SCR-07 Bước 3 — Kiểm tra & xác nhận bàn giao
│ └── SCR-08 Kết quả bàn giao & lịch sử thao tác
MOBILE (Diva Partner App)
├── 🏠 MOB-01 Trang chủ — Hiệu suất cá nhân
├── 📋 MOB-02 Danh sách khách nợ quá hạn
├── 👤 MOB-03 Chi tiết khách nợ & lịch nhắc
├── 📅 MOB-04 Tạo lịch nhắc chăm sóc (bottom sheet)
└── 🔔 MOB-05 Trung tâm thông báoB2. Screen Inventory
| Screen | Tiêu đề hiển thị trên UI | Mô tả ngắn | Persona | FR | Platform | Mở từ đâu |
|---|---|---|---|---|---|---|
| SCR-01 | Báo cáo hiệu suất tư vấn & công nợ | Tổng quan KPI + bảng danh sách khách | Sale, CSKH, Manager | FR-001…007, 012 | Web | Menu [Báo cáo] |
| SCR-01-TAB-ANALYTICS | Thống kê | KPI quản trị công nợ + chart + leaderboard nhân sự | Manager, Admin | FR-014…020 | Web | Tab Thống kê trong SCR-01 |
| SCR-01-ANALYTICS-DRAWER | Chi tiết nhân viên | Drawer KPI nợ chi tiết + aging + Top 5 khách nợ | Manager, Admin | FR-019 | Web | Bấm tên nhân viên trong leaderboard |
| SCR-02 | Chi tiết danh sách tư vấn | Xem từng lượt / khách duy nhất đã tư vấn | Manager, Team lead | FR-002, 003, 004 | Web | Bấm card KPI trên SCR-01 |
| SCR-03 | Nợ quá hạn cần xử lý | Danh sách khách nợ + hành động gọi/nhắc/đánh dấu | Sale, CSKH, Manager | FR-005, 007, 009, 013 | Web | Bấm card “Khách nợ” trên SCR-01 hoặc noti nợ |
| SCR-03-DRAWER | Lịch nhắc chăm sóc của tôi | Drawer quản lý tất cả lịch nhắc + timeline theo khách | Sale, CSKH, Manager | FR-013 | Web | Bấm nút [📅 Lịch nhắc] trên SCR-03 |
| SCR-04 | Cài đặt cảnh báo nợ quá hạn | Cấu hình ngưỡng ngày + giờ gửi + override chi nhánh | Admin, Manager | FR-008 | Web | Menu [Cài đặt] |
| SCR-05 | Bàn giao khách — Bước 1: Chọn nguồn | Chọn nhân viên nghỉ + phạm vi + ngày hiệu lực | Manager, HR Ops | FR-010 | Web | Menu [Khách hàng] → [Bàn giao] |
| SCR-06 | Bàn giao khách — Bước 2: Xem trước | Chọn người nhận + xem so sánh trước/sau | Manager, HR Ops | FR-010 | Web | Bấm [Tiếp theo] từ SCR-05 |
| SCR-07 | Bàn giao khách — Bước 3: Xác nhận | Tóm tắt + cam kết + ghi chú | Manager, HR Ops | FR-010, 011 | Web | Bấm [Tiếp theo] từ SCR-06 |
| SCR-08 | Kết quả bàn giao & lịch sử thao tác | Xem audit trail + rollback trong 24h | Manager, HR Ops | FR-011 | Web | Sau khi xác nhận SCR-07 |
| MOB-01 | Hiệu suất cá nhân | KPI tư vấn + chốt + nợ cho bản thân | Sale, CSKH, Telesale | FR-003…005, 007 | Mobile | Tab [🏠 Home] |
| MOB-02 | Khách nợ quá hạn | Danh sách khách nợ + CTA gọi/nhắc/đánh dấu | Sale, CSKH, Telesale | FR-005, 007, 009 | Mobile | Tab [📋 Nợ] hoặc tap push noti |
| MOB-03 | Chi tiết khách nợ & lịch nhắc | Thông tin nợ + dịch vụ + lịch sử LH + lịch nhắc | Sale, CSKH, Telesale | FR-005, 009, 013 | Mobile | Tap 1 khách trên MOB-02 |
| MOB-04 | Tạo lịch nhắc chăm sóc | Bottom sheet nhập ngày/giờ/nội dung nhắc | Sale, CSKH, Telesale | FR-009, 013 | Mobile | Bấm [📅 Lịch] trên MOB-02/03 |
| MOB-05 | Trung tâm thông báo | Danh sách noti nợ + bàn giao | Sale, CSKH, Telesale | FR-009, FR-013 | Mobile | Tab [🔔 Thông báo] |
B3. Luồng thao tác chính (User Flows)
| Flow | Mô tả | Các bước chi tiết | FR |
|---|---|---|---|
| FLOW-01 | Nhân viên xem báo cáo ngày (web) | Menu [Báo cáo] → SCR-01 → chọn bộ lọc → xem KPI → bấm [Xuất XLSX] | FR-001…004, 012 |
| FLOW-02 | Xử lý cảnh báo nợ (web) | Nhận noti in-app → bấm noti → SCR-03 → xem danh sách → bấm [📞 Gọi] / [✓ Đã LH] → bấm [→ CRM] mở chi tiết khách | FR-005, 007, 009 |
| FLOW-02M | Xử lý cảnh báo nợ (mobile) | Nhận push → tap noti → MOB-02 → bấm khách → MOB-03 → bấm [📞 Gọi] → quay lại → đánh dấu [✓ Đã LH] | FR-005, 007, 009 |
| FLOW-03 | Manager so sánh hiệu suất nhân viên | Menu [Báo cáo] → SCR-01 → chọn Chi nhánh → chọn Vai trò → xem bảng so sánh → bấm tên khách → SCR-02 | FR-004…007, 012 |
| FLOW-04 | Cấu hình ngưỡng cảnh báo | Menu [Cài đặt] → SCR-04 → sửa ngưỡng ngày / giờ gửi → bấm [Lưu thay đổi] | FR-008 |
| FLOW-05 | Bàn giao khách khi nhân sự nghỉ | Menu [Khách hàng] → [Bàn giao] → SCR-05 chọn nguồn → [Tiếp theo] → SCR-06 chọn đích + xem preview → [Tiếp theo] → SCR-07 tick cam kết + [Xác nhận] → SCR-08 xem kết quả | FR-010, 011 |
| FLOW-06 | Tạo và theo dõi lịch nhắc (web) | SCR-03 → bấm [📅] cạnh khách → popup tạo lịch → bấm [Tạo lịch] → bấm [📅 Lịch nhắc] trên toolbar → SCR-03-DRAWER mở → xem danh sách → bấm [✅ Đã xong] | FR-013 |
| FLOW-06M | Tạo và theo dõi lịch nhắc (mobile) | MOB-02 → tap khách → MOB-03 → bấm [📅 Lịch] → MOB-04 bottom sheet → tạo xong → cuộn xuống section “Lịch nhắc” xem lại → bấm [✅ Xong] | FR-013 |
| FLOW-07 | Nhận nhắc follow-up (web) | Đến giờ nhắc → noti in-app xuất hiện → bấm noti → SCR-03 → drawer tự mở filter theo khách → xử lý | FR-013 |
| FLOW-08 | Manager/Admin xem thống kê công nợ | Menu [Báo cáo] → SCR-01 → tab Thống kê → xem 6 KPI + 2 biểu đồ + leaderboard → bấm nhân viên mở drawer → bấm [Xem chi tiết] quay về tab Công nợ với filter nhân sự | FR-014…020 |
B3.1 Bản đồ điều hướng giữa các màn hình (Navigation Map)
Web Desktop
MENU CHÍNH
│
┌─────────────┼──────────────────┐
▼ ▼ ▼
[Báo cáo] [Khách hàng] [Cài đặt]
│ │ │
▼ ▼ ▼
SCR-01 SCR-05 SCR-04
Dashboard Bàn giao B1 Cấu hình
│ │ cảnh báo
┌────────┼────────┐ ▼
▼ ▼ ▼ SCR-06
SCR-02 SCR-03 Xuất Bàn giao B2
Chi tiết Nợ quá XLSX │
tư vấn hạn ▼
│ │ SCR-07
▼ │ Xác nhận
[→ CRM] ├───────┐ │
(tab mới) ▼ ▼ ▼
SCR-03 Popup SCR-08
DRAWER Tạo Kết quả
Lịch lịch + Audit
nhắc nhắc + RollbackMobile (Diva Partner App)
BOTTOM TAB BAR
│
┌─────────┼──────────┬──────────┐
▼ ▼ ▼ ▼
[🏠 Home] [📋 Nợ] [🔔 TB] [👤 Tôi]
│ │ │
▼ ▼ ▼
MOB-01 MOB-02 MOB-05
KPI Nợ quá Thông báo
cá nhân hạn
│ │
│ ▼
│ MOB-03
│ Chi tiết
│ khách nợ
│ │
│ ├──────────┐
│ ▼ ▼
│ MOB-04 Section
│ Tạo lịch Lịch nhắc
│ nhắc
│
└──→ MOB-02 (tap card Nợ / chart nhóm nợ)Bảng điều hướng chi tiết (Bấm vào đâu → Mở màn nào)
Từ SCR-01 (Dashboard)
| Vị trí bấm | Đích đến | Hành vi | Ghi chú |
|---|---|---|---|
| Card “Lượt tư vấn” | → SCR-02 | Chuyển trang, giữ bộ lọc thời gian | Filter = tất cả trạng thái |
| Card “Khách đã tư vấn” | → SCR-02 | Chuyển trang, toggle = “Theo khách đã tư vấn” | Auto toggle |
| Card “Khách mua trong kỳ” | → SCR-02 | Chuyển trang, filter = “Đã mua trong kỳ” | Pre-filter trạng thái |
| Card “Khách đang nợ” | → SCR-01 / tab Công nợ | Chuyển sang tab Công nợ, giữ filter hiện tại | Giữ filter hiện tại |
| Card “Khách nợ quy đổi” | → SCR-01 / tab Công nợ | Chuyển sang tab Công nợ, giữ filter hiện tại | — |
| Card “Tổng tiền nợ” | → SCR-01 / tab Công nợ | Chuyển sang tab Công nợ, giữ filter hiện tại | Giữ filter hiện tại |
| Card “Tiền nợ quy đổi” | → SCR-01 / tab Công nợ | Chuyển sang tab Công nợ, giữ filter hiện tại | — |
| Tab “Thống kê” | → SCR-01-TAB-ANALYTICS | Chuyển nội dung trong SCR-01 sang view thống kê (chỉ Manager/Admin) | Giữ top filter hiện tại |
| Tên NV trong leaderboard | → SCR-01-ANALYTICS-DRAWER | Mở drawer phải, hiển thị chi tiết nhân viên | Drawer 720px |
| CTA [Xem chi tiết] trong drawer | → SCR-01 / tab Công nợ | Đóng drawer, chuyển tab Công nợ, set filter Nhân sự = {staff_id} | Cuộn tới bảng nợ |
| Aging bar chart (click bucket) | → Bảng BLOCK D bên dưới | Nếu đang ở tab Công nợ: cuộn xuống bảng + apply filter bucket tương ứng | Cùng trang, cuộn |
| Tên khách trong bảng | → CRM | Mở tab mới đến trang chi tiết khách CRM | Tab mới |
| Nút [→ CRM] trong bảng | → CRM | Mở tab mới | Tab mới |
| Nút [Xuất XLSX] | → Download file | Tải file XLSX theo bộ lọc đang chọn | Async nếu > 50k rows |
Từ SCR-02 (Chi tiết tư vấn)
| Vị trí bấm | Đích đến | Hành vi | Ghi chú |
|---|---|---|---|
| [← Quay lại Dashboard] | → SCR-01 | Quay lại, giữ bộ lọc thời gian | Breadcrumb |
| Tên khách trong bảng | → CRM | Mở tab mới | Tab mới |
| Nút [Xuất XLSX] | → Download file | Tải file theo filter + quyền | — |
Từ SCR-03 (Nợ quá hạn)
| Vị trí bấm | Đích đến | Hành vi | Ghi chú |
|---|---|---|---|
| Nút [📞 Gọi] cạnh khách | → Gọi điện | Mở tính năng gọi trên web/SIP | — |
| Nút [📅 Tạo lịch] cạnh khách | → Popup tạo lịch nhắc | Mở popup, điền sẵn tên khách + tiền nợ | Popup (overlay) |
| Nút [✓ Đã LH] cạnh khách | → Cập nhật tại chỗ | Toggle trạng thái liên hệ, không chuyển trang | Inline action |
| Nút [→ CRM] cạnh khách | → CRM chi tiết khách | Mở tab mới | Tab mới |
| Nút [📅 Lịch nhắc (N)] trên toolbar | → SCR-03-DRAWER | Mở drawer phải, hiện tất cả lịch nhắc trong scope filter hiện tại | Drawer 420px |
| [Lọc nhóm ≥90 →] trên summary banner | → Filter bảng | Giữ nguyên filter hiện tại, tự filter Nhóm nợ = ≥90, cùng trang | Cùng trang |
| Nút [Xuất XLSX] | → Download file | Tải file XLSX theo tab + filter | — |
| Tên khách trong bảng | → CRM | Mở tab mới | Tab mới |
Từ SCR-03-DRAWER (Drawer lịch nhắc)
| Vị trí bấm | Đích đến | Hành vi | Ghi chú |
|---|---|---|---|
| Nút [✅ Đã xong] | → Cập nhật tại chỗ | Chuyển status → done, cập nhật UI ngay | Inline action |
| Nút [❌ Hủy] | → Popup xác nhận | Hỏi “Bạn chắc muốn hủy lịch nhắc này?” → [Xác nhận] / [Quay lại] | Confirm dialog |
| Nút [📞 Gọi] | → Gọi điện | Mở tính năng gọi | — |
| Nút [+ Tạo lịch nhắc mới] | → Popup tạo lịch nhắc | Mở popup tạo mới, drawer vẫn mở phía sau | Popup + drawer |
| Tên khách | → CRM chi tiết | Mở tab mới | Tab mới |
| Nút [✕] đóng drawer | → SCR-03 (đóng drawer) | Đóng drawer, quay lại bảng nợ | — |
Từ SCR-04 (Cấu hình cảnh báo)
| Vị trí bấm | Đích đến | Hành vi | Ghi chú |
|---|---|---|---|
| Nút [Lưu thay đổi] | → Cập nhật tại chỗ | Lưu config, hiện toast “Đã lưu. Hiệu lực từ lần chạy tiếp theo.” | Toast thành công |
| Nút [+ Thêm override] | → Inline form | Mở dòng mới ngay trong bảng | Inline |
| Nút [Sửa] cạnh branch | → Inline edit | Chuyển dòng sang chế độ sửa | Inline |
| Nút [Xóa] cạnh branch | → Popup xác nhận | “Xóa cấu hình riêng của chi nhánh này?” → [Xác nhận] | Confirm dialog |
Từ SCR-05 → 06 → 07 → 08 (Bàn giao)
| Vị trí bấm | Đích đến | Hành vi | Ghi chú |
|---|---|---|---|
| SCR-05: Nút [Tiếp theo: Chọn đích →] | → SCR-06 | Chuyển bước wizard, validate trước khi qua | Validate: nguồn + phạm vi + ngày |
| SCR-06: Nút [Tiếp theo: Xác nhận →] | → SCR-07 | Chuyển bước wizard | Validate: đích đã chọn |
| SCR-06: Nút [← Quay lại] | → SCR-05 | Quay lại bước trước, giữ data đã nhập | Giữ state |
| SCR-07: Nút [Xác nhận bàn giao] | → SCR-08 | Thực hiện handover, chuyển sang kết quả | Disabled khi chưa tick đủ 2 checkbox |
| SCR-07: Nút [← Quay lại] | → SCR-06 | Quay lại bước 2 | Giữ state |
| SCR-08: Nút [↩ Hoàn tác bàn giao] | → Popup xác nhận rollback | Mở popup, yêu cầu nhập lý do | Chỉ hiện khi còn trong 24h |
| SCR-08: Nút [← Về Dashboard] | → SCR-01 | Quay về dashboard chính | — |
| SCR-08: Nút [Xem danh sách khách] | → SCR-03 filter theo handover | Mở danh sách nợ, filter owner = người nhận | Pre-filter |
| Mỗi bước: Nút [Hủy] | → Popup xác nhận huỷ | “Bạn chắc muốn huỷ? Dữ liệu đã nhập sẽ mất.” → [Xác nhận] / [Tiếp tục] | Confirm dialog |
Mobile — Điều hướng giữa màn hình
| Từ | Vị trí bấm | Đích đến | Hành vi | | --------------- | ------------------------------------- | ------------------------- | ------------------------------------- | -------------------- | | MOB-01 | Tap card “Khách nợ” | → MOB-02 | Navigate danh sách nợ theo filter hiện tại | Giữ filter hiện tại | | MOB-01 | Tap chart nhóm nợ | → MOB-02 | Navigate + pre-filter nhóm nợ đã chọn | Pre-filter | | MOB-01 | Nút [→ Xem danh sách nợ quá hạn] | → MOB-02 | Navigate danh sách nợ | — | | MOB-02 | Tap 1 dòng khách | → MOB-03 | Navigate đến chi tiết khách nợ | Truyền customer_id | | MOB-02 | Nút [Gọi ngay] trên card | → Gọi điện (native) | Mở phone app, quay lại hỏi “Đã LH?” | Native intent | | MOB-02 | Nút [Tạo lịch] trên card | → MOB-04 bottom sheet | Mở bottom sheet tạo lịch nhắc | Bottom sheet | | MOB-02 | Nút [⋯] trên card | → Menu action | Mở action sheet: Đã liên hệ / Xem chi tiết / CRM | Inline menu | | MOB-03 | Nút [📞 Gọi ngay] | → Gọi điện (native) | Mở phone app | Native intent | | MOB-03 | Nút [📅 Lịch] | → MOB-04 bottom sheet | Tạo lịch nhắc cho khách này | Bottom sheet | | MOB-03 | Nút [→ CRM] | → Web CRM (external) | Mở browser ngoài app | External link | | MOB-03 | Nút [✅ Xong] trong section lịch nhắc | → Cập nhật tại chỗ | Chuyển status done, refresh section | Inline action | | MOB-03 | Nút [❌ Hủy] trong section lịch nhắc | → Confirm bottom sheet | Xác nhận hủy | Bottom sheet | | MOB-03 | Nút [+ Tạo] trong section lịch nhắc | → MOB-04 bottom sheet | Tạo lịch nhắc mới | Bottom sheet | | MOB-05 | Tap noti “Nợ quá hạn” | → MOB-02 | Navigate vào danh sách nợ theo payload deeplink | Deeplink | | MOB-05 | Tap noti “Bàn giao” | → MOB-01 | Navigate, refresh KPI | Deeplink | | Bottom tab [🏠] | — | → MOB-01 | Tab Home | Always available | | Bottom tab [📋] | — | → MOB-02 | Tab Nợ | Always available | | Bottom tab [🔔] | — | → MOB-05 | Tab Thông báo | Badge = unread count | | Bottom tab [👤] | — | → Trang cá nhân | Cài đặt, đăng xuất | — |
Từ Notification (Web In-app)
| Noti | Bấm vào | Đích đến | Hành vi |
|---|---|---|---|
| NTF-DEBT-DAILY-001 | Bấm noti | → SCR-01 / tab Công nợ | Chuyển sang tab Công nợ, apply pre-filter theo payload (ưu tiên nhóm nợ cao nhất) |
| NTF-HANDOVER-TARGET | Bấm noti | → SCR-08 (kết quả handover) | Chuyển đến trang kết quả bàn giao |
| NTF-HANDOVER-CREATOR | Bấm noti | → SCR-08 | Chuyển đến trang kết quả bàn giao |
| NTF-FOLLOWUP-REMIND | Bấm noti | → SCR-01 / tab Công nợ + tự mở SCR-03-DRAWER | Mở tab Công nợ, drawer filter theo customer_id từ noti |
B4. Notification Spec (Web In-app + Mobile Push)
| Template ID | Platform | Trigger | Title | Body | Dedupe |
|---|---|---|---|---|---|
| NTF-DEBT-DAILY-001 | In-app + Mobile Push | Job hằng ngày, overdue_days >= threshold_days (Ref DEC-025) — gửi mỗi ngày nếu vẫn quá hạn (Ref DEC-026 rule 1) | ⚠️ Nợ quá hạn cần xử lý | "Bạn có khách nợ ≥ ngày hôm nay. Tổng nợ: đ" | 1 lần/ngày/(owner, customer, current_bucket) |
| NTF-DEBT-BUCKET-ESCALATE | In-app + Mobile Push | Customer chuyển bucket lên cấp cao hơn (Ref DEC-026 rule 2) — vd watch_list → high_risk | ⚠️ Khách chuyển nhóm rủi ro | "Khách chuyển sang nhóm ( ngày). Nợ: đ" | Per (owner, customer, bucket_transition) |
| NTF-HANDOVER-TARGET | In-app + Mobile Push | Handover confirm thành công | ✅ Khách mới được bàn giao | " khách từ đã chuyển cho bạn (hiệu lực )" | Per handover ID |
| NTF-HANDOVER-CREATOR | In-app | Handover confirm thành công | ✅ Bàn giao thành công | "Bàn giao — khách từ sang đã hoàn tất" | Per handover ID |
| NTF-FOLLOWUP-REMIND | In-app (web only) | Scheduler kiểm tra remind_at ≤ now() | 📅 Nhắc follow-up khách nợ | "Nhắc: — Khách , nợ đ ( ngày)" | Per task_id |
Quy tắc bất biến:
- NTF-DEBT-DAILY-001: Chỉ gửi cho owner chính, chỉ khi
overdue_days >= threshold_days(Ref DEC-025). KHÔNG dedupe cross-day — gửi mỗi ngày nếu vẫn quá hạn (DEC-026 rule 1). - Customer-level bucket trong payload: dùng FORMULA-006B (max bucket), KHÔNG order-level. Vd: KH có 1 đơn 95d + 1 đơn 5d → noti gắn bucket
very_high_risk. - Partial payment (DEC-026 rule 3): Khách thanh toán 1 phần nhưng còn nợ → vẫn nhận noti hằng ngày. Field
total_debtpayload =SUM(remaining)còn lại. - Settled drop (DEC-026 rule 4): Khách settled hết → drop khỏi list từ ngày sau.
- Bucket escalation (NTF-DEBT-BUCKET-ESCALATE): Gửi 1 lần riêng vào ngày customer chuyển bucket cao hơn — KHÔNG thay thế NTF-DEBT-DAILY-001 cùng ngày, mà gửi BỔ SUNG (user sẽ nhận 2 noti: daily + escalation).
- Thiếu biến bắt buộc → không gửi + ghi log lỗi
- Mobile push: badge count = overdue_count; deeplink → MOB-02
- NTF-FOLLOWUP-REMIND: Chỉ in-app web (DEC-020), gửi khi
remind_at ≤ now()vàstatus = 'pending'. Deeplink → tabCông nợ(web). Không push mobile. - Mỗi noti phải có: template_id, recipient_id, created_at, deeplink_route. Timezone: Asia/Ho_Chi_Minh.
B5. Permission Matrix (Đầy đủ — Tất cả màn hình)
Rule Manager (DEC-013): Manager chỉ xem data trong các branch mình đang quản lý. Manager quản lý 3 branch → dropdown Chi nhánh chỉ hiển thị 3 branch đó.
SCR-01 — Dashboard
| Quyền | Sale / CSKH / Telesale | Manager | Admin |
|---|---|---|---|
| Xem dashboard | ✅ Chỉ data của mình | ✅ Các branch đang quản lý | ✅ Toàn bộ |
| Filter theo nhân sự khác | ❌ | ✅ Trong branch quản lý | ✅ |
| Filter theo chi nhánh | ❌ | ✅ Chỉ branch đang quản lý | ✅ Tất cả |
| Xem tên khách | ✅ | ✅ | ⚠️ Masked |
| Xuất XLSX | ✅ Data của mình | ✅ Data branch quản lý | ✅ |
SCR-01-TAB-ANALYTICS — Thống kê
| Quyền | Sale / CSKH / Telesale | Manager | Admin |
|---|---|---|---|
| Xem tab “Thống kê” | ❌ Ẩn tab | ✅ Trong branch đang quản lý | ✅ Toàn bộ |
| Dùng segment so sánh Chi nhánh/NV | ❌ | ✅ Trong scope branch được phân quyền | ✅ |
| Dùng sort chip Đã thu/Còn nợ | ❌ | ✅ | ✅ |
| Xem leaderboard + mở drawer nhân sự | ❌ | ✅ | ✅ |
| Xuất XLSX tab Thống kê | ❌ | ✅ Dữ liệu branch quản lý | ✅ |
SCR-02 — Drill-down Tư Vấn
| Quyền | Sale / CSKH / Telesale | Manager | Admin |
|---|---|---|---|
| Xem danh sách | ✅ Của mình | ✅ Team trong branch quản lý | ✅ |
| Xem % hoa hồng người khác | ❌ | ✅ | ✅ |
| Xuất XLSX | ✅ Của mình | ✅ Team | ✅ |
SCR-03 — Nợ Quá Hạn
| Quyền | Sale / CSKH / Telesale | Manager | Admin |
|---|---|---|---|
| Dùng scope chip vai trò (Sale/Telesale/CSKH) | ✅ | ✅ | ✅ Vận hành |
| Xem nợ nhân viên khác | ❌ | ✅ Trong branch quản lý | ✅ |
| Gọi điện | ✅ | ✅ | ❌ |
| Tạo lịch nhắc | ✅ | ✅ | ❌ |
| Đánh dấu Đã LH | ✅ | ✅ | ❌ |
| Thao tác hàng loạt | ✅ Của mình | ✅ | ✅ Vận hành |
| Mở drawer lịch nhắc | ✅ Của mình | ✅ Trong branch quản lý | ✅ |
| Đánh dấu lịch nhắc Đã xong/Hủy | ✅ Lịch của mình | ✅ Lịch trong branch | ❌ |
| Xuất XLSX | ✅ Của mình | ✅ Branch quản lý | ✅ |
SCR-04 — Cấu Hình Cảnh Báo (Ref: DEC-004 — branch_override ?? global)
| Quyền | Sale / CSKH / Telesale | Manager | Admin |
|---|---|---|---|
| Xem cấu hình | ❌ | ✅ Xem | ✅ |
| Sửa global config | ❌ | ❌ | ✅ |
| Sửa branch override | ❌ | ✅ Branch mình quản lý | ✅ Tất cả |
| Xem lịch sử thay đổi | ❌ | ✅ Branch mình | ✅ |
SCR-05…08 — Handover Wizard
| Quyền | Sale / CSKH / Telesale | Manager | HR Ops | Admin |
|---|---|---|---|---|
| Tạo handover | ❌ | ✅ Nhân sự trong branch quản lý | ✅ Tất cả | ✅ |
| Chọn nguồn/đích ngoài branch | ❌ | ❌ | ✅ | ✅ |
| Xem preview | ❌ | ✅ | ✅ | ✅ |
| Xác nhận handover | ❌ | ✅ | ✅ | ✅ |
| Rollback trong 24h | ❌ | ✅ Tạo bởi mình | ✅ | ✅ |
| Xem audit log | ❌ | ✅ Branch quản lý | ✅ Tất cả | ✅ |
| Xuất XLSX audit (SCR-08) | ❌ | ❌ | ✅ | ✅ |
Mobile — Diva Partner App
| Quyền | Sale / CSKH / Telesale | Manager |
|---|---|---|
| Xem KPI cá nhân — MOB-01 | ✅ Của mình | ✅ Của mình |
| Xem KPI team trên mobile | ❌ | ❌ (dùng web) |
| Xem danh sách nợ — MOB-02 | ✅ Của mình | ✅ Của mình |
| Gọi điện (MOB-02, MOB-03) | ✅ | ✅ |
| Đánh dấu Đã LH (MOB-02) | ✅ | ✅ |
| Xem chi tiết khách nợ — MOB-03 | ✅ Của mình | ✅ Của mình |
| Xem lịch nhắc (MOB-03 section) | ✅ Của mình | ✅ Của mình |
| Đánh dấu lịch nhắc Đã xong/Hủy (MOB-03) | ✅ Lịch của mình | ✅ Lịch của mình |
| Tạo lịch nhắc — MOB-04 | ✅ | ✅ |
| Xem thông báo — MOB-05 | ✅ | ✅ |
| Nhận push notification | ✅ | ✅ |
B6. State Matrix
| Screen | Loading | Empty | Error | Không quyền | Partial |
|---|---|---|---|---|---|
| SCR-01 | Skeleton KPI + skeleton table | “Chưa có dữ liệu tư vấn” + [Đặt lại bộ lọc] | Banner đỏ + [Thử lại] | Chặn toàn bộ | KPI OK, bảng lỗi + retry tại chỗ |
| SCR-01-TAB-ANALYTICS | Skeleton KPI + chart + leaderboard | “Không có dữ liệu thống kê trong kỳ này” | Banner + [Thử lại] | Ẩn tab (Staff) | KPI OK, chart/bảng lỗi cục bộ |
| SCR-01-ANALYTICS-DRAWER | Skeleton drawer 720px | — (mở từ dòng đã có dữ liệu) | Banner + [Thử lại] | Chặn drawer | KPI OK, aging/top5 lỗi cục bộ |
| SCR-01 Action Card | Ẩn (chờ data) | Ẩn (không có nợ ≥30d) | Ẩn (fallback — không block trang) | Ẩn | N/A |
| SCR-02 | Skeleton table | “Không có dữ liệu tư vấn theo bộ lọc” | Banner + [Thử lại] | Chặn toàn bộ | Bảng OK, export lỗi + retry |
| SCR-03 Debt list | Skeleton rows | “Tuyệt vời! 🎉 Không có khách nào nợ quá hạn” | Retry tại chỗ | Chặn toàn bộ | Action disabled |
| SCR-03-DRAWER | Skeleton rows | “Chưa có lịch nhắc nào” | Banner + retry | Chặn drawer | N/A — single query |
| SCR-04 | Skeleton form | “Chưa có cấu hình override cho chi nhánh nào” (Phần 2) | Banner + retry | Chặn toàn bộ | N/A — single form |
| SCR-05…07 | Skeleton step | — (wizard luôn có data từ bước trước) | Giữ data đã nhập + banner lỗi | Chặn flow | Preview lỗi → retry |
| SCR-05…07 (Quick Handover) | Skeleton step (2 bước) | — (wizard luôn có data từ bước trước) | Giữ data đã nhập + banner lỗi | Chặn flow | N/A — bước 2 gộp preview+confirm |
| SCR-08 | Skeleton timeline | — (luôn có handover vừa tạo) | Banner “Không thể tải kết quả bàn giao” + [Thử lại] | Chặn toàn bộ | Timeline OK, rollback lỗi + retry |
| MOB-01 | Skeleton cards | “Chưa có dữ liệu tư vấn” | Banner + [Thử lại] | N/A — mobile chỉ xem data mình | KPI OK, chart lỗi |
| MOB-02 | Skeleton list | “Không có nợ quá hạn 🎉” | Banner + retry | N/A — mobile chỉ xem data mình | N/A — single list |
| MOB-03 | Skeleton chi tiết + skeleton lịch nhắc | — (luôn có data từ MOB-02) | Retry tại chỗ từng section | N/A — mobile chỉ xem data mình | Chi tiết OK, lịch nhắc lỗi |
| MOB-03 (section lịch nhắc) | Skeleton rows | “Chưa có lịch nhắc” | Retry tại chỗ | N/A | N/A — sub-section |
| MOB-04 (bottom sheet) | Spinner trên nút [Tạo lịch] | — (form luôn sẵn sàng) | Toast “Tạo lịch nhắc thất bại” + giữ form data | N/A — ai cũng tạo được | N/A — single form |
| MOB-05 | Skeleton list | “Chưa có thông báo nào” | Banner + retry | N/A — mobile chỉ xem noti mình | N/A — single list |
| SCR-01 KPI Debt Grid | Ẩn (chờ KPI load) | Ẩn (không có Block B data) | Ẩn | Ẩn | N/A |
| SCR-03 Daily Focus | Skeleton 5 rows | Ẩn (tất cả nợ < 30 ngày) | Ẩn (fallback — không block trang) | Ẩn | N/A |
| SCR-01 Onboarding Tour | Không hiện khi loading | N/A — hiện theo localStorage flag | N/A — tour tự ẩn nếu target element lỗi | N/A | N/A |
B7. Copy Text & Content
Error Messages
| Lỗi | i18n Key | Message | Action |
|---|---|---|---|
| Load thất bại | debt.error.load_failed | “Không thể tải dữ liệu. Vui lòng thử lại.” | [Thử lại] |
| Export XLSX thất bại | debt.error.export_failed | “Xuất file thất bại. Dữ liệu quá lớn hoặc có lỗi hệ thống.” | [Thử lại] [Liên hệ hỗ trợ] |
| Handover thất bại | debt.error.handover_failed | “Bàn giao thất bại. Vui lòng thử lại hoặc liên hệ Admin.” | [Thử lại] |
| Rollback hết hạn | debt.error.rollback_expired | “Thời gian hoàn tác đã hết. Liên hệ Admin để xử lý thủ công.” | — |
| Không có quyền | debt.error.permission_denied | “Bạn không có quyền thực hiện thao tác này.” | [← Quay lại] |
| Nguồn = Đích | debt.error.source_eq_target | “Người giao và người nhận không được là cùng 1 nhân viên.” | — |
| Nhân viên không active | debt.error.staff_inactive | “Nhân viên này không còn hoạt động. Vui lòng chọn người khác.” | — |
| Đang có handover pending | debt.error.handover_pending | “Đang có bàn giao chưa hoàn tất cho nhân viên này.” | [Xem bàn giao →] |
| Filter không có khách | debt.error.filter_empty | “Bộ lọc không tìm thấy khách nào. Thử điều kiện khác.” | — |
| Vượt giới hạn handover | debt.error.handover_limit | “Tối đa 200 khách/lần bàn giao. Vui lòng chia nhỏ phạm vi.” | — |
| Scheduler lỗi (Ops alert) | debt.ops.scheduler_failed | “debt_scheduler_run_failed — [timestamp] — [error detail]” | Xem runbook |
Empty State Messages
| Screen | i18n Key | Message |
|---|---|---|
| SCR-01 empty | debt.empty.dashboard | “Chưa có dữ liệu tư vấn” |
| SCR-02 empty | debt.empty.no_consultation | “Không có dữ liệu tư vấn theo bộ lọc” |
| SCR-03 debt list empty | debt.empty.no_overdue | “Tuyệt vời! Không có khách nào nợ quá hạn” |
| MOB-01 empty | debt.mobile.empty.dashboard | “Chưa có dữ liệu tư vấn” |
| MOB-02 empty | debt.mobile.empty.no_overdue | “Không có nợ quá hạn” |
| Filter tương lai | debt.empty.future_date | “Chưa có dữ liệu cho kỳ này” |
| Drawer lịch nhắc empty | debt.empty.no_followup | “Chưa có lịch nhắc nào” |
| MOB-03 lịch nhắc empty | debt.mobile.empty.no_followup | “Chưa có lịch nhắc” |
| MOB-05 empty | debt.mobile.empty.no_notification | “Chưa có thông báo nào” |
| SCR-04 branch empty | debt.empty.no_branch_override | “Chưa có cấu hình override cho chi nhánh nào” |
Badge & Status Labels
| Context | i18n Key | Text |
|---|---|---|
| Followup quá hạn badge | debt.followup.badge.overdue | “Quá hạn” |
| Followup đã xong | debt.followup.badge.done | “Đã xong” |
| Followup đã hủy | debt.followup.badge.cancelled | “Đã hủy” |
| Followup chờ | debt.followup.badge.pending | “Chờ nhắc” |
Success / Toast Messages
| Action | i18n Key | Message |
|---|---|---|
| Đánh dấu Đã LH | debt.success.mark_contacted | “Đã đánh dấu liên hệ khách hàng” |
| Hủy đánh dấu LH | debt.success.unmark_contacted | “Đã hủy đánh dấu liên hệ” |
| Lưu cấu hình (SCR-04) | debt.success.config_saved | “Đã lưu. Hiệu lực từ lần chạy tiếp theo.” |
| Handover thành công | debt.success.handover_done | “Bàn giao thành công khách” |
| Rollback thành công | debt.success.rollback_done | “Hoàn tác thành công. khách đã trả về ” |
| Tạo lịch nhắc | debt.success.followup_created | “Đã tạo lịch nhắc” |
| Đánh dấu lịch nhắc Đã xong | debt.success.followup_done | “Đã hoàn thành lịch nhắc” |
| Hủy lịch nhắc | debt.success.followup_cancelled | “Đã hủy lịch nhắc” |
| Export thành công | debt.success.export_done | “Xuất file thành công” |
Notification Templates
| Template | i18n Key | Text |
|---|---|---|
| NTF-DEBT-DAILY-001 | debt.noti.debt_daily | “Bạn có khách nợ quá ngày. Tổng nợ: đ” |
| NTF-HANDOVER-TARGET | debt.noti.handover_target | “ khách từ đã chuyển cho bạn (hiệu lực )” |
| NTF-HANDOVER-CREATOR | debt.noti.handover_creator | “Bàn giao — khách từ sang đã hoàn tất” |
| NTF-FOLLOWUP-REMIND | debt.noti.followup_remind | “Nhắc: — Khách , nợ đ ( ngày)” |
B8. Analytics Event Mapping
| Event | Trigger | Screen | KPI Link |
|---|---|---|---|
| kpi_dashboard_viewed | Mở SCR-01 / MOB-01 | SCR-01, MOB-01 | Usage |
| dashboard_filter_applied | Bấm filter | SCR-01…03 | Usage funnel |
| report_export_clicked | Bấm Xuất XLSX | SCR-01, SCR-02, SCR-03, SCR-08 | FR-012 |
| debt_alert_opened | Click noti nợ (web + mobile) | SCR-03, MOB-02 | KPI-1 |
| followup_task_created | Tạo lịch chăm sóc | SCR-03 (popup), MOB-03→MOB-04 | Debt resolution |
| mark_contacted_clicked | Đã liên hệ | SCR-03, MOB-02 | Ops efficiency |
| call_initiated | Tap nút Gọi | SCR-03, SCR-03-DRAWER, MOB-02, MOB-03 | Engagement |
| handover_completed | Xác nhận bước 3 | SCR-08 | KPI vận hành |
| handover_rollback_requested | Bấm rollback | SCR-08 | NFR-008 |
| push_notification_tapped | Tap push trên mobile | MOB-02, MOB-01 | Push engagement |
| followup_task_completed | Đánh dấu Đã xong | SCR-03-DRAWER, MOB-03 | Debt resolution |
| followup_task_cancelled | Hủy lịch nhắc | SCR-03-DRAWER, MOB-03 | — |
| followup_drawer_opened | Mở drawer lịch nhắc | SCR-03 | Usage |
| followup_remind_sent | Noti nhắc gửi thành công | — (server) | Scheduler |
| action_card_cta_clicked | Bấm “Xử lý ngay” trên Action Card | SCR-01, MOB-01 | Engagement |
| action_card_dismissed | Bấm “Ẩn hôm nay” / swipe dismiss Action Card | SCR-01, MOB-01 | UX feedback |
| overflow_menu_opened | Bấm [⋯] trên dòng bảng nợ | SCR-03 | Usage pattern |
| dashboard_tab_switched | Chuyển tab “Hiệu suất tư vấn” ↔ “Công nợ” | SCR-01 | Navigation pattern |
| kpi_card_clicked | Click KPI card để navigate (→ SCR-02 / tab Công nợ) | SCR-01 | Drill-down funnel |
| aging_bucket_clicked | Click segment trên Aging Chart để apply filter Nhóm nợ cho debt action area | SCR-01 | Feature adoption |
| debt_scope_chip_changed | Thay đổi scope chip vai trò (Sale/Telesale/CSKH) | SCR-01 tab Công nợ, MOB-02 | Filter behavior |
| debt_card_action_opened | Bấm CTA trực tiếp hoặc mở menu [⋯] trên card nợ | MOB-02 | Mobile UX adoption |
| mob03_section_toggled | Expand/collapse section (Dịch vụ nợ / Lịch sử LH / Lịch nhắc) | MOB-03 | Mobile UX adoption |
| debt_kpi_fullset_viewed | Mở tab Công nợ và hiển thị full bộ KPI nợ | SCR-01 tab Công nợ, MOB-01 | Feature adoption |
| daily_focus_call_clicked | Bấm [📞] trong Daily Focus | SCR-03 | Engagement |
| daily_focus_hidden | Bấm [Ẩn ▴] Daily Focus | SCR-03 | UX feedback |
| daily_focus_view_all | Bấm “Xem toàn bộ danh sách” | SCR-03 | Navigation |
| quick_handover_used | Hoàn thành handover qua Quick mode (2 steps) | SCR-05→06 | Feature adoption |
| onboarding_started | Tour bắt đầu (step 1 hiện) | SCR-01 | Onboarding |
| onboarding_completed | Tour hoàn thành (step 3 done) | SCR-01 | Onboarding |
| onboarding_skipped | Bấm “Bỏ qua” trong tour | SCR-01 | Onboarding |
| onboarding_replayed | Bấm “Xem lại hướng dẫn” từ menu ⓘ | SCR-01 | Re-engagement |
B9. Bảng Tooltip (Tooltip Dictionary)
Tooltip hiển thị khi hover (web) hoặc long-press (mobile) lên các phần tử tương tác. Mục đích: hướng dẫn user mới và giảm chọn nhầm chức năng.
Web — Dashboard & Báo cáo (SCR-01)
| Phần tử | Tooltip text | i18n key |
|---|---|---|
| Card “Lượt tư vấn” | “Số lượt tư vấn theo (Khách + Chi nhánh + Ngày) có phát sinh tư vấn (consultant)” | debt.tooltip.visited_count |
| Card “Khách đã tư vấn” | “Số khách hàng khác nhau đã được tư vấn (không đếm trùng)” | debt.tooltip.unique_customers |
| Card “Khách mua trong kỳ” | “Khách đã mua dịch vụ hoặc sản phẩm và có thực thu > 0 trong kỳ” | debt.tooltip.closed_count |
| Card “Tỷ lệ chuyển đổi” | “= Khách mua trong kỳ ÷ Khách đã tư vấn × 100%” | debt.tooltip.conversion_rate |
| Card "Doanh thu ròng" (Ref DEC-032) | "Doanh thu ròng (ghi nhận kế toán) = Tổng giá đơn − Chiết khấu − Hoàn trả. ⚠️ Đây là doanh thu đã ghi nhận, KHÔNG phải tiền đã thu về quỹ. Phần khách còn nợ chưa được trừ. Để xem tiền thực thu → KPI 'Đã thu trong kỳ'." | debt.tooltip.net_revenue |
| Card “Khách đang nợ” | “Khách còn dư nợ > 0đ tính đến thời điểm hiện tại” | debt.tooltip.debt_customer_count |
| Card “Khách nợ quy đổi” | “Số khách nợ được quy đổi theo % hưởng của bạn. VD: 2 người 50/50 thì mỗi người tính 0.5 khách” | debt.tooltip.debt_customer_equivalent |
| Card “Tổng tiền nợ” | “Tổng công nợ còn lại tại cuối kỳ theo bộ lọc hiện tại” | debt.tooltip.total_debt |
| Card “Tiền nợ quy đổi” | “Phần tiền nợ được tính cho bạn theo tỷ lệ hoa hồng của bạn” | debt.tooltip.allocated_debt |
| Card “Tỷ lệ nợ/doanh thu” | “= Tổng tiền nợ ÷ Doanh thu ròng × 100%” | debt.tooltip.debt_ratio |
| Biểu đồ phân loại nhóm nợ | “Phân bố khách nợ theo số ngày quá hạn. Bấm vào từng nhóm để xem chi tiết” | debt.tooltip.aging_chart |
| Nút [Xuất XLSX] (SCR-01) | “Tải báo cáo về file Excel theo bộ lọc hiện tại” | debt.tooltip.export_xlsx |
| Filter “Chi nhánh” | “Lọc theo chi nhánh bạn đang quản lý” | debt.tooltip.filter_branch |
| Filter “Nhân viên” | “Lọc theo nhân viên phụ trách (owner chính)” | debt.tooltip.filter_staff |
| Nút [Xem chi tiết] drill-down | “Mở danh sách tư vấn chi tiết theo bộ lọc” | debt.tooltip.drill_down |
Web — Chi tiết danh sách tư vấn (SCR-02)
| Phần tử | Tooltip text | i18n key |
|---|---|---|
| Toggle "Theo lượt / Theo khách" | "Chuyển giữa đếm theo lượt tư vấn và đếm theo khách đã tư vấn" | debt.tooltip.scr02_toggle_view |
| Cột "Cuộc gọi" (Ref DEC-028) | "Cuộc gọi gần nhất từ app Diva Staff cho khách này" | debt.tooltip.scr02_last_call |
| Cột "% tham gia" (Ref DEC-029 + FORMULA-008) | "% phụ trách nợ của bạn, weighted theo tổng giá trị các đơn của khách. Hover để xem chi tiết per-đơn" | debt.tooltip.scr02_participation_pct |
| Tooltip drill-down "% tham gia" (Ref FORMULA-008) | Format: "% tham gia: {weighted}% (weighted theo tổng giá trị đơn) — Chi tiết theo đơn: • Đơn #{code} ({money}): {pct}% — Tổng: ..." | debt.tooltip.scr02_participation_drilldown |
| Filter "Trạng thái" | "Lọc theo: Tất cả / Đã mua trong kỳ / Chưa mua trong kỳ" | debt.tooltip.scr02_filter_status |
| Nút [Xuất XLSX] (SCR-02) | "Tải danh sách tư vấn về file Excel" | debt.tooltip.scr02_export |
| Nút [← Quay lại Dashboard] | "Quay về trang báo cáo hiệu suất chính" | debt.tooltip.scr02_back |
Web — Danh sách nợ quá hạn (SCR-03)
| Phần tử | Tooltip text | i18n key |
|---|---|---|
| Scope chip “Sale” | “Bật/tắt phạm vi dữ liệu theo vai trò Sale trong tab Công nợ” | debt.tooltip.scope_sale |
| Scope chip “Telesale” | “Bật/tắt phạm vi dữ liệu theo vai trò Telesale trong tab Công nợ” | debt.tooltip.scope_telesale |
| Scope chip “CSKH” | “Bật/tắt phạm vi dữ liệu theo vai trò CSKH trong tab Công nợ” | debt.tooltip.scope_cskh |
Badge aging 0-29 ngày | “Nợ mới — chưa quá hạn nghiêm trọng” | debt.tooltip.aging_0_29 |
Badge aging 30-59 ngày | “Cảnh báo — cần liên hệ sớm” | debt.tooltip.aging_30_59 |
Badge aging 60-89 ngày | “Nợ nguy hiểm — ưu tiên xử lý” | debt.tooltip.aging_60_89 |
Badge aging ≥90 ngày | “Nợ rất cũ — cần báo Manager xử lý” | debt.tooltip.aging_90_plus |
| Nút [📞 Gọi] | “Gọi điện cho khách. Sau khi gọi sẽ hỏi đánh dấu liên hệ” | debt.tooltip.btn_call |
| Nút [📅 Lịch] | “Tạo lịch nhắc chăm sóc cho khách này” | debt.tooltip.btn_schedule |
| Nút [✓ Đã LH] | “Đánh dấu đã liên hệ khách hàng hôm nay” | debt.tooltip.btn_contacted |
| Nút [→ CRM] | “Mở hồ sơ khách hàng trên CRM (tab mới)” | debt.tooltip.btn_crm |
| Nút [📅 Lịch nhắc (N)] trên toolbar | “Mở danh sách lịch nhắc chăm sóc của tôi. Số đỏ = lịch quá hạn” | debt.tooltip.btn_open_drawer |
| Checkbox chọn nhiều | “Chọn nhiều khách để thao tác hàng loạt (đánh dấu liên hệ)” | debt.tooltip.bulk_select |
| Nút [Đã đánh dấu LH] (bulk) | “Đánh dấu đã liên hệ cho tất cả khách đang chọn” | debt.tooltip.bulk_contacted |
| Filter “Nhóm nợ” | “Lọc theo nhóm ngày nợ quá hạn” | debt.tooltip.filter_bucket |
| Filter “Owner” | “Lọc theo nhân viên phụ trách nợ” | debt.tooltip.filter_owner |
| Nút [Lọc nhóm ≥90 →] | “Nhanh chóng lọc xem chỉ khách nợ ≥90 ngày” | debt.tooltip.filter_shortcut_90 |
| Nút [Xuất XLSX] (SCR-03) | “Tải danh sách nợ quá hạn về file Excel” | debt.tooltip.scr03_export |
Web — Drawer lịch nhắc (SCR-03-DRAWER)
| Phần tử | Tooltip text | i18n key |
|---|---|---|
| Nút [✅ Xong] | “Đánh dấu lịch nhắc đã hoàn thành” | debt.tooltip.followup_done |
| Nút [❌ Hủy] | “Hủy lịch nhắc này (cần xác nhận)” | debt.tooltip.followup_cancel |
| Nút [📞 Gọi] (trong drawer) | “Gọi điện cho khách từ lịch nhắc” | debt.tooltip.drawer_call |
| Badge 🔴 “Quá hạn” | “Lịch nhắc đã quá thời gian đặt — cần xử lý ngay” | debt.tooltip.followup_overdue |
| Filter “Trạng thái” | “Lọc theo: Tất cả / Chờ nhắc / Đã xong / Đã hủy / Quá hạn” | debt.tooltip.followup_filter_status |
| Nút [×] đóng drawer | “Đóng panel lịch nhắc” | debt.tooltip.drawer_close |
Web — Cài đặt cảnh báo (SCR-04)
| Phần tử | Tooltip text | i18n key |
|---|---|---|
| Field “Ngưỡng ngày quá hạn” | “Số ngày nợ quá hạn để bắt đầu gửi cảnh báo (mặc định: 30 ngày)” | debt.tooltip.threshold_days |
| Field “Giờ gửi cảnh báo” | “Thời điểm gửi noti cảnh báo nợ hàng ngày (mặc định: 07:00)” | debt.tooltip.alert_time |
| Toggle “Bật/Tắt cảnh báo” | “Bật hoặc tắt gửi cảnh báo nợ quá hạn cho chi nhánh này” | debt.tooltip.alert_toggle |
| Nút [Lưu cấu hình] | “Lưu thay đổi cài đặt cảnh báo” | debt.tooltip.save_config |
Web — Bàn giao khách (SCR-05 → SCR-08)
| Phần tử | Tooltip text | i18n key |
|---|---|---|
| Dropdown “Nhân viên nghỉ” (SCR-05) | “Chọn nhân viên sẽ ngừng phụ trách — khách sẽ chuyển đi” | debt.tooltip.select_source |
| Dropdown “Người tiếp nhận” (SCR-06) | “Chọn nhân viên sẽ nhận phụ trách khách hàng” | debt.tooltip.select_target |
| Nút [Tiếp theo] (SCR-05, 06) | “Chuyển sang bước tiếp theo trong wizard bàn giao” | debt.tooltip.wizard_next |
| Nút [← Quay lại] (SCR-06, 07) | “Quay về bước trước (dữ liệu đã nhập được giữ nguyên)” | debt.tooltip.wizard_back |
| Checkbox “Tôi xác nhận…” (SCR-07) | “Bắt buộc tick để kích hoạt nút Xác nhận bàn giao” | debt.tooltip.confirm_checkbox |
| Nút [Xác nhận bàn giao] (SCR-07) | “Thực hiện chuyển toàn bộ khách sang người nhận. Có thể hoàn tác trong 24h” | debt.tooltip.confirm_handover |
| Nút [↩ Hoàn tác] (SCR-08) | “Trả toàn bộ khách về nhân viên cũ. Chỉ khả dụng trong 24h sau bàn giao” | debt.tooltip.rollback_handover |
| Nút [← Về Dashboard] (SCR-08) | “Quay về trang báo cáo hiệu suất chính” | debt.tooltip.back_dashboard |
| Nút [Xem danh sách khách] (SCR-08) | “Mở danh sách nợ quá hạn, filter sẵn theo đợt bàn giao này” | debt.tooltip.view_transferred |
| Nút [📋 Sao chép] (SCR-08) | “Sao chép mã bàn giao vào clipboard” | debt.tooltip.copy_handover_code |
| Nút [Xuất XLSX] (SCR-08) | “Tải audit bàn giao về file Excel” | debt.tooltip.scr08_export |
| Badge “Còn X giờ” (SCR-08) | “Thời gian còn lại để hoàn tác bàn giao (tối đa 24h)” | debt.tooltip.rollback_countdown |
| Badge “Đã hết hạn” (SCR-08) | “Không thể hoàn tác — đã quá 24h. Liên hệ Admin” | debt.tooltip.rollback_expired |
Mobile — Diva Partner App (MOB-01 → MOB-05)
| Phần tử | Tooltip (long-press) | i18n key |
|---|---|---|
| Card “Lượt TV” (MOB-01) | “Số lượt tư vấn theo (Khách + Chi nhánh + Ngày) có phát sinh tư vấn” | debt.mobile.tooltip.visited |
| Card “Khách đang nợ” (MOB-01) | “Tap để xem danh sách khách đang nợ” | debt.mobile.tooltip.debt_card |
| Chart aging (MOB-01) | “Tap vào nhóm nợ để xem danh sách chi tiết” | debt.mobile.tooltip.aging_chart |
| Nút [📞 Gọi] (MOB-02, 03) | “Gọi điện cho khách qua ứng dụng điện thoại” | debt.mobile.tooltip.call |
| Nút [📅 Lịch] (MOB-02, 03) | “Mở form tạo lịch nhắc chăm sóc” | debt.mobile.tooltip.schedule |
| Nút [✓ Đã LH] (MOB-02) | “Đánh dấu đã liên hệ” | debt.mobile.tooltip.contacted |
| Nút [→ CRM] (MOB-03) | “Mở hồ sơ khách trên trình duyệt” | debt.mobile.tooltip.crm |
| Nút [✅ Xong] (MOB-03) | “Hoàn thành lịch nhắc này” | debt.mobile.tooltip.followup_done |
| Nút [❌ Hủy] (MOB-03) | “Hủy lịch nhắc (cần xác nhận)” | debt.mobile.tooltip.followup_cancel |
| Nút [+ Tạo] (MOB-03) | “Tạo lịch nhắc mới cho khách này” | debt.mobile.tooltip.followup_create |
| Field “Ngày nhắc” (MOB-04) | “Chọn ngày và giờ muốn nhận nhắc” | debt.mobile.tooltip.remind_date |
| Field “Nội dung” (MOB-04) | “Nội dung sẽ hiện trong noti nhắc nhở” | debt.mobile.tooltip.remind_content |
| Nút [Tạo lịch ✓] (MOB-04) | “Lưu lịch nhắc và đóng form” | debt.mobile.tooltip.submit_followup |
| Nút [Hủy] (MOB-04) | “Đóng form không lưu” | debt.mobile.tooltip.cancel_followup |
| Badge 🔔 (header) | “Số thông báo chưa đọc” | debt.mobile.tooltip.noti_badge |
| Bottom tab [🏠] | “Trang chủ — hiệu suất cá nhân” | debt.mobile.tooltip.tab_home |
| Bottom tab [📋] | “Danh sách khách nợ quá hạn” | debt.mobile.tooltip.tab_debt |
| Bottom tab [🔔] | “Trung tâm thông báo” | debt.mobile.tooltip.tab_noti |
B-Desktop) UX Chi Tiết — Web Desktop
Platform: Web Admin / CRM | Breakpoint: 1280px+
SCR-01 — Báo cáo hiệu suất tư vấn & công nợ
Layout (1440px)
┌──────────────────────────────────────────────────────────────────┐
│ HEADER │
│ "Báo cáo Hiệu suất Tư vấn & Công nợ" [Xuất XLSX ▼] │
├──────────────────────────────────────────────────────────────────┤
│ TAB: [📊 Hiệu suất tư vấn] [💰 Công nợ] │
│ FILTER BAR — sticky, height 56px, background #FAFAFA │
│ [Hôm nay] [Tuần này] [Tháng này] [Tùy chỉnh] │
│ [📅 Từ ngày ──── Đến ngày] (disabled trừ khi chọn Tùy chỉnh) │
│ [Chi nhánh: Tất cả ▼] [Vai trò: Tất cả ▼] [Nhân sự: Tất cả ▼]│
├──────────────────────────────────────────────────────────────────┤
│ ACTION CARD — Việc cần làm hôm nay (ẩn khi không có nợ ≥30d) │
│ 🔴 5 khách nợ ≥90 ngày · 🟠 3 khách nợ 60-89 ngày │
│ 📅 2 lịch nhắc quá hạn │
│ [Xử lý ngay →] [Ẩn hôm nay] │
├──────────────────────────────────────────────────────────────────┤
│ KHI TAB = "📊 Hiệu suất tư vấn" │
│ BLOCK A — KPI tiếp khách & chuyển đổi │
│ BLOCK D — Bảng tư vấn: Khách | Dịch vụ | % Của tôi | Ngày TV | Trạng thái │
├──────────────────────────────────────────────────────────────────┤
│ KHI TAB = "💰 Công nợ" │
│ SCOPE CHIP: [☑ Sale] [☑ Telesale] [☑ CSKH] │
│ BLOCK B — KPI nợ (full bộ card, không toggle) │
│ BLOCK C — Phân loại nhóm nợ │
│ BLOCK D — Bảng nợ: Khách | Liên hệ gần nhất | Phụ trách chính│
│ | % tham gia | Tiền nợ | Nhóm nợ | Quá hạn │
│ | Hành động │
└──────────────────────────────────────────────────────────────────┘Spec Dashboard Tabs
| Tab | Nội dung hiển thị | Default | Lý do tách |
|---|---|---|---|
| 📊 Hiệu suất tư vấn | Block A (KPI tiếp khách & chuyển đổi) + Block D (bảng danh sách tư vấn) | ✅ Default khi mở | Sale xem hằng ngày — focus tư vấn + chuyển đổi |
| 💰 Công nợ | Scope chip vai trò + Block B (KPI nợ) + Block C (Aging chart) + Block D (bảng nợ quá hạn) | — | Manager xem khi cần phân tích nợ |
Behavior:
- Tab giữ nguyên filter bar (không reset khi chuyển tab)
- URL hash:
/report/sales-debt-performance#consultationvs#debt - Action Card hiện trên cả 2 tab (vì liên quan urgency nợ)
- [Xuất XLSX] export theo tab đang active
- Nếu click KPI card nợ trong tab Tư vấn → tự chuyển sang tab
Công nợvà giữ nguyên top filter/scope chip đang chọn
Spec Scope Chip Vai trò trong tab “Công nợ”
| Thuộc tính | Mô tả |
|---|---|
| Vị trí | Nằm trên cùng hàng với top filter, ngay trước nút Xuất XLSX |
| Kiểu hiển thị | Checkbox chip: Sale / Telesale / CSKH |
| Vai trò | Filter theo nhóm vai trò tham gia xử lý nợ trong tab Công nợ |
| Default | Tất cả chip bật |
| Scope | Ảnh hưởng Block B, Block C, Priority Card strip, Block D, export |
| Persist | Giữ trong session khi user đổi date/filter khác |
Spec Filter Bar
| Component | Type | Default | Cascade behavior |
|---|---|---|---|
| Preset thời gian | Button group | Tháng này | Click → cập nhật date range |
| Date range picker | Range input | Đầu tháng – Hôm nay | Chỉ enabled khi chọn “Tùy chỉnh” |
| Chi nhánh | Single select | Tất cả (theo quyền) | Đổi → reset Nhân sự về “Tất cả” |
| Vai trò | Multi-select chip | Tất cả | Đổi → filter danh sách Nhân sự theo role |
| Nhân sự | Single select | Tất cả | Cascade từ Branch + Role. Hiển thị: [Avatar] Tên — Role |
Quy tắc multi-select Vai trò:
- Multi-select role = OR logic: hiển thị tất cả nhân sự thuộc bất kỳ role đã chọn
- VD: chọn “Sale” + “CSKH” → danh sách Nhân sự hiển thị tất cả Sale và CSKH
- KPI dashboard: aggregate tất cả nhân sự match filter (không tách theo role)
- Nếu chọn 1 nhân sự cụ thể → chỉ hiển thị KPI của riêng người đó
- Nếu không chọn role nào (= “Tất cả”) → hiển thị tất cả nhân sự trong branch scope
Spec Ma trận phạm vi bộ lọc
| Bộ lọc / Điều khiển | Hiển thị ở đâu | Ảnh hưởng đến đâu | Ghi chú rõ nghĩa |
|---|---|---|---|
Preset thời gian + Date range | SCR-01, cả 2 tab | Toàn bộ KPI/card/chart/bảng/export của tab đang active | Là filter toàn cục. Với tab Công nợ, date_to là mốc snapshot nợ |
Chi nhánh (top filter) | SCR-01, cả 2 tab | Toàn bộ dữ liệu của tab đang active | Đổi → reset Nhân sự về Tất cả |
Vai trò | SCR-01, cả 2 tab | Toàn bộ dữ liệu của tab đang active | Chỉ là filter scope cho danh sách nhân sự + aggregate dashboard |
Nhân sự | SCR-01, cả 2 tab | Toàn bộ dữ liệu của tab đang active | Nếu chọn 1 người cụ thể, mọi KPI/bảng chỉ còn dữ liệu của người đó |
Tab chính Hiệu suất tư vấn / Công nợ | SCR-01 | Đổi module hiển thị | Giữ nguyên top filter khi chuyển tab |
Scope chip Sale/Telesale/CSKH | SCR-01 tab Công nợ | Block B, Block C, Priority Card strip, Block D, export debt | Là filter scope theo vai trò tham gia nợ |
Chi nhánh (local debt filter) | SCR-03 | Toàn bộ thành phần của debt view trong tab Công nợ | Staff ẩn. Manager/Admin dùng để slice debt scope trong tab nợ |
Owner (local debt filter) | SCR-03 | Toàn bộ thành phần của debt view trong tab Công nợ | Nếu top filter Nhân sự đã chọn 1 người cụ thể → local Owner tự set cùng giá trị và chuyển sang trạng thái readonly. Khi top filter quay về Tất cả → local Owner mở lại để user chọn |
Nhóm nợ | SCR-03 | Summary Banner, Daily Focus, Block D, export debt | Đây là working-set filter của debt action area. Block C vẫn giữ full distribution theo scope hiện tại và chỉ highlight nhóm đang chọn, không recalc lại segment |
Tìm khách | SCR-03 | Chỉ Block D | Là filter truy xuất nhanh, không làm thay đổi KPI/chart/banner |
Spec As-of Date cho dữ liệu nợ
| Quy tắc | Mô tả |
|---|---|
| Mốc snapshot | Toàn bộ dữ liệu nợ trong tab Công nợ được tính tại cuối ngày date_to theo timezone Asia/Ho_Chi_Minh |
date_to | Quyết định outstanding_amount, overdue_days, Nhóm nợ, Khách đang nợ, Tổng tiền nợ, Tiền nợ quy đổi, Tỷ lệ thu nợ, Đã thu trong kỳ, Priority Card strip |
date_from | Không loại các khoản nợ cũ hơn nếu chúng vẫn còn outstanding tại date_to; giữ để đồng bộ report context, so sánh kỳ và metadata export |
| Không có custom range | Nếu user chọn preset như Hôm nay / Tuần này / Tháng này, hệ thống map ra date_to tương ứng ngày cuối của preset đang xem |
| Công thức overdue | overdue_days = max(0, end_of_day(date_to) - debt_due_date) tính theo số ngày lịch |
| Hệ quả | Một khoản nợ phát sinh trước date_from nhưng còn outstanding tại date_to vẫn phải xuất hiện trong tab Công nợ |
Ví dụ: chọn
01/02 → 28/02/2026, tabCông nợphản ánh ảnh chụp nợ tại 23:59:59 ngày 28/02/2026, không phải “ngay lúc user đang xem”.
Spec KPI Cards
Nguyên tắc sub-label: Mỗi KPI card hiển thị sub-label giải thích trực tiếp bên dưới giá trị chính (font 12px, color
#757575). Giúp user hiểu KPI ngay mà không cần hover tooltip. Tooltip ⓘ vẫn giữ cho chi tiết đầy đủ.Nguyên tắc format đơn vị tiền (Ref BUG-PROD-002 LOCKED): Mọi KPI giá trị tiền hiển thị bằng VND đầy đủ + suffix "đ" (vd
125.000.000đ). KHÔNG dùng đơn vị "Triệu" / "Tỷ" / "M" / "B" để tránh nhầm lẫn unit format. Số > 1 tỷ vẫn hiển thị đầy đủ với separator nghìn (1.250.000.000đ).
| Card | Field | Giá trị chính | Sub-label trực tiếp | Tooltip ⓘ | Click | Visibility |
|---|---|---|---|---|---|---|
| Lượt tư vấn | visit_count | 128 lượt ↗ | — | "Đếm theo (Khách + Chi nhánh + Ngày) có source consultant. Nhiều sự kiện tư vấn cùng ngày chỉ tính 1 lượt" | → SCR-02 | ✅ Luôn hiện |
| Khách đã tư vấn | unique_customer_count | 67 khách | — | "Đếm theo customer_id, không trùng lặp" | → SCR-02 | ✅ Luôn hiện |
| Khách mua trong kỳ | closed_customer_count | 45 khách | — | "Khách đã mua VÀ có thực thu > 0 trong kỳ" | → SCR-02 filter=closed | ✅ Luôn hiện |
| Tỷ lệ chuyển đổi | conversion_rate | 67.2% | "45 khách mua / 67 khách" | "= Khách mua trong kỳ ÷ Khách đã tư vấn. N/A khi mẫu số = 0" | — | ✅ Luôn hiện |
| Doanh thu ròng | net_revenue (Ref FORMULA-003 + DEC-032) | 125.000.000đ ↘ | "≠ tiền thực thu" | "Doanh thu ghi nhận kế toán = Tổng giá đơn − Chiết khấu − Hoàn trả. ⚠️ Phần khách còn nợ CHƯA được trừ. Tiền thực thu → KPI 'Đã thu trong kỳ' (Ref DEC-022 + DEC-032)" | — | ✅ Luôn hiện (Sale: × % HH; Manager/BOD: raw branch/system — Ref DEC-024) |
| Khách đang nợ | debt_customer_count_distinct | 67 khách | — | "Khách còn dư nợ > 0đ tại snapshot date_to" | → tab Công nợ | ✅ Luôn hiện |
| Khách nợ quy đổi | debt_customer_equivalent | 18.5% | "Theo % HH của bạn" | "Số khách nợ quy đổi theo tỷ lệ tham gia/hoa hồng" | → tab Công nợ | ✅ Luôn hiện |
| Tiền nợ quy đổi | allocated_debt_amount | 18.500.000đ | — | "Phần tiền nợ quy đổi theo tỷ lệ tham gia/hoa hồng" | → tab Công nợ | ✅ Luôn hiện |
| Tổng tiền nợ | total_debt_amount | 45.200.000đ | "23 khách còn nợ" | "Tổng outstanding tại snapshot date_to" | → tab Công nợ | ✅ Luôn hiện |
| Tỷ lệ thu nợ | debt_collection_rate | 77.5% | — | "Tỷ lệ đã thu trên tổng phải thu trong kỳ" | — | ✅ Luôn hiện |
| TB ngày thu nợ | avg_recovery_days_weighted | 18.3 ngày | "Càng thấp càng tốt" | "Số ngày thu nợ trung bình có trọng số theo số tiền" | — | ✅ Luôn hiện |
| Đã thu trong kỳ | collected_amount_in_period | 62.000.000đ | — | "Tổng tiền đã thu trong kỳ lọc" | — | ✅ Luôn hiện |
| Tỷ lệ nợ/doanh thu (headline) | debt_ratio | 36.2% | (+8.00%) | “= Tổng nợ ÷ Doanh thu ròng” | — | ✅ Luôn hiện |
Sub-label rules:
- Font: 12px, color
#757575, italic - Hiển thị trực tiếp dưới giá trị chính, không cần hover
- Khi giá trị = N/A → sub-label đổi thành
"Chưa đủ dữ liệu" - Sub-label dùng dữ liệu thực (e.g.
"45 khách mua / 67 khách"thay đổi theo filter)
Trend indicator: ↗ (xanh) / ↘ (đỏ) so với kỳ trước cùng độ dài. Chỉ hiển thị khi có đủ data kỳ trước.
Hiển thị KPI Block B: luôn hiển thị full bộ KPI trên web và mobile để đồng bộ nhận thức giữa 2 nền tảng, không dùng toggle ẩn/hiện.
Spec Priority Card Strip — "Ưu tiên xử lý nợ quá hạn" (Ref DEC-033)
Quy tắc as-of TODAY (Ref DEC-033 LOCKED): Banner LUÔN tính số khách + tiền nợ as-of
NOW(), KHÔNG phụ thuộc filter date của dashboard. Đây là CTA hành động hôm nay, KHÔNG phải report kỳ. Scopebranch+Nhân sựcủa filter VẪN áp dụng (Manager xem branches mình quản lý; Sale xem của mình).
| Thành phần | Mô tả |
|---|---|
| Vị trí | Ngay dưới top filter, phía trên Block B/C |
| Data scope (Ref DEC-033) | As-of NOW() — KHÔNG dùng date_to filter. COUNT khách + SUM tiền nợ tính theo snapshot HIỆN TẠI. Filter branch + Nhân sự của dashboard VẪN áp dụng (không phải global) |
| Hiển thị khi | Có ≥1 khách nợ quá hạn >= threshold_days AS-OF NOW |
| Header | "Ưu tiên xử lý nợ quá hạn ({count})" + summary tổng nợ + count theo bucket |
| Sub-label cảnh báo (Ref DEC-033) | "⚡ Dữ liệu hiện tại — không phụ thuộc bộ lọc thời gian" (font 11px, italic, color #FF8800) — luôn hiện để phân biệt với KPI cards |
| Visual cue | Background cam nhạt (#FFF4E5) + border-left 4px cam (#FF8800) + icon ⚠️ — phân biệt với KPI cards trung tính |
| Nội dung chính | Danh sách card ngang khách ưu tiên (desktop) / card dọc (mobile) |
| Card item | Tên, SĐT, badge Nhóm nợ, Tổng nợ, Quá hạn, % tham gia (weighted Ref DEC-029), CTA [Gọi] [Tạo lịch] |
Hành vi click [Xử lý ngay →] | Mở SCR-03 (tab Công nợ) với filter date RESET về TODAY (date_from = date_to = NOW()). Filter branch + Nhân sự giữ nguyên |
Hành vi click card hoặc CTA [Gọi]/[Tạo lịch] | Mở flow xử lý nợ tại chỗ (không chuyển màn) |
| Empty state | Ẩn banner hoàn toàn nếu count = 0 AS-OF NOW |
| i18n keys | debt.priority_strip.title: "Ưu tiên xử lý nợ quá hạn" · debt.priority_strip.sublabel_realtime: "⚡ Dữ liệu hiện tại — không phụ thuộc bộ lọc thời gian" |
Logic edge cases:
- User filter "tháng 1/2024" (quá khứ) → Banner vẫn hiển thị "5 khách nợ HÔM NAY" với sub-label cảnh báo. Click [Xử lý ngay] → reset filter date về TODAY → mở SCR-03
- Manager filter Nhân sự = staff X → Banner đếm khách nợ AS-OF NOW của staff X
- Không có khách nợ overdue >= threshold AS-OF NOW → Banner ẨN hoàn toàn (không hiển thị empty state)
Spec Biểu đồ phân loại nhóm nợ (Block C)
| Thuộc tính | Mô tả |
|---|---|
| Loại biểu đồ | Horizontal stacked bar chart (1 thanh ngang, 4 segments) |
| Vị trí | Block C — toàn chiều ngang, giữa Block A+B (KPI) và Block D (bảng) |
| Hiển thị trên tab | Chỉ trên tab “💰 Công nợ”. Ẩn trên tab “📊 Hiệu suất tư vấn” |
| Dữ liệu nguồn | Aggregate từ getDebtOverdueList → group by aging bucket → count + sum |
4-Color Mapping:
| Bucket | Màu | Hex | Ý nghĩa |
|---|---|---|---|
| 0–29 ngày | 🟢 Xanh | #4CAF50 | Nợ mới, chưa quá hạn nghiêm trọng |
| 30–59 ngày | 🟡 Vàng | #FFC107 | Cần theo dõi |
| 60–89 ngày | 🟠 Cam | #FF9800 | Rủi ro cao |
| ≥90 ngày | 🔴 Đỏ | #F44336 | Nghiêm trọng, ưu tiên xử lý |
Mỗi segment hiển thị:
- Số khách (VD:
12 khách) - Phần trăm (VD:
52%) - Tổng tiền nợ (VD:
27.800.000đ)
Behavior:
| Hành động | Mô tả |
|---|---|
| Click segment | Apply local filter Nhóm nợ = bucket đã click. Ảnh hưởng Summary Banner, Daily Focus, Block D và export debt. VD: click 🔴 → working set chỉ còn nhóm ≥90d |
| Click segment đang active | Bỏ local filter Nhóm nợ → working set quay về Tất cả |
| Hover segment (web) | Tooltip: "{bucket_label}: {count} khách · {amount}đ · {percent}%" |
| Cursor | pointer trên mỗi segment |
| Active state | Segment được chọn có border 2px solid #000. Các segment khác opacity 0.4. Chart vẫn giữ full distribution của scope hiện tại, không recalc theo local filter Nhóm nợ |
| Animation | Bar segments animate từ trái sang phải, stagger 100ms mỗi segment |
| Empty state | Khi tất cả bucket = 0: Ẩn chart, hiện text "Không có khách nợ quá hạn 🎉" |
| Segment = 0 | Bucket không có khách → không render segment (không hiện thanh rỗng) |
| Responsive | Min-width segment: 40px. Nếu segment quá nhỏ → hiện tooltip thay vì label inline |
| i18n keys | debt.aging.bucket_0_29: “0–29 ngày” |
debt.aging.bucket_30_59: “30–59 ngày” | |
debt.aging.bucket_60_89: “60–89 ngày” | |
debt.aging.bucket_90_plus: “≥90 ngày” | |
debt.aging.empty: “Không có khách nợ quá hạn” | |
debt.aging.tooltip: “{label}: {count} khách · {amount} · {percent}%” |
Mapping bảng danh sách theo tab
Theo IA mới, không còn 1 bảng danh sách dùng chung cho cả 2 tab. Mỗi tab dùng 1 bảng riêng với mục tiêu và cột hiển thị khác nhau.
| Tab | Bảng dùng | Cột hiển thị chính |
|---|---|---|
| Hiệu suất tư vấn | SCR-02 | Khách / Dịch vụ / % Của tôi / Ngày TV / Trạng thái |
| Công nợ | SCR-03 | Khách / Liên hệ gần nhất / Phụ trách chính / Tiền nợ / % tham gia / Nhóm nợ / Quá hạn / Hành động |
Nguyên tắc:
- Tab
Hiệu suất tư vấntập trung vào conversion sau tư vấn, không hiển thị cột nợ. - Tab
Công nợtập trung vào follow-up thu nợ, không hiển thị cột dịch vụ tư vấn hay trạng thái mua trong kỳ. - Nếu cần xem chi tiết từng bảng, dùng spec riêng ở
SCR-02vàSCR-03bên dưới.
SCR-02 — Chi tiết danh sách tư vấn
Layout
┌──────────────────────────────────────────────────────────┐
│ ← Quay lại Dashboard │
│ "Danh sách tư vấn — Trần Văn Sale — Tháng 2/2026" │
├──────────────────────────────────────────────────────────┤
│ [Trạng thái: Tất cả ▼ | Đã mua trong kỳ | Chưa mua trong kỳ] │
│ Toggle: [Theo lượt tư vấn ●] [Theo khách đã tư vấn] │
├──────────────────────────────────────────────────────────┤
│ Hiển thị: 1–50 / 128 [Xuất XLSX] │
│ Khách │ Dịch vụ │ % Của tôi │ Ngày TV │ Trạng thái│
│ A │ Cắt tóc │ 100% │ 20/02 │ ✅ Đã mua trong kỳ │
│ A │ Nhuộm │ 50% ① │ 20/02 │ ✅ Đã mua trong kỳ │
│ B │ Spa │ 100% │ 21/02 │ Chưa mua trong kỳ │
└──────────────────────────────────────────────────────────┘
① Tooltip: "Nhuộm: Sale A (50%) | Sale B (50%) — Bạn là Sale A"Toggle “Theo lượt / Theo khách đã tư vấn”:
- Theo lượt (default): Hiển thị theo grain lượt tư vấn hiện tại
(customer_id, branch_id, date)cóconsultant - Theo khách đã tư vấn: Gộp theo customer_id, hiển thị customer summary row neo theo lần tư vấn gần nhất
Spec Quy tắc gộp dòng (SCR-02)
| Chế độ | Grain dòng | Quy tắc hiển thị cột |
|---|---|---|
Theo lượt tư vấn | 1 row = 1 lượt theo grain (customer_id, branch_id, date) có source consultant | Khách: có thể lặp lại theo ngày/chi nhánh. Dịch vụ, % Của tôi, Ngày TV lấy theo bản ghi representative trong ngày. Trạng thái tính theo khách trong kỳ lọc: nếu khách có ≥1 giao dịch thỏa điều kiện mua trong kỳ thì mọi row của khách đó hiện Đã mua trong kỳ |
Theo khách đã tư vấn | 1 row = 1 customer_id trong kỳ lọc | Đây là customer summary row. Dịch vụ, % Của tôi, Ngày TV phản ánh touchpoint tư vấn gần nhất của khách trong kỳ để user biết lần follow-up cuối. Trạng thái phản ánh kết quả mua của cả khách trong kỳ lọc, không chỉ riêng lượt gần nhất |
Tie-break cho representative row (Theo khách đã tư vấn) — Ref DEC-027 + FORMULA-007:
- Sort canonical:
consultation_date DESC, order_service_id DESC LIMIT 1 - Nếu vẫn bằng nhau →
consultation_idASC để deterministic
Cột "Dịch vụ" — quy tắc multi-DV (Ref FORMULA-007):
- 1 DV duy nhất trong lượt/kỳ: hiển thị tên DV (vd "Tẩy nốt ruồi")
- ≥ 2 DV cùng lượt/kỳ: hiển thị "
{tên DV representative}(+{N-1} DV khác)" — vd "Tẩy nốt ruồi (+2 DV khác)" - Tooltip khi hover: liệt kê đầy đủ tất cả DV theo format
• {tên DV} — {% Của tôi} - Edge case: DV name NULL → hiển thị "Dịch vụ #{order_service_id}"; không có DV nào → "—"
Ghi chú UX: Trong mode
Theo khách đã tư vấn, row không đại diện cho một giao dịch duy nhất mà là tóm tắt 1 khách trong kỳ. Vì vậy cộtTrạng tháiphải được hiểu là "khách này có mua trong kỳ hay không", cònDịch vụ / % Của tôi / Ngày TVlà dấu vết của lần tư vấn gần nhất.
Spec Filter Bar (SCR-02)
| Component | Type | Default | Behavior |
|---|---|---|---|
| Trạng thái | Button group | Tất cả | Tất cả / Đã mua trong kỳ / Chưa mua trong kỳ. Click → filter bảng |
| Toggle view | Switch | Theo lượt tư vấn | Chuyển đổi chế độ hiển thị. Không reset filter Trạng thái |
Spec Bảng danh sách tư vấn (SCR-02)
| Cột | Sortable | Format | Ghi chú |
|---|---|---|---|
| Khách | ✅ | Text + link → CRM | — |
| Dịch vụ | ❌ | Text với multi-DV format (Ref FORMULA-007) | Format: 1 DV → tên DV; ≥2 DV → "{tên rep} (+N-1 DV khác)" với tooltip liệt kê đầy đủ. Tie-break: consultation_date DESC, order_service_id DESC |
| Cuộc gọi (Ref DEC-028 LOCKED) | ✅ | "Gần nhất: HH:mm DD/MM/YYYY" + icon phone xanh | Source: ecommerce_user.last_contacted_at (denormalized field, được app mobile cập nhật khi Sale gọi qua click-to-call). Scope: all-time, không filter theo dashboard period. Empty: "Chưa gọi" nếu last_contacted_at IS NULL. Tooltip: "Cuộc gọi gần nhất từ app Diva Staff cho khách này". Lưu ý format: dùng DD/MM/YYYY (uppercase MM = tháng), KHÔNG dùng DD/mm/YYYY (lowercase mm = phút) — Ref BUG-PROD-001 |
| % tham gia (Ref DEC-029 + FORMULA-008) | ✅ | N.NN% + icon ⓘ | Weighted average theo order.total khi KH có nhiều đơn cùng row tư vấn. Công thức: SUM(pct × order.total) / SUM(order.total). Tooltip drill-down phải liệt kê chi tiết per-order với format đầy đủ (xem FORMULA-008 ví dụ). Single order: = % của đơn đó (no aggregation). Edge cases: denominator = 0 → "—". Ref DEC-024: ẨN cột này trong view Manager/BOD (không có % HH) |
| Ngày TV | ✅ | DD/MM/YYYY | Ngày tư vấn |
| Trạng thái | ✅ | ✅ Đã mua trong kỳ / Chưa mua trong kỳ | — |
Pagination: 50 rows/trang. Format: Hiển thị: 1–50 / {total}. Default sort: ngày TV DESC (tư vấn gần nhất lên đầu).
SCR-03 — Nợ quá hạn cần xử lý
Layout
┌────────────────────────────────────────────────────────────────┐
│ "Nợ quá hạn cần xử lý" │
│ ℹ️ Dữ liệu cập nhật lúc 07:00 hôm nay, 24/02/2026 │
├────────────────────────────────────────────────────────────────┤
│ SCOPE CHIP: [☑ Sale] [☑ Telesale] [☑ CSKH] │
├────────────────────────────────────────────────────────────────┤
│ SUMMARY BANNER (màu theo nhóm nợ cao nhất hiện có) │
│ 🔴 Bạn có 5 khách nợ ≥90 ngày — ưu tiên xử lý ngay │
│ Tổng: 23 khách | 85.000.000đ nợ quá hạn [Lọc nhóm ≥90 →] │
├────────────────────────────────────────────────────────────────┤
│ 🎯 DAILY FOCUS — Top 5 khách ưu tiên hôm nay [Ẩn ▴] │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 1. Nguyễn A · 6,500k · 95d 🔴 [📞] Chưa LH 15 ngày ││
│ │ 2. Lê C · 4,800k · 72d 🟠 [📞] Nhắc hôm nay 10:00 ││
│ │ 3. Phạm D · 4,200k · 68d 🟠 [📞] Chưa LH 8 ngày ││
│ │ 4. Trần B · 3,200k · 45d 🟡 [📞] Chưa LH 20 ngày ││
│ │ 5. Hoàng E · 2,100k · 35d 🟡 [📞] Lịch nhắc quá hạn ││
│ └─────────────────────────────────────────────────────────────┘│
│ [Xem toàn bộ danh sách ↓] │
├────────────────────────────────────────────────────────────────┤
│ [Nhóm nợ: Tất cả ▼] [Chi nhánh ▼] [Owner ▼] [🔍 Tìm khách...] │
├────────────────────────────────────────────────────────────────┤
│ [📅 Lịch nhắc (2) 🔴] [Xuất] │
│ TOOLBAR (hiện khi chọn ≥1 row) │
│ ☑ 3 đã chọn [✓ Đánh dấu LH]│
├──┬──────────┬──────────┬────────┬────────┬────────┬────────┬───────────┤
│☐ │ Khách │ Liên hệ gần nhất │ Owner chính │ Tiền │ % tham │ Nhóm nợ │ Quá hạn │ Hành động │
│ │ │ chính │ trách │ nợ │ nợ │ hạn │ │
├──┼──────────┼──────────┼────────┼────────┼────────┼────────┼───────────┤
│☐ │▶Nguyễn A │ Đã gọi: 2 lần │ Nguyễn B │6,500k│ 30% │≥90 🔴 │95 ngày │[📞 Gọi] [⋯]│
│ │ 3DV·2đơn │ Gần nhất 09:00... │ │ │ │ │ │ │
│ ├──────────┴──────────┴────────┴────────┴────────┴────────┤ │
│ │ (expand) CHI TIẾT NỢ: │ │
│ │ 📋 ĐH-001 · 15/01 Cắt tóc 2,000k 47d 🟡 │ │
│ │ Nhuộm 3,000k 47d 🟡 │ │
│ │ 📋 ĐH-002 · 20/02 Massage 1,500k 5d 🟢 │ │
│ ├──────────┬──────────┬────────┬────────┬────────┬────────┤ │
├──┼──────────┼──────────┼────────┼────────┼────────┼────────┼───────────┤
│☐ │▶Trần B │ Chưa gọi lần nào │ Nguyễn B │3,200k│ 50% │30-59🟡 │45 ngày │[📞 Gọi] [⋯]│
│ │ 1DV·1đơn │ │ │ │ │ │ │ │
└──┴──────────┴──────────┴────────┴────────┴────────┴────────┴───────────┘
│ [< 1 2 3 >] 25/trang ▼ │
└────────────────────────────────────────────────────────────────┘Spec Sắp xếp mặc định bảng
| Thành phần | Mô tả |
|---|---|
| Default sort | overdue_days DESC — khách nợ lâu nhất lên đầu |
| Lý do | Người dùng cần xử lý khách rủi ro cao nhất trước, giảm thời gian tìm kiếm |
| Secondary sort | outstanding_amount DESC (khi overdue_days bằng nhau) |
| User override | Bấm header cột sortable → đổi sort. Reset khi chuyển tab/filter |
Spec Filter Bar (SCR-03)
| Component | Type | Default | Behavior |
|---|---|---|---|
| Nhóm nợ | Single select dropdown | Tất cả | Options: Tất cả / 0–29 / 30–59 / 60–89 / ≥90. Click → áp dụng working-set filter cho Summary Banner, Daily Focus, Block D và export debt. Block C không recalc, chỉ highlight nhóm đang active |
| Chi nhánh | Single select dropdown | Tất cả (theo quyền) | Staff: ẩn (chỉ xem data mình). Manager: branch scope. Admin: tất cả |
| Owner | Single select dropdown | Tất cả | Cascade từ Chi nhánh. Staff: ẩn. Manager/Admin: chọn nhân viên trong branch. Nếu top filter Nhân sự đã chọn 1 người cụ thể thì local Owner tự sync theo người đó và chuyển readonly |
| Tìm khách | Text input | — | Search by customer_name. Debounce 300ms. Min 2 ký tự |
Reset: Đổi scope chip vai trò (Sale/Telesale/CSKH) không reset Nhóm nợ; chỉ recalc dữ liệu theo scope mới. Nếu top filter Nhân sự đang khóa local Owner, local filter vẫn giữ readonly.
Spec Bảng nợ quá hạn (SCR-03)
| Cột | Sortable | Format | Ghi chú |
|---|---|---|---|
| Khách | ✅ | ▶ Tên + N DV·M đơn dưới | Click ▶/tên → expand chi tiết nợ theo đơn |
| Liên hệ gần nhất | ✅ | Đã gọi N lần + Gần nhất: HH:mm DD/MM/YYYY | Cột riêng cho lịch sử gọi/liên hệ. Nếu chưa có lịch sử gọi: hiển thị Chưa gọi lần nào |
| Phụ trách chính | ✅ | Text | Tên owner chính. “Tôi” nếu là current user |
| Tiền nợ | ✅ | #,### đ | SUM outstanding tất cả DV nợ |
| % tham gia | ✅ | N% | Hiển thị theo weighted average theo outstanding_amount |
| Nhóm nợ | ✅ | Badge màu | Aging bucket cao nhất (customer-level rollup — Ref FORMULA-006B + DEC-023). Lấy max(overdue_days) trong tất cả ORDER nợ của khách (KHÔNG order-level). Vd: KH có đơn 15d + 95d → bucket = very_high_risk. Biểu đồ phân loại + bảng count đều dùng customer-level (1 KH = 1 unit ở bucket cao nhất) |
| Quá hạn | ✅ | N ngày | Số ngày quá hạn cao nhất trong tất cả DV nợ |
| Hành động | ❌ | [👁 Xem] [📅 Tạo lịch] | Có thể bổ sung icon gọi theo cấu hình role |
Pagination: 25 rows/trang. Dropdown chọn: 25 / 50 / 100.
Spec Quy tắc gộp dòng (SCR-03)
| Chủ đề | Quy tắc |
|---|---|
| Grain dòng | 1 row = 1 customer_id trong scope filter hiện tại |
| Scope dữ liệu | Include khách có ≥1 dòng nợ outstanding thỏa top filter (thời gian/chi nhánh/vai trò/nhân sự) và local filter (nhóm nợ/owner/tìm kiếm) |
Tiền nợ | Tổng outstanding của các dòng nợ nằm trong scope hiện tại của row |
Owner chính | Là debt_owner_id theo rule owner của từng dòng nợ; ở row tổng hợp hiển thị owner có outstanding lớn nhất trong row scope |
% tham gia | Weighted average theo outstanding_amount trong row scope, làm tròn 1 chữ số thập phân; tooltip liệt kê chi tiết từng dòng DV |
Liên hệ gần nhất | Lấy từ debt_contact_log: call_count = tổng số lần gọi/liên hệ đã ghi nhận cho khách trong scope hiện tại; last_call_time = contacted_at mới nhất. Nếu chưa có log → Chưa gọi lần nào |
Nhóm nợ | Lấy nhóm nợ cao nhất của các dòng nợ trong row scope = max(overdue_days) |
Quá hạn | Số ngày quá hạn cao nhất của các dòng nợ trong row scope |
| Expand row | Chỉ hiển thị các đơn/dịch vụ nợ thuộc row scope hiện tại, không hiển thị dòng nợ ngoài scope |
Ví dụ: cùng 1 khách có 3 đơn nợ với 2 owner khác nhau. Row vẫn gộp theo khách (
1 row/customer), nhưng tooltip% tham giavà phần expand sẽ thể hiện đầy đủ phân bổ theo từng đơn/dịch vụ và owner.
Spec Toolbar Batch Actions (SCR-03)
| Thuộc tính | Mô tả |
|---|---|
| Hiện khi | Chọn ≥1 row qua checkbox |
| Vị trí | Sticky bar giữa filter bar và bảng |
| Nội dung | ☑ {N} đã chọn [✓ Đánh dấu LH] |
| Action “Đánh dấu LH” | Batch mark tất cả row đã chọn là “Đã liên hệ”. Gọi markCustomerContacted × N lần (parallel, max 10 concurrent) |
| Confirm | Không cần confirm (action nhẹ, có thể undo từng row) |
| Feedback | Toast: "Đã đánh dấu liên hệ {N} khách". Mỗi row hiện icon ✅ |
| Undo | Không có batch undo. User mở overflow menu [⋯] → “Hủy đánh dấu LH” từng row |
| Nút [📅 Lịch nhắc (N) 🔴] | Luôn hiện (không thuộc toolbar). Click → mở SCR-03-DRAWER. Badge count = số lịch nhắc pending. 🔴 khi có ≥1 quá hạn |
| i18n keys | debt.toolbar.selected: “ đã chọn” |
debt.toolbar.mark_contacted: “Đánh dấu LH” | |
debt.toolbar.toast_contacted: “Đã đánh dấu liên hệ khách” |
Spec Expandable Row — Chi tiết nợ theo đơn hàng
| Thành phần | Mô tả |
|---|---|
| Trigger | Click ▶ icon hoặc tên khách để expand/collapse |
| Mặc định | Collapsed — chỉ hiện dòng tổng hợp: tổng nợ + số DV + số đơn + aging cao nhất |
| Nội dung expand | Nhóm theo đơn hàng (order_code + ngày đơn), mỗi đơn liệt kê DV nợ bên dưới |
| Mỗi dòng DV | Tên dịch vụ + số tiền nợ + số ngày quá hạn + badge aging (tính riêng từ debt_due_date của order_service) |
| Sắp xếp đơn | Đơn cũ nhất trước (ngày đơn ASC) |
| Sắp xếp DV | Tiền nợ DESC trong mỗi đơn |
| Dòng tổng hợp (collapsed) | Hiện: N DV · M đơn bên dưới tên khách |
| Cột "Nhóm nợ" | Customer-level aging bucket — max(overdue_days) trong tất cả ORDER nợ của khách (Ref FORMULA-006B + DEC-023). Tránh double-count nếu KH có nhiều đơn ở nhiều bucket — chỉ gán 1 KH vào bucket cao nhất |
| Cột "Quá hạn" | Hiển thị số ngày quá hạn cao nhất = max(overdue_days) trong tất cả order nợ của khách (consistent với cột Nhóm nợ) |
| Cột “Tiền nợ” | Tổng tất cả dòng DV nợ (= SUM outstanding per order_service) |
Spec CTA Buttons
Nguyên tắc: Chỉ hiện 1 CTA chính ([📞 Gọi]) trực tiếp trên dòng. Các action còn lại gom vào overflow menu [⋯] để giảm noise thị giác. Lý do: 25 dòng × 4 nút = 100+ nút trên 1 trang gây “decision paralysis”.
| Nút | Vị trí | Hành động | Behavior |
|---|---|---|---|
| 📞 Gọi | Hiện trực tiếp trên dòng | Mở tính năng gọi trên diva partner | CTA chính. Nợ ≥90d → nút màu #B71C1C (đỏ). Nợ < 90d → màu neutral |
| ⋯ Menu | Hiện trực tiếp bên cạnh nút Gọi | Mở dropdown menu | Click → hiện dropdown 3 mục bên dưới |
| ├ 📅 Tạo lịch nhắc | Trong menu ⋯ | Mở popup tạo lịch nhắc | Xem spec popup |
| ├ ✓ Đánh dấu Đã LH | Trong menu ⋯ | Toggle trạng thái liên hệ | Lưu timestamp + user_id. Icon ✅ khi đã LH |
| └ → Xem CRM | Trong menu ⋯ | Mở CRM chi tiết khách | Mở tab mới |
Overflow menu [⋯] behavior:
- Click → dropdown hiện 3 items, đóng khi click ngoài
- Mỗi item có icon + label tiếng Việt
- Nếu khách đã đánh dấu LH → item “Đánh dấu Đã LH” đổi thành “Hủy đánh dấu LH”
Spec Daily Focus — Top 5 khách ưu tiên
| Thuộc tính | Mô tả |
|---|---|
| Vị trí | SCR-03, giữa Summary Banner và Filter Bar |
| Hiển thị cho | Staff (dữ liệu cá nhân), Manager/Admin theo scope filter hiện tại |
| Số lượng | Top 5 khách. Không phân trang, không “load more” |
| Thuật toán sắp xếp | Ưu tiên phức hợp: (1) Có lịch nhắc quá hạn, (2) Nợ ≥90 ngày chưa LH, (3) overdue_days DESC, (4) outstanding_amount DESC |
| Mỗi dòng hiển thị | #. Tên khách · tiền nợ · số ngày quá hạn + badge · [📞] · context hint |
| Context hint | Logic: Lịch nhắc quá hạn > Nhắc hôm nay HH:MM > “Chưa LH N ngày” > “LH lần cuối DD/MM” |
| CTA | [📞] — gọi trực tiếp. Click tên khách → scroll xuống dòng tương ứng trong bảng chính |
| Toggle ẩn/hiện | [Ẩn ▴] / [Hiện ▾] — persist localStorage key debt_daily_focus_visible |
| Default | Hiện khi có ≥1 khách nợ ≥30 ngày. Ẩn nếu tất cả nợ < 30 ngày |
| Link cuối | [Xem toàn bộ danh sách ↓] → smooth scroll đến bảng chính (Block D) |
| Refresh | Cập nhật khi thay đổi filter. Luôn lấy từ dữ liệu đã filter |
| i18n keys | debt.daily_focus.title: “Top 5 khách ưu tiên hôm nay” |
debt.daily_focus.no_contact: “Chưa LH ngày” | |
debt.daily_focus.reminder_today: “Nhắc hôm nay ” | |
debt.daily_focus.reminder_overdue: “Lịch nhắc quá hạn” | |
debt.daily_focus.view_all: “Xem toàn bộ danh sách” |
Spec Summary Banner (SCR-03)
| Thuộc tính | Mô tả |
|---|---|
| Vị trí | SCR-03, ngay dưới scope chip vai trò, trên Daily Focus (nếu có) và Filter Bar |
| Mục đích | Cảnh báo nhanh mức độ nghiêm trọng nợ — 1 dòng glanceable |
| Hiển thị khi | Có ≥1 khách nợ quá hạn (overdue_days > 0) trong tab đang active |
| Ẩn khi | Tất cả khách nợ có overdue_days = 0 (chưa quá hạn) |
Color logic (background + icon theo nhóm nợ cao nhất hiện có):
| Nhóm nợ cao nhất | Background | Border-left | Icon | Text mẫu |
|---|---|---|---|---|
| ≥90 ngày | #FFEBEE (đỏ nhạt) | 4px #B71C1C | 🔴 | “Bạn có 5 khách nợ ≥90 ngày — ưu tiên xử lý ngay” |
| 60–89 ngày | #FFF3E0 (cam nhạt) | 4px #E65100 | 🟠 | “Bạn có 3 khách nợ 60-89 ngày — cần theo dõi” |
| 30–59 ngày | #FFF8E1 (vàng nhạt) | 4px #F57F17 | 🟡 | “Bạn có 6 khách nợ 30-59 ngày” |
| 0–29 ngày | #E8F5E9 (xanh nhạt) | 4px #2E7D32 | 🟢 | “Tất cả khách nợ trong vùng an toàn” |
Dynamic text template:
"{icon} Bạn có {{count_highest}} khách nợ {{bucket_label}} — {{urgency_text}}"
"Tổng: {{total_count}} khách | {{total_amount}}đ nợ quá hạn [CTA]"Behavior:
| Hành động | Mô tả |
|---|---|
CTA [Lọc nhóm ≥90 →] | Click → apply local filter Nhóm nợ = ≥90 ngày cho Summary Banner, Daily Focus, Block D và export debt. Label CTA dynamic theo nhóm nợ cao nhất |
| CTA khi nhóm nợ max < 90 | [Lọc nhóm {bucket_label} →] — VD: [Lọc nhóm 60-89 ngày →] |
| Đổi scope chip | Banner cập nhật theo tổ hợp scope vai trò mới (Sale/Telesale/CSKH) |
| Đổi filter | Banner recalculate theo working set đang active |
| Transition | Fade-in 200ms khi xuất hiện, slide-up 150ms khi ẩn |
| Manager xem | Hiện aggregate tất cả staff trong branch (không chỉ của mình) |
| i18n keys | debt.banner.critical: “Bạn có khách nợ ≥90 ngày — ưu tiên xử lý ngay” |
debt.banner.warning: “Bạn có khách nợ — cần theo dõi” | |
debt.banner.info: “Bạn có khách nợ ” | |
debt.banner.safe: “Tất cả khách nợ trong vùng an toàn” | |
debt.banner.total: “Tổng: khách | nợ quá hạn” | |
debt.banner.filter_cta: “Lọc nhóm ” |
Popup Tạo lịch nhắc
┌───────────────────────────────────────────┐
│ 📅 Tạo lịch nhắc — Nguyễn Văn A │
│ Tổng nợ: 6.500.000đ | 3 DV · 2 đơn │
│ ĐH-001: Cắt tóc 2,000k 47d 🟡 │
│ Nhuộm 3,000k 47d 🟡 │
│ ĐH-002: Massage 1,500k 5d 🟢 │
├───────────────────────────────────────────┤
│ Ngày nhắc * [DD/MM/YYYY] [HH:MM] │
│ Nội dung * [Gọi nhắc thanh toán...] │
│ Ghi chú [Thêm ghi chú...] │
│ Gán cho [Tôi ▼] (Manager có thể gán) │
├───────────────────────────────────────────┤
│ [Hủy] [Tạo lịch] │
└───────────────────────────────────────────┘Lưu ý: Phần chi tiết nợ trong popup chỉ hiện tối đa 3 đơn gần nhất. Nếu > 3 đơn → hiện dòng “+N đơn khác” dạng link. Không collapse — giữ popup nhỏ gọn.
SCR-03-DRAWER — Lịch nhắc chăm sóc của tôi (Overlay Drawer)
Trigger: Nút [📅 Lịch nhắc (N)] trên toolbar SCR-03 hoặc nút [📅] bên cạnh từng khách. Mở từ khách cụ thể: Drawer lọc sẵn theo customer_id đó. Mở từ toolbar: Hiển thị tất cả lịch nhắc của user.
Layout mode: Overlay (không push)
- Drawer phủ lên bảng SCR-03, không đẩy bảng sang trái
- Background SCR-03 bị mờ (backdrop
rgba(0,0,0,0.4)) - Click vùng mờ hoặc nút [✕] → đóng drawer, bảng trở lại bình thường
- Lý do: Tránh bảng 8 cột bị co lại khi drawer mở. User focus 1 việc (quản lý lịch nhắc), xong đóng drawer quay lại bảng nguyên vẹn.
┌─── SCR-03 (nền mờ, backdrop) ─────────┬─────────────────────────────────────┐
│ │ ← Lịch nhắc của tôi [✕] │
│ (Danh sách nợ quá hạn │─────────────────────────────────────│
│ bị mờ — click để đóng drawer) │ [Tất cả ▼] [Chờ] [Quá hạn] [Xong] │
│ │ [🔍 Tìm tên khách...] │
│ │─────────────────────────────────────│
│ │ HÔM NAY (2 nhắc) │
│ │ ┌─────────────────────────────────┐ │
│ │ │ 🔴 QUÁ HẠN │ │
│ │ │ Nguyễn Văn A · 6.500.000đ │ │
│ │ │ 3DV · 2đơn · cao nhất 95d 🔴 │ │
│ │ │ 📅 09:00 — Gọi nhắc thanh toán│ │
│ │ │ Ghi chú: Hẹn trả cuối tháng │ │
│ │ │ [✅ Đã xong] [❌ Hủy] [📞 Gọi]│ │
│ │ └─────────────────────────────────┘ │
│ │ ┌─────────────────────────────────┐ │
│ │ │ 🟡 CHỜ NHẮC │ │
│ │ │ Trần Thị B · 3.200.000đ │ │
│ │ │ 1DV · 1đơn · 45d 🟡 │ │
│ │ │ 📅 14:00 — Nhắc trả tiền DV │ │
│ │ │ [✅ Đã xong] [❌ Hủy] [📞 Gọi]│ │
│ │ └─────────────────────────────────┘ │
│ │─────────────────────────────────────│
│ │ NGÀY MAI (1 nhắc) │
│ │ ┌─────────────────────────────────┐ │
│ │ │ 🟢 CHỜ NHẮC │ │
│ │ │ Lê Văn C · 1.500.000đ │ │
│ │ │ 1DV · 1đơn · 15d 🟢 │ │
│ │ │ 📅 03/03 10:00 — Gọi hỏi thăm│ │
│ │ │ [✅ Đã xong] [❌ Hủy] [📞 Gọi]│ │
│ │ └─────────────────────────────────┘ │
│ │─────────────────────────────────────│
│ │ ĐÃ HOÀN THÀNH (collapse) │
│ │ ▶ 5 lịch nhắc đã xong trong 7 ngày│
│ │─────────────────────────────────────│
│ │ [+ Tạo lịch nhắc mới] │
│ └─────────────────────────────────────┘
└───────────────────────────────────────┘Khi mở từ 1 khách cụ thể (Nguyễn Văn A):
┌─────────────────────────────────────┐
│ ← Lịch nhắc — Nguyễn Văn A [✕] │
│ Tổng nợ: 6.500.000đ · cao nhất 95d │
│─────────────────────────────────────│
│ DỊCH VỤ NỢ (3 DV · 2 đơn) │
│ │
│ 📋 ĐH-001 · 15/01/2026 │
│ ├ Cắt tóc 2.000.000đ 47d 🟡 │
│ └ Nhuộm 3.000.000đ 47d 🟡 │
│ │
│ 📋 ĐH-002 · 20/02/2026 │
│ └ Massage 1.500.000đ 5d 🟢 │
│ │
│ Tổng: 6.500.000đ │
│─────────────────────────────────────│
│ LỊCH SỬ FOLLOW-UP (3 lần nhắc) │
│ │
│ ● 02/03 09:00 — Gọi nhắc TT │
│ Trạng thái: 🔴 Quá hạn │
│ Ghi chú: Hẹn trả cuối tháng │
│ [✅ Đã xong] [❌ Hủy] │
│ │
│ ● 25/02 10:00 — Nhắc qua Zalo │
│ Trạng thái: ✅ Đã xong │
│ Hoàn thành: 25/02 10:32 bởi Tôi │
│ │
│ ● 20/02 09:00 — Gọi nhắc lần 1 │
│ Trạng thái: ✅ Đã xong │
│ Hoàn thành: 20/02 09:15 bởi Tôi │
│ │
│─────────────────────────────────────│
│ [+ Tạo lịch nhắc mới cho khách này]│
└─────────────────────────────────────┘Spec Drawer Lịch Nhắc
| Thành phần | Mô tả |
|---|---|
| Width | 420px (fixed right drawer) |
| Nhóm theo | Ngày (Hôm nay / Ngày mai / DD/MM/YYYY) |
| Sắp xếp | remind_at ASC trong mỗi nhóm ngày |
| Badge trạng thái | 🔴 Quá hạn (pending + remind_at < now) · 🟡 Chờ nhắc (pending + remind_at ≥ now) · 🟢 Chờ nhắc (xa, > 3 ngày) · ✅ Đã xong · ❌ Đã hủy |
| Section “Đã hoàn thành” | Collapse mặc định, chỉ hiện 7 ngày gần nhất |
| Mở từ toolbar | Hiện tất cả lịch nhắc của user (assigned_to = me) |
| Mở từ nút khách | Filter theo customer_id, hiện timeline follow-up |
| Nút “Đã xong” | Chuyển status → done, ghi completed_at = now() |
| Nút “Hủy” | Confirm dialog → chuyển status → cancelled |
| Counter trên nút toolbar | Badge đỏ = số lịch nhắc quá hạn (pending + overdue) |
Spec Scope Chip Vai trò (SCR-03)
| Tiêu chí | Quy tắc |
|---|---|
| Điều kiện scope | Sale: include dòng nợ có role Sale; Telesale: include dòng nợ có role Telesale; CSKH: include dòng nợ có role CSKH |
| Kết hợp nhiều chip | OR logic giữa các chip đang bật |
| Grain danh sách | 1 row = 1 customer_id trong scope filter hiện tại |
| Cột “Owner chính” | Hiển thị owner của dòng nợ có outstanding lớn nhất trong row scope |
| Cột “% tham gia” | Weighted average participation theo outstanding_amount trong row scope |
| Cột “Liên hệ gần nhất” | Đã gọi N lần + Gần nhất: ... hoặc Chưa gọi lần nào |
| Quyền đánh dấu LH, tạo lịch | Theo RBAC user hiện tại |
| Quyền chọn hàng loạt | Theo RBAC user hiện tại |
SCR-04 — Cài đặt cảnh báo nợ quá hạn
Layout
┌─────────────────────────────────────────────────────────┐
│ "Cấu hình cảnh báo nợ quá hạn" │
│ ℹ️ Thay đổi có hiệu lực từ lần chạy tiếp theo (07:00) │
├─────────────────────────────────────────────────────────┤
│ PHẦN 1 — CẤU HÌNH TOÀN HỆ THỐNG (Global) │
│ Ngưỡng mặc định: [30] ngày ⓘ │
│ Giờ gửi cảnh báo: [07:00] VN timezone │
│ Trạng thái: [● Đang bật ▼] │
│ [Lưu thay đổi] │
├─────────────────────────────────────────────────────────┤
│ PHẦN 2 — OVERRIDE THEO CHI NHÁNH │
│ [+ Thêm override chi nhánh] │
│ Chi nhánh Q1 │ 45 ngày │ [Sửa] [Xóa] │
│ Chi nhánh Q3 │ 60 ngày │ [Sửa] [Xóa] │
├─────────────────────────────────────────────────────────┤
│ PHẦN 3 — LỊCH SỬ THAY ĐỔI │
│ 24/02 09:15 — Admin Nguyễn: global 30→45 ngày │
│ 22/02 14:30 — Admin Trần: thêm override Q1 │
└─────────────────────────────────────────────────────────┘Validation
| Field | Rule | Error |
|---|---|---|
| Ngưỡng ngày | Integer, min=1, max=365 | “Ngưỡng phải từ 1 đến 365 ngày” |
| Giờ gửi | HH:MM, trong 06:00–10:00 | “Giờ gửi phải từ 06:00–10:00” |
| Chi nhánh override | Chọn từ dropdown, không trùng | “Chi nhánh này đã có cấu hình override” |
Spec PHẦN 3 — Lịch sử thay đổi
| Thuộc tính | Mô tả |
|---|---|
| Nguồn dữ liệu | debt_alert_config audit fields (created_by, updated_by, updated_at) + snapshot diff |
| Số dòng hiện | Tối đa 20 entries gần nhất. Scroll nếu nhiều hơn |
| Format mỗi dòng | {DD/MM HH:mm} — {user_name}: {action_description} |
| Action types | global_threshold_changed (“global {old}→{new} ngày”), global_time_changed (“giờ gửi {old}→{new}”), global_status_changed (“bật/tắt cảnh báo”), branch_override_added (“thêm override {branch_name}”), branch_override_updated (“sửa override {branch_name}: {old}→{new} ngày”), branch_override_deleted (“xóa override {branch_name}”) |
| Sắp xếp | updated_at DESC (mới nhất lên trên) |
| Quyền xem | Manager: chỉ thấy entries liên quan branch mình. Admin: thấy tất cả |
| Empty state | “Chưa có lịch sử thay đổi” |
| i18n key (empty) | debt.config.history_empty |
SCR-05 — Bàn giao khách — Bước 1: Chọn nhân viên nghỉ
Layout
┌────────────────────────────────────────────────────────────────┐
│ "Bàn giao danh sách khách hàng" │
│ STEP: [① Chọn nguồn] ─── ② Chọn đích ─── ③ Xác nhận │
│ PROGRESS: ████░░░░░░░░ 33% │
├────────────────────────────────────────────────────────────────┤
│ 1A — NHÂN VIÊN NGHỈ (NGUỒN) * │
│ [🔍 Tìm tên nhân viên...] │
│ 👤 Nguyễn Văn Sale · Sale · Chi nhánh Q1 │
│ Đang quản lý: 45 khách · Có nợ: 12 khách │
│ [Chọn nhân viên này] │
├────────────────────────────────────────────────────────────────┤
│ 1B — PHẠM VI BÀN GIAO * │
│ ⦿ Toàn bộ danh sách (45 khách) │
│ ○ Theo bộ lọc: │
│ [Nhóm nợ ▼] [Từ ngày ▼] [Đến ngày ▼] │
│ → Kết quả lọc: 12 khách │
├────────────────────────────────────────────────────────────────┤
│ 1C — NGÀY HIỆU LỰC * │
│ [📅 DD/MM/YYYY] ≥ hôm nay │
│ ℹ️ Từ ngày này, cảnh báo nợ sẽ gửi về người tiếp nhận │
├────────────────────────────────────────────────────────────────┤
│ [Hủy] [Tiếp theo: Chọn đích →] │
└────────────────────────────────────────────────────────────────┘Validation
| Trường | Rule | Hành vi khi sai |
|---|---|---|
| Nguồn | Bắt buộc, phải active | Nút Tiếp theo disabled / error message |
| Phạm vi filter | ≥ 1 khách nếu chọn filter | “Bộ lọc không ra khách nào” |
| Ngày hiệu lực | ≥ hôm nay | “Ngày hiệu lực phải từ hôm nay trở đi” |
Quick Handover Mode (khi Phạm vi = “Toàn bộ”)
Khi user chọn scope “Toàn bộ danh sách” ở Bước 1, wizard gộp Bước 2 + 3 thành một bước duy nhất, giảm từ 3 bước → 2 bước.
| Thuộc tính | Mô tả |
|---|---|
| Trigger | Phạm vi = “Toàn bộ danh sách” (radio ⦿ đầu tiên) ở SCR-05 |
| Behavior | Bước 2 hiển thị: Chọn đích + Preview + Tóm tắt + Cam kết + Xác nhận (gộp SCR-06 + SCR-07) |
| Stepper UI | [① Chọn nguồn] ─── [② Chọn đích & Xác nhận] (2 steps thay vì 3) |
| Progress | 50% → 100% (thay vì 33% → 66% → 100%) |
| Ẩn | Bảng “DANH SÁCH KHÁCH SẼ CHUYỂN” — vì scope = toàn bộ, không cần duyệt từng khách |
| Hiện thêm | Section Tóm tắt + Cam kết + Ghi chú (từ SCR-07) ngay dưới Preview |
| Nút CTA | [Xác nhận bàn giao] thay vì [Tiếp theo: Xác nhận →] |
| Fallback | Nếu scope ≠ “Toàn bộ” (chọn filter) → wizard 3 bước như bình thường |
| Lý do | Toàn bộ = đã rõ scope, xem preview đủ. Bỏ bước xác nhận riêng giảm 1 click |
Quick Handover — Bước 2 (gộp):
┌────────────────────────────────────────────────────────────────┐
│ STEP: ✅ Chọn nguồn ─── [② Chọn đích & Xác nhận] │
│ PROGRESS: ████████████ 100% │
├────────────────────────────────────────────────────────────────┤
│ NHÂN VIÊN TIẾP NHẬN (ĐÍCH) * │
│ [🔍 Tìm...] │
│ 👤 Trần Thị CSKH · CSKH · Q1 │
│ Hiện có: 30 khách · Sau bàn giao: 75 khách ⚠️ │
├────────────────────────────────────────────────────────────────┤
│ PREVIEW │
│ TRƯỚC │ SAU │
│ Owner: Nguyễn Sale │ Owner: Trần CSKH │
│ Khách: 45 │ Khách: 75 (+45) │
│ Có nợ: 12 khách │ Có nợ: +12 │
│ Tổng nợ: 85tr │ Tổng nợ thêm: 85tr │
├────────────────────────────────────────────────────────────────┤
│ TÓM TẮT │
│ 📤 Từ: Nguyễn Văn Sale · 📥 Đến: Trần Thị CSKH │
│ 📅 Hiệu lực: 01/03/2026 · 👥 45 khách · 💰 85.000.000đ │
├────────────────────────────────────────────────────────────────┤
│ CAM KẾT (bắt buộc tick đủ 2) │
│ ☐ Tôi đã kiểm tra preview thông tin bàn giao * │
│ ☐ Tôi hiểu có thể rollback toàn bộ trong 24h * │
│ GHI CHÚ: [Lý do bàn giao...] │
├────────────────────────────────────────────────────────────────┤
│ [← Quay lại] [Xác nhận bàn giao] │
└────────────────────────────────────────────────────────────────┘SCR-06 — Bàn giao khách — Bước 2: Chọn người tiếp nhận & xem trước
Layout
┌────────────────────────────────────────────────────────────────┐
│ STEP: ✅ Chọn nguồn ─── [② Chọn đích & preview] ─── ③ Xác nhận│
│ PROGRESS: ████████░░░░ 66% │
├────────────────────────────────────────────────────────────────┤
│ NHÂN VIÊN TIẾP NHẬN (ĐÍCH) * │
│ [🔍 Tìm...] │
│ 👤 Trần Thị CSKH · CSKH · Q1 │
│ Hiện có: 30 khách · Sau bàn giao: 75 khách ⚠️ │
│ ⚠️ Số khách sau bàn giao cao (75). Cân nhắc chia nhỏ. │
├────────────────────────────────────────────────────────────────┤
│ PREVIEW BEFORE / AFTER (Bắt buộc xem trước khi tiếp theo) │
│ TRƯỚC │ SAU │
│ Owner: Nguyễn Sale │ Owner: Trần CSKH │
│ Khách: 45 │ Khách: 75 (+45) │
│ Có nợ: 12 khách │ Có nợ: +12 │
│ Tổng nợ: 85tr │ Tổng nợ thêm: 85tr │
│ Alert: Nguyễn Sale │ Alert: Trần CSKH (từ 01/03) │
├────────────────────────────────────────────────────────────────┤
│ DANH SÁCH KHÁCH SẼ CHUYỂN │
│ [🔍 Tìm] [Nhóm nợ ▼] │
│ # Khách Tiền nợ Quá hạn Nhóm nợ │
│ 1 Nguyễn A 5.000.000đ 95 ngày ≥90 🔴 │
│ 2 Trần B 3.200.000đ 45 ngày 30-59 🟡 │
├────────────────────────────────────────────────────────────────┤
│ [← Quay lại] [Tiếp theo: Xác nhận →] │
└────────────────────────────────────────────────────────────────┘SCR-07 — Bàn giao khách — Bước 3: Kiểm tra & xác nhận
Layout
┌────────────────────────────────────────────────────────────────┐
│ STEP: ✅ Chọn nguồn ─── ✅ Chọn đích ─── [③ Xác nhận] │
│ PROGRESS: ████████████ 100% │
├────────────────────────────────────────────────────────────────┤
│ TÓM TẮT BÀN GIAO │
│ 📤 Từ: Nguyễn Văn Sale (Sale · Q1) │
│ 📥 Đến: Trần Thị CSKH (CSKH · Q1) │
│ 📅 Hiệu lực: 01/03/2026 │
│ 👥 Số khách: 45 khách │
│ 💰 Tổng nợ: 85,000,000 đ │
├────────────────────────────────────────────────────────────────┤
│ CAM KẾT (bắt buộc tick đủ 2 mới bật nút Xác nhận — DEC-UX-008)│
│ ☐ Tôi đã kiểm tra danh sách và xem preview * │
│ ☐ Tôi hiểu có thể rollback toàn bộ trong 24h * │
├────────────────────────────────────────────────────────────────┤
│ GHI CHÚ (tuỳ chọn) │
│ [Lý do bàn giao / ghi chú thêm...] │
├────────────────────────────────────────────────────────────────┤
│ ⚠️ Sau khi xác nhận: │
│ • Trần Thị CSKH nhận cảnh báo nợ từ 01/03/2026 │
│ • Nguyễn Văn Sale ngừng nhận cảnh báo nợ này │
│ • Audit trail lưu vĩnh viễn, không thể xóa │
├────────────────────────────────────────────────────────────────┤
│ [← Quay lại] [Xác nhận bàn giao] ← disabled khi chưa tick│
└────────────────────────────────────────────────────────────────┘SCR-08 — Kết quả bàn giao & lịch sử thao tác
Layout — Thành công, trong 24h
┌────────────────────────────────────────────────────────────────┐
│ ✅ BÀN GIAO THÀNH CÔNG │
│ Mã: HO-2026-0224-001 [📋 Sao chép] │
├────────────────────────────────────────────────────────────────┤
│ Từ: Nguyễn Văn Sale → Đến: Trần Thị CSKH │
│ Hiệu lực: 01/03/2026 · Khách: 45 · Tổng nợ: 85.000.000đ │
├────────────────────────────────────────────────────────────────┤
│ AUDIT TIMELINE (immutable) │
│ ● 24/02 09:15 — Admin Lê: Tạo handover HO-2026-0224-001 │
│ ● 24/02 09:15 — System: 45 khách chuyển thành công │
│ ● 24/02 09:16 — System: Noti gửi cho Trần Thị CSKH │
├────────────────────────────────────────────────────────────────┤
│ ROLLBACK — Còn 18 giờ 45 phút │
│ ⚠️ Có thể hoàn tác toàn bộ trước 09:15 ngày 25/02/2026 │
│ Lưu ý: Chỉ rollback toàn bộ 45 khách, không hỗ trợ partial │
│ [↩ Hoàn tác bàn giao này] │
├────────────────────────────────────────────────────────────────┤
│ [← Về Dashboard] [Xem danh sách 45 khách đã chuyển] │
└────────────────────────────────────────────────────────────────┘Layout — Sau 24h
│ ROLLBACK — Đã hết hạn │
│ 🔒 Thời gian hoàn tác đã hết (09:15 ngày 25/02/2026) │
│ Liên hệ Admin nếu cần xử lý thủ công │Popup Xác nhận Rollback
┌────────────────────────────────────────────┐
│ ↩ XÁC NHẬN HOÀN TÁC — HO-2026-0224-001 │
├────────────────────────────────────────────┤
│ • 45 khách trở về: Nguyễn Văn Sale │
│ • Alert nợ gửi về Sale Nguyễn │
│ • Thao tác không thể hoàn tác lần nữa │
│ Lý do hoàn tác *: [__________________] │
├────────────────────────────────────────────┤
│ [Hủy] [Xác nhận hoàn tác] │
└────────────────────────────────────────────┘Platform: iOS & Android | Screen chuẩn: 390px (iPhone 14) Scope MVP: Xem KPI + Xem/xử lý nợ cơ bản + Nhận push notification Handover Wizard: Pending PD-007
Mobile Scope Summary
Ref: DEC-012 — Mobile scope: Diva Partner App, chỉ xem + hành động cơ bản. Nhân viên cần theo dõi mọi lúc.
| Tính năng | MVP | Ghi chú |
|---|---|---|
| Xem KPI cá nhân (MOB-01) | ✅ | — |
| Xem danh sách nợ quá hạn (MOB-02) | ✅ | — |
| Xem chi tiết khách nợ (MOB-03) | ✅ | — |
| Gọi điện từ app | ✅ | Native phone app |
| Đánh dấu Đã liên hệ | ✅ | — |
| Tạo lịch nhắc (MOB-04) | ✅ | — |
| Nhận push notification (MOB-05) | ✅ | FCM (DEC-018) |
| Drill-down tư vấn | ❌ Phase 2 | Quá phức tạp cho màn nhỏ |
| Cấu hình ngưỡng cảnh báo | ❌ | Chỉ web |
| Handover Wizard | ❌ Phase 2 | PD-007 resolved → Phase 2 |
| Export XLSX | ❌ | PD-009 resolved → mobile chỉ xem (DEC-019) |
| Xem report của người khác | ❌ | Mobile chỉ xem data cá nhân |
MOB-01 — Quản lý hiệu suất (mobile overview)
Layout (390px)
┌─────────────────────────────────────┐
│ ← Quản lý hiệu suất │
│─────────────────────────────────────│
│ [Hiệu suất tư vấn] [Công nợ] │
│─────────────────────────────────────│
│ [🔍 Tìm kiếm] [⚙] │
│─────────────────────────────────────│
│ ┌───────────────────────────────┐ │
│ │ ⚠ Ưu tiên xử lý nợ quá hạn │ │
│ │ Nợ quá hạn: 85.000.000đ │ │
│ │ 4 khách nợ ≥90 ngày │ │
│ │ 3 khách nợ 60-80 ngày [→] │ │
│ └───────────────────────────────┘ │
│─────────────────────────────────────│
│ KHI TAB = "Hiệu suất tư vấn" │
│ ┌──────────────┬──────────────┐ │
│ │ Khách đã TV │ Đã mua kỳ │ │
│ │ 67 khách │ 45 khách │ │
│ └──────────────┴──────────────┘ │
│ ┌──────────────┬──────────────┐ │
│ │ Lượt tư vấn │ Tỷ lệ CĐ │ │
│ │ 128 lượt │ 67.2% │ │
│ │ │ ₍45/67 khách₎│ │
│ └──────────────┴──────────────┘ │
│ ┌───────────────────────────────┐ │
│ │ Doanh thu ròng │ │
│ │ 125.000.000đ │ │
│ │ ₍Đã thu 62M (49,6%) · Nợ 63M₎ │ │
│ └───────────────────────────────┘ │
│─────────────────────────────────────│
│ Danh sách hiệu suất tư vấn │
│ ┌─────────────────────────────────┐ │
│ │ Nguyễn Trần Nguyên [Đã mua]│ │
│ │ 0123456789 │ │
│ │ Tham gia: 30% Ngày TV:20/02/26 │ │
│ │ Gần nhất: 09:00 20/02/2026 │ │
│ │ [DV-1][DV-2] │ │
│ └─────────────────────────────────┘ │
│ [Tải thêm...] │
└─────────────────────────────────────┘Sub-label KPI mobile (P1 — TASK-022): Mỗi KPI card hiện sub-label dưới giá trị chính (font 12px, color #757575). Ký hiệu ₍...₎ trong wireframe = sub-label. Khi giá trị = N/A → sub-label = "Chưa đủ dữ liệu". Spec sub-label chi tiết giống SCR-01 Block A/B (xem section Sub-label rules).
Sub-label rule riêng cho card "Doanh thu ròng" mobile (Ref BUG-PROD-006 — Option 1 LOCKED 2026-05-15):
- Lý do: Vai trò user trên mobile chính = Sale CSKH làm việc thu hồi nợ — họ cần thông tin vận hành (đã thu / % thu / còn nợ), không phải warning wording như web.
- Format sub-label:
Đã thu {collected_short} ({collection_rate}%) · Nợ {outstanding_short}collected_short=collected_amount_in_periodrút gọn (M/Tỷ) — VD:62.000.000đ→62M,1.500.000.000đ→1,5Tcollection_rate=collected_amount_in_period / actual_revenue * 100— định dạng 1 chữ số thập phân với dấu,(VD:49,6%)outstanding_short=total_debt_amountrút gọn cùng quy tắc- Dấu phân tách 3 chỉ số:
·(interpunct, có space 2 bên)
- Edge cases:
actual_revenue = 0(chưa có booking trong kỳ) → sub-label ="Chưa có doanh thu trong kỳ"collected_amount_in_period = 0(booking nhưng chưa thu) → sub-label =Đã thu 0đ (0%) · Nợ {outstanding}total_debt_amount = 0(đã thu hết) → sub-label =Đã thu {collected} (100%) · Đã tất toán(badge xanh)
- Format số tiền primary:
actual_revenuehiển thị VND đầy đủ + suffixđ(Ref rule line 858 + BUG-PROD-002 LOCKED). KHÔNG dùng "Triệu" / "Tỷ" cho primary value. - Khác biệt vs Web (SCR-01 line 866): Web sub-label =
"≠ tiền thực thu"(DEC-032 LOCKED — wording cảnh báo audit-friendly). Mobile sub-label = 3 chỉ số vận hành (thông tin dày). Khác nhau intentional vì target audience khác (Manager/BOD audit ở web, Sale CSKH operation ở mobile).
Header & tabs mobile:
- Tiêu đề màn:
Quản lý hiệu suất - Có 2 tab nội dung:
Hiệu suất tư vấn/Công nợ - Tab
Hiệu suất tư vấnlà default khi mở màn - Chuyển tab không reset ô tìm kiếm; mỗi tab giữ state local riêng nếu cần
Khi tab = Công nợ (mobile):
- Hiển thị
Ưu tiên xử lý nợ quá hạndạng card strip (dọc). - Hiển thị Block
Chỉ số Công nợcùng bộ KPI như web:Khách đang nợ,Khách nợ quy đổi,Tiền nợ quy đổi,Tổng tiền nợ,Tỷ lệ thu nợ,TB ngày thu nợ,Đã thu trong kỳ, headlineTỷ lệ nợ/doanh thu. - Hiển thị
Phân loại nhóm nợvà danh sách card khách nợ ngay trong cùng tab. - Mỗi card khách:
Tên,SĐT,Tổng nợ,Quá hạn,% tham gia,Đã gọi...,Phụ trách chính, CTA[Gọi ngay] [Tạo lịch].
Filter mobile: Sticky search bar + nút mở bottom sheet filter. Trên tab Hiệu suất tư vấn, filter sheet có thể chứa thời gian / vai trò / chi nhánh theo quyền.
Danh sách hiệu suất tư vấn (mobile card list):
- Dùng
cardthay cho table row của web. - Mỗi card hiển thị:
Tên+ badge trạng thái →SĐT→Tham gia+Ngày tư vấn→Gần nhất: HH:mm DD/MM/YYYY→ danh sách dịch vụ rút gọn. Gần nhấtlà metadata phụ, thay cho wordingĐã gọi ...để giữ đúng ngữ cảnh tư vấn.- Badge trạng thái dùng đúng 2 trạng thái:
Đã mua/Chưa mua. Tải thêmhoặc phân trang lazy-load ở cuối danh sách.
MOB-02 — Danh sách khách nợ quá hạn
Layout
┌─────────────────────────────────────┐
│ ← Danh sách nợ quá hạn │
│─────────────────────────────────────│
│ [☑ Sale] [☑ Telesale] [☑ CSKH] │
│─────────────────────────────────────│
│ 🔴 5 khách nợ ≥90 ngày cần ưu tiên │
│ 23 khách · 85.000.000đ tổng nợ │
│─────────────────────────────────────│
│ [🔍 Tìm...] [⚙ Bộ lọc] │
│─────────────────────────────────────│
│ ┌─────────────────────────────────┐ │
│ │ Nguyễn Văn A [≥90 🔴] │ │
│ │ 0901 234 567 │ │
│ │ Tổng nợ: 5.000.000đ · 95 ngày │ │
│ │ Tham gia: 30% │ │
│ │ Phụ trách chính: Nguyễn Sale │ │
│ │ Đã gọi 2 lần · 09:00 20/02/2026│ │
│ │ [Gọi ngay] [Tạo lịch] [⋯] │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Trần Thị B [30-59 🟡] │ │
│ │ 0901 888 999 │ │
│ │ Tổng nợ: 3.200.000đ · 45 ngày │ │
│ │ Tham gia: 30% │ │
│ │ Phụ trách chính: Nguyễn Sale │ │
│ │ Đã gọi 1 lần · 14:30 18/02/2026│ │
│ │ [Gọi ngay] [Tạo lịch] [⋯] │ │
│ └─────────────────────────────────┘ │
│ [Tải thêm...] │
│─────────────────────────────────────│
│ [🏠] [📋 Nợ] [🔔] [👤] │
└─────────────────────────────────────┘Sắp xếp mặc định: overdue_days DESC — khách nợ lâu nhất lên đầu, sau đó outstanding_amount DESC. Giống logic web SCR-03.
Card layout (Mobile responsive):
- Card dùng thay cho table row của web. Không cố giữ parity theo cột.
- Mỗi card hiển thị theo thứ tự ưu tiên:
Tên + badge→SĐT→Tổng nợ + Quá hạn→% tham gia→Phụ trách chính→Đã gọi ... Phụ trách chínhhiển thị dạng text line, không dùng box nền riêng để tránh tăng chiều cao card.Đã gọi {call_count} lần · {last_call_time}là metadata phụ, nằm ngay dướiPhụ trách chính.- Luôn hiển thị dòng
Phụ trách chínhđể tránh mơ hồ khi 1 khách có nhiều người tham gia. - CTA hiển thị trực tiếp dưới card:
[Gọi ngay] [Tạo lịch] [⋯] - Nút
[⋯]gom action phụ nhưĐã liên hệ,Xem chi tiết,Mở CRMnếu cần. - Button sizing: min 44×44px tap target (iOS HIG / Material Design)
Quick filter chips (MOB-02):
- Hiển thị 1 hàng chip ngay trước danh sách card để lọc nhanh theo
Nhóm nợ. - Options:
Tất cả,≥90,60–89,30–59,0–29. - Chip
Xử lý ngay(nếu dùng label này trên UI) phải map sangNhóm nợ = ≥90để tránh mơ hồ. - Khi chip active: chỉ filter working set của danh sách card (giống web), không làm thay đổi KPI/aging ở phần tổng quan nếu chúng đang hiển thị trên cùng màn.
Behavior nút Gọi mobile:
- Tap → Mở phone app native với số khách
- Sau khi gọi, quay lại → hỏi "Đã liên hệ thành công?" [Có] [Không]
- [Có] → tự toggle Đã LH
MOB-03 — Thông tin chi tiết khách nợ & lịch nhắc
Layout
┌─────────────────────────────────────┐
│ ← Nguyễn Văn A [≥90 🔴] │
│─────────────────────────────────────│
│ Tổng nợ: 6.500.000đ │ ← STICKY SUMMARY
│ Quá hạn cao nhất: 95 ngày │ ← (cố định khi scroll)
│ Owner: Tôi │
│ SĐT: 0901 234 567 [📞 Gọi ngay]│
│═════════════════════════════════════│
│ ▼ DỊCH VỤ NỢ (3DV · 2đơn) 6,500k │ ← collapsible
│ │
│ 📋 ĐH-001 · 15/01/2026 │
│ ├ Cắt tóc 2.000.000đ 47d 🟡 │
│ └ Nhuộm 3.000.000đ 47d 🟡 │
│ │
│ 📋 ĐH-002 · 20/02/2026 │
│ └ Massage 1.500.000đ 5d 🟢 │
│─────────────────────────────────────│
│ ▼ LỊCH SỬ LIÊN HỆ (2) │ ← collapsible
│ 20/02 10:15 — Tôi: Đã LH, hẹn TT │
│ 15/02 09:00 — Tôi: Gọi, ko nghe │
│ [+ Thêm ghi chú liên hệ] │
│─────────────────────────────────────│
│ ▼ LỊCH NHẮC (3 · 1🔴 quá hạn) [+] │ ← collapsible, luôn mở
│ ┌─────────────────────────────────┐│
│ │ 🔴 02/03 09:00 — Gọi nhắc TT ││
│ │ Quá hạn · Ghi chú: Hẹn cuối ││
│ │ [✅ Xong] [❌ Hủy] ││
│ └─────────────────────────────────┘│
│ ┌─────────────────────────────────┐│
│ │ ✅ 25/02 10:00 — Nhắc Zalo ││
│ │ Đã xong lúc 25/02 10:32 ││
│ └─────────────────────────────────┘│
│ ┌─────────────────────────────────┐│
│ │ ✅ 20/02 09:00 — Gọi nhắc #1 ││
│ │ Đã xong lúc 20/02 09:15 ││
│ └─────────────────────────────────┘│
│─────────────────────────────────────│
│ [📞 Gọi] [📅 Lịch] [→ CRM] │
└─────────────────────────────────────┘Section DỊCH VỤ NỢ — Spec:
| Thành phần | Mô tả |
|---|---|
| Nhóm theo | Đơn hàng (order_code + ngày đơn) |
| Sắp xếp đơn | Đơn cũ nhất trước (ngày đơn ASC) |
| Sắp xếp DV trong đơn | Theo số tiền nợ DESC |
| Mỗi dòng DV hiển thị | Tên dịch vụ + số tiền nợ + số ngày quá hạn + badge aging màu |
| Aging mỗi dòng | Tính riêng từ debt_due_date của order_service đó |
| Badge trên header | Lấy aging bucket cao nhất trong tất cả dòng DV nợ |
| Dòng tổng | Hiển thị tổng tiền nợ tất cả DV |
| Collapse | Nếu > 5 đơn → collapse từ đơn thứ 4, nút "Xem thêm N đơn" |
Spec Sticky Summary Card (MOB-03)
| Thành phần | Mô tả |
|---|---|
| Khi nào sticky | Khi user scroll xuống quá phần header (tên + thông tin chính) |
| Nội dung sticky | 1 dòng compact: Nguyễn Văn A · 6.500.000đ · [≥90 🔴] + nút [📞] |
| Chiều cao | 48px, background #FFFFFF, shadow bottom 0 2px 4px rgba(0,0,0,0.1) |
| Khi scroll lên | Sticky ẩn (header gốc hiện lại). Transition: slide up 200ms |
| Lý do | User scroll 5+ sections dài, mất context khách đang xem. Sticky giữ thông tin cốt lõi |
Spec Collapsible Sections (MOB-03)
| Section | Header | Default state | Badge count |
|---|---|---|---|
| DỊCH VỤ NỢ | ▼ Dịch vụ nợ (3DV · 2đơn) 6,500k | Collapsed nếu > 2 đơn. Expanded nếu ≤ 2 đơn | Tổng tiền nợ |
| LỊCH SỬ LIÊN HỆ | ▼ Lịch sử liên hệ (2) | Collapsed mặc định | Số lần LH |
| LỊCH NHẮC FOLLOW-UP | ▼ Lịch nhắc (3 · 1🔴 quá hạn) [+] | Expanded mặc định (vì là action chính) | Số nhắc + badge đỏ quá hạn |
Behavior:
- Tap header
▼/▶→ toggle expand/collapse. Animation: slide 200ms - Badge count hiện ngay trên header → user biết số lượng mà không cần expand
- Section "Lịch nhắc" luôn mở nếu có ≥1 nhắc quá hạn (auto-expand)
- Tổng chiều dài trang giảm ~50% khi "Dịch vụ nợ" + "Lịch sử" collapsed
MOB-04 — Tạo lịch nhắc chăm sóc (bottom sheet)
Layout
┌─────────────────────────────────────┐
│ ▬ (drag handle) │
│ Tạo lịch nhắc — Nguyễn Văn A │
│ Nợ: 6.500.000đ · 3DV · 2đơn │
│─────────────────────────────────────│
│ Ngày nhắc * [DD/MM/YYYY] [HH:MM] │
│ Nội dung * [Gọi nhắc thanh toán] │
│ Ghi chú [Thêm ghi chú...] │
│─────────────────────────────────────│
│ [Hủy] [Tạo lịch ✓] │
└─────────────────────────────────────┘Lưu ý: Bottom sheet mobile chỉ hiện dòng tổng hợp nợ (tổng tiền + số DV + số đơn), không expand chi tiết. Chi tiết xem ở MOB-03 phía trên.
MOB-05 — Trung tâm thông báo
Layout
┌─────────────────────────────────────┐
│ ← Thông báo │
│─────────────────────────────────────│
│ HÔM NAY │
│ ┌─────────────────────────────────┐ │
│ │ ⚠️ Nợ quá hạn · 07:01 │ │
│ │ Bạn có 23 khách nợ quá 30 ngày │ │
│ │ → Xem danh sách │ │
│ └─────────────────────────────────┘ │
│ HÔM QUA │
│ ┌─────────────────────────────────┐ │
│ │ ✅ Bàn giao · 23/02 09:15 │ │
│ │ 45 khách từ Nguyễn Sale │ │
│ │ đã chuyển về bạn (hiệu lực 1/3)│ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘Deeplink behavior:
- Tap NTF-DEBT-DAILY-001 → MOB-02 (Danh sách nợ)
- Tap NTF-HANDOVER-TARGET → MOB-01 (Home, refresh KPI)
- App chưa mở → Mở app → navigate thẳng đến màn hình đúng
Deep Link URL Scheme
| Màn hình | Custom Scheme | Universal Link (fallback) | Ghi chú |
|---|---|---|---|
| MOB-01 Home | divapartner://home | https://partner.diva.vn/home | — |
| MOB-02 Nợ quá hạn | divapartner://debt/overdue | https://partner.diva.vn/debt/overdue | Mở danh sách nợ theo filter mặc định của user |
| MOB-02 filter bucket | divapartner://debt/overdue?bucket=>=90 | https://partner.diva.vn/debt/overdue?bucket=>=90 | Filter preset |
| MOB-03 Chi tiết khách | divapartner://debt/customer/{customer_id} | https://partner.diva.vn/debt/customer/{customer_id} | — |
| MOB-05 Notification | divapartner://notifications | https://partner.diva.vn/notifications | — |
B-Export) Export Spec — XLSX Only
Export SCR-01 — Dashboard / Danh sách
Trigger: Nút [Xuất XLSX] góc trên phải Phạm vi: Theo bộ lọc đang active + quyền user Row cap: 200,000 rows/job — vượt → chunking tự động Tên file: bao-cao-cong-no_{branch_code}_{YYYYMMDD}_{HHmm}.xlsxSheet: "Báo cáo công nợ" | Header: bold, background #E3F2FD | Freeze: Row 1 + Col 1-2
| # | Header | Nguồn | Format |
|---|---|---|---|
| 1 | Mã khách hàng | customer_code | Text |
| 2 | Tên khách hàng | customer_name | Text |
| 3 | Số điện thoại | customer_phone | Text (không format) |
| 4 | Người phụ trách | debt_owner_name | Text |
| 5 | Chi nhánh | branch_name | Text |
| 6 | Dịch vụ | service_names | Text (phân cách ", ") |
| 7 | % Hoa hồng | commission_percent | 0.00% |
| 8 | Ngày tư vấn gần nhất | last_consultation_date | DD/MM/YYYY |
| 9 | Số tiền nợ (đ) | outstanding_amount | #,### |
| 10 | Quá hạn (ngày) | overdue_days | Integer (0 nếu chưa quá hạn) |
| 11 | Nhóm nợ | aging_bucket | "0-29" / "30-59" / "60-89" / "≥90" / "—" |
| 12 | Ngày bắt đầu nợ | debt_start_date | DD/MM/YYYY |
| 13 | Hạn thanh toán | debt_due_date | DD/MM/YYYY |
Export SCR-03 — Nợ Quá Hạn
Tên file: no-qua-han_{branch_code}_{YYYYMMDD}_{HHmm}.xlsxSheet: "Nợ quá hạn"
Bao gồm tất cả 13 cột SCR-01, cộng thêm:
| # | Header | Nguồn | Format |
|---|---|---|---|
| 14 | Đã liên hệ | is_contacted | "Có" / "Không" |
| 15 | Lần liên hệ cuối | last_contacted_at | DD/MM/YYYY HH:MM |
| 16 | Người liên hệ | contacted_by_name | Text |
| 17 | Ghi chú liên hệ | contact_note | Text |
| 18 | % tham gia | participation_percent | 0.00% |
Export SCR-08 — Audit Handover
Quyền: Admin / HR Ops only Tên file: audit-ban-giao_{YYYYMMDD}_{HHmm}.xlsxSheet: "Audit bàn giao"
| Header | Nguồn | Format |
|---|---|---|
| Mã bàn giao | handover_id | Text |
| Từ nhân viên | source_owner_name | Text |
| Đến nhân viên | target_owner_name | Text |
| Ngày hiệu lực | effective_date | DD/MM/YYYY |
| Số khách | customer_count | Integer |
| Tổng nợ (đ) | debt_amount | #,### |
| Người thực hiện | created_by_name | Text |
| Thời gian thực hiện | created_at | DD/MM/YYYY HH:MM:SS |
| Đã rollback | is_rolled_back | "Có" / "Không" |
| Thời gian rollback | rollback_at | DD/MM/YYYY HH:MM:SS |
| Người rollback | rollback_by_name | Text |
| Lý do rollback | rollback_reason | Text |
B-EdgeCases) Edge Cases
Handover Edge Cases
| Case | Hành vi | Error message |
|---|---|---|
| Nguồn = Đích | Block ngay Bước 1 | "Người giao và người nhận không được trùng nhau" |
| Nguồn không có khách | Block Bước 1 | "Nhân viên này không có khách để bàn giao" |
| Nguồn không active | Block Bước 1 | "Nhân viên này không còn hoạt động" |
| Đích không active | Block Bước 2 | "Nhân viên tiếp nhận không còn hoạt động. Chọn người khác." |
| Đang có handover pending cho nguồn | Block Bước 1 | "Đang có bàn giao chưa hoàn tất cho nhân viên này" |
| Filter lọc 0 khách | Block "Tiếp theo" | "Bộ lọc không tìm thấy khách nào. Thử điều kiện khác." |
| Rollback sau 24h | Block nút rollback | "Thời gian hoàn tác đã hết. Liên hệ Admin." |
| Rollback partial | Không hỗ trợ MVP (DEC-015) | "Chỉ hỗ trợ hoàn tác toàn bộ. Để chuyển lại 1 phần, tạo handover mới." |
| Khách chốt đơn mới trong 24h sau handover | Rollback vẫn thực hiện được | Đơn mới giữ nguyên owner mới |
| 2 user cùng rollback 1 handover | First write wins | Người thứ 2 nhận: "Bàn giao này đã được hoàn tác" |
Followup Task Edge Cases
| Case | Hành vi | Error message |
|---|---|---|
| Tạo lịch nhắc cho khách đã tất toán nợ | Cho phép tạo (vẫn cần follow-up quan hệ) | — |
| Đánh dấu Đã xong task đã Đã xong | Block | "Lịch nhắc này đã hoàn thành" |
| Hủy task đã Đã xong | Block | "Không thể hủy lịch nhắc đã hoàn thành" |
| remind_at trong quá khứ khi tạo | Block | "Ngày nhắc phải từ bây giờ trở đi" |
| Nhiều lịch nhắc cùng khách cùng giờ | Cho phép (user tự quản lý) | — |
| Handover khách sang người khác → lịch nhắc cũ | Giữ nguyên assigned_to cũ. User tạo mới nếu cần | Badge warning khi drawer mở |
| Scheduler followup reminder lỗi | Retry lần sau (5 phút). Không alert Ops (noti nhẹ) | Ghi log followup_remind_failed |
| User xóa lịch nhắc | Soft delete (deleted_at). Không hiển thị trong danh sách | — |
Scheduler Edge Cases
| Case | Hành vi |
|---|---|
| Owner resolution không ra ai (3 cấp đều null) | Bỏ qua, ghi log owner_resolution_failed, tăng metric |
| Job retry trong ngày | Dedupe key (owner, customer, current_bucket, date) → lần 2 skip, không gửi trùng |
| Cross-day re-trigger (Ref DEC-026 rule 1) | Khách vẫn quá hạn ngày sau → gửi noti mới (dedupe key date khác → KHÔNG skip). User nhận noti mỗi ngày |
| Bucket transition (Ref DEC-026 rule 2) | Khách chuyển bucket lên cao hơn → gửi NTF-DEBT-BUCKET-ESCALATE riêng + vẫn gửi NTF-DEBT-DAILY-001 bucket mới. User nhận 2 noti trong ngày |
| Partial payment (Ref DEC-026 rule 3) | Khách thanh toán 1 phần, còn nợ → vẫn nằm list. total_debt payload = SUM(remaining) còn lại. KHÔNG reset trạng thái |
| Settled drop (Ref DEC-026 rule 4) | Khách settled (paid = total) trong ngày X → ngày X+1 trở đi không nhận noti nữa. Nếu phát sinh nợ mới sau đó → reset chu kỳ noti từ đầu |
| Trigger at exact threshold (Ref DEC-025) | overdue_days == threshold_days → CÓ gửi (inclusive >=). Test seed: overdue = 30, threshold = 30 |
| Khách vừa handover trong ngày → scheduler chạy sau | Gửi cho owner mới (tại thời điểm scheduler chạy), cũ không nhận |
| threshold_days = 0 (misconfiguration) | Validate khi save: min = 1. Không cho lưu. |
| Scheduler fail hoàn toàn | Alert Ops ≤ 5 phút (NFR-006), retry auto, ghi log |
| Push mobile delivery thất bại | Ghi log, không retry (fallback: in-app web) |
KPI Edge Cases
| Case | Hành vi |
|---|---|
| unique_customer_count = 0 | conversion_rate = N/A (không phải 0%) |
| net_revenue = 0 (Ref FORMULA-003) | debt_ratio = N/A, hiển thị "—" |
| Tất cả payments vào ngày phát sinh nợ | avg_recovery_days_weighted = 0 |
| Khách có nhiều đơn, 1 đơn hoàn trả | actual_revenue của đơn đó đã pre-computed (đã trừ refund). KHÔNG cần thêm logic FE (Ref DEC-022) |
| Khách có nhiều đơn ở nhiều bucket (Ref FORMULA-006B + DEC-023) | Customer-level rollup: gán KH vào bucket cao nhất (max(overdue_days)). Vd: KH có đơn 15d + 95d → bucket = very_high_risk. Biểu đồ phân loại + bảng count: 1 KH = 1 unit. Tổng tiền nợ trong bucket = SUM remaining của tất cả đơn KH gán bucket đó |
| Filter chọn ngày tương lai | Empty state "Chưa có dữ liệu cho kỳ này" |
| 1 dịch vụ có 2 Sale 50/50, cùng % và cùng created_at | Tie-break: user_id ASC |
| debt_start_date null | Skip khỏi aging calculation, ghi log debt_date_missing |
| Manager/BOD view các KPI quy đổi (Ref DEC-024 + A12.4) | ẨN cột "Khách nợ quy đổi" / "Tiền nợ quy đổi" / "% Của tôi" — thay bằng raw aggregate trong branch/system scope. API trả view_role: manager/bod với raw values |
| Manager kiêm Sale (vừa quản lý vừa được phân khách) | UI có pill switcher giữa "View của tôi" (individual quy đổi) và "View team" (raw aggregate). Mặc định: view team |
| BOD chưa có branch assigned trong allowed list | Trả empty + banner "Hệ thống chưa có data trong scope của bạn" |
| Khách có DV null trong DB (cột Dịch vụ SCR-02) | Hiển thị "Dịch vụ #{order_service_id}". Nếu lượt TV không có DV nào → cell "—" |
| Khách có nhiều DV cùng lượt TV (Ref FORMULA-007) | Hiển thị "{tên DV rep} (+N-1 DV khác)". Tooltip liệt kê tất cả |
Mobile Edge Cases
| Case | Hành vi |
|---|---|
| Mất kết nối | Hiển thị cache + banner "Đang offline, dữ liệu có thể chưa mới nhất" (xem chi tiết offline bên dưới) |
| Push notification tap khi app chưa mở | Mở app → navigate đúng màn hình (deep link scheme) |
| Gọi xong, quay lại app sau > 5 phút | Vẫn hỏi "Đã liên hệ thành công?" (không tự timeout) |
| Số điện thoại khách null | Nút [📞 Gọi] disabled + tooltip "Chưa có số điện thoại" |
| FCM token expired / revoked | Tự động refresh token khi app mở lần tiếp; nếu fail → fallback in-app web |
| User tắt push notification ở OS level | In-app badge "Bạn đã tắt thông báo. Bật lại trong Cài đặt." |
Mobile Offline Behavior (Chi tiết)
| Dữ liệu | Cache TTL | Hành vi offline | Sync khi có mạng |
|---|---|---|---|
| KPI cá nhân (MOB-01) | 30 phút | Hiển thị data cached + badge timestamp "Cập nhật lúc HH:MM" | Auto refresh |
| Danh sách nợ (MOB-02) | 15 phút | Hiển thị list cached, CTA gọi vẫn hoạt động (native phone) | Auto refresh |
| Chi tiết khách (MOB-03) | 15 phút | Hiển thị cached, nút Gọi hoạt động | Auto refresh |
| Đánh dấu Đã LH | — | Queue locally (optimistic UI: hiển thị xanh ngay) | Sync khi có mạng, nếu fail → revert UI + toast lỗi |
| Tạo lịch nhắc | — | Queue locally (hiển thị pending indicator) | Sync khi có mạng |
| Banner offline | — | "Đang offline — dữ liệu có thể chưa mới nhất" (sticky top) | Tự ẩn khi reconnect |
B-Onboarding) First-time Onboarding Tour
Hướng dẫn 3 bước cho user mới, chỉ hiện lần đầu truy cập module. Giảm thời gian onboard và support ticket.
Trigger
| Điều kiện | Mô tả |
|---|---|
| Khi nào hiện | Lần đầu user mở SCR-01 (Dashboard) VÀ localStorage key debt_onboarding_completed chưa tồn tại |
| Không hiện | User đã hoàn thành tour HOẶC đã bấm "Bỏ qua" |
| Reset | Không tự reset. Admin có thể reset qua setting user nếu cần |
Tour Steps (3 bước)
| Step | Target element | Title | Mô tả | Vị trí tooltip |
|---|---|---|---|---|
| 1/3 | Block B — KPI Nợ | "KPI nợ của bạn" | "Đây là tổng quan nợ bạn đang phụ trách. Số đỏ = nợ quá hạn cần ưu tiên xử lý." | Bottom |
| 2/3 | Aging Bucket Chart (Block C) | "Phân nhóm theo thời gian nợ" | "Biểu đồ chia khách nợ thành 4 nhóm. Click vào nhóm để lọc khu vực xử lý nợ bên dưới; biểu đồ vẫn giữ phân bổ tổng." | Top |
| 3/3 | Nút [📅 Lịch nhắc] trên SCR-01 Action Card (CTA "Xử lý ngay") | "Quản lý lịch nhắc" | "Từ đây bạn có thể xem danh sách nợ cần xử lý. Bấm vào khách để tạo lịch nhắc — hệ thống sẽ gửi notification đúng giờ." | Left |
Lưu ý Step 3: Tour chạy hoàn toàn trên SCR-01 (Dashboard). Step 3 spotlight nút CTA "Xử lý ngay" trên Action Card (cùng trang) thay vì navigate sang SCR-03 — tránh phá vỡ flow tour. User tự navigate sau khi hoàn thành tour.
UI Components
Step 1/3:
┌──────────────────────────────────────────────────┐
│ ┌─ spotlight ──────────────────────────────────┐│
│ │ ░░░░░░ Block B — KPI Nợ ░░░░░░░░░░░░░░░░░░░ ││
│ └──────────────────────────────────────────────┘│
│ ┌─ tooltip (bottom) ──────────────────────────┐ │
│ │ 📍 1/3 — KPI nợ của bạn │ │
│ │ Đây là tổng quan nợ bạn đang phụ trách. │ │
│ │ Số đỏ = nợ quá hạn cần ưu tiên xử lý. │ │
│ │ │ │
│ │ [Bỏ qua] [Tiếp theo →] ● ○ ○ │ │
│ └──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘Behavior
| Thuộc tính | Mô tả |
|---|---|
| Backdrop | Overlay rgba(0,0,0,0.5) + spotlight cutout trên target element |
| Navigation | [Tiếp theo →] / [← Quay lại] (từ step 2). Step 3: [Hoàn thành ✓] |
| Bỏ qua | [Bỏ qua] → confirm dialog "Bạn có thể xem lại hướng dẫn bất kỳ lúc nào từ menu ⓘ" → set debt_onboarding_completed = true |
| Hoàn thành | Step 3 [Hoàn thành ✓] → set debt_onboarding_completed = true + toast "Sẵn sàng! Bạn có thể bắt đầu quản lý công nợ." |
| Dot indicator | ● ○ ○ → ● ● ○ → ● ● ● (hiện step hiện tại) |
| Scroll | Tự scroll target element vào viewport trước khi hiện tooltip |
| Xem lại | Menu ⓘ trên header SCR-01 → "Xem lại hướng dẫn" → reset tour |
| Mobile | Tour giống desktop, tooltip hiện full-width bottom sheet thay vì positioned tooltip |
| i18n keys | debt.onboarding.step1_title: "KPI nợ của bạn" |
debt.onboarding.step1_desc: "Đây là tổng quan nợ…" | |
debt.onboarding.step2_title: "Phân nhóm theo thời gian nợ" | |
debt.onboarding.step2_desc: "Biểu đồ chia khách nợ…" | |
debt.onboarding.step3_title: "Quản lý lịch nhắc" | |
debt.onboarding.step3_desc: "Tạo lịch nhắc để không quên…" | |
debt.onboarding.skip: "Bỏ qua" | |
debt.onboarding.next: "Tiếp theo" | |
debt.onboarding.back: "Quay lại" | |
debt.onboarding.finish: "Hoàn thành" | |
debt.onboarding.toast_done: "Sẵn sàng! Bạn có thể bắt đầu quản lý công nợ." |