Skip to content

UI Spec — Nâng cấp Voucher Management

Version: 1.0 Ngày: 2026-03-16 Tham chiếu: docs/features/voucher-enhancement/prd.md


B1. Screen Map

/cms/voucher-management/
├── voucher/                           # Hiện có
│   ├── /create                        # Hiện có — thêm field min_activation_hours (P1)
│   │                                  #          thêm step Đối tác (P3)
│   ├── /:id                           # Hiện có
│   │   ├── Tab: Tổng quan             # Hiện có
│   │   ├── Tab: Báo cáo              # Hiện có
│   │   ├── Tab: Thống kê NV          # MỚI (P2) — FR-P2-01, FR-P2-02, FR-P2-03
│   │   ├── Tab: Chu kỳ sử dụng      # MỚI (P4) — FR-P4-01 đến FR-P4-05
│   │   ├── Tab: Danh sách KH         # Hiện có
│   │   └── Tab: Lịch sử & Audit      # Hiện có
│   └── /:id/edit                      # Hiện có — thêm field min_activation_hours (P1)
│                                      #          thêm step Đối tác (P3)
├── voucher-campaign/                  # Hiện có (không đổi)
├── override-dashboard/                # MỚI (P1) — FR-P1-04

└── [Settings module]
    └── Voucher Settings               # MỚI (P1) — FR-P1-05, FR-P1-06

[Màn hình tạo ĐH — module ecommerce]
└── Dialog chặn voucher               # MỚI (P1) — FR-P1-02, FR-P1-03

B2. Component Inventory

Phase 1 — Components mới/sửa

ComponentLoạiFileMô tả
MinActivationHoursFieldSửa inlineVoucherConfigForm.tsxInput number trong form tạo chiến dịch
VoucherTimeBlockDialogMớiVoucherTimeBlockDialog.tsxDialog chặn + nút "Yêu cầu duyệt"
VoucherOverrideDashboardMớiVoucherOverrideDashboard.tsxDanh sách override + filter
VoucherSettingsSectionMớiSettings moduleConfig mặc định thời gian chờ + hạn sử dụng

Phase 2 — Components mới

ComponentLoạiFileMô tả
VoucherStaffStatisticsTabMớitabs/VoucherStaffStatisticsTab.tsxTab thống kê NV + drill-down
StaffDrillDownDialogMớiStaffDrillDownDialog.tsxChi tiết voucher per NV

Phase 3 — Components mới

ComponentLoạiFileMô tả
AffiliateAssignStepMớicreate-components/AffiliateAssignStep.tsxWizard step chọn đối tác + quota
AffiliateSourceBadgeMớiOrder creation moduleBadge "Voucher do KOL X phát"

Phase 4 — Components mới

ComponentLoạiFileMô tả
VoucherUsageCycleTabMớitabs/VoucherUsageCycleTab.tsxKPI cards + bar chart + bảng
CycleBucketChartMớiCycleBucketChart.tsxHorizontal bar chart (Chart.js)

B3. User Flows

Flow 1: Tạo chiến dịch với min_activation_hours (P1)

Admin mở /create
  → Step 1: Chọn loại voucher
  → Step 2: Cấu hình (THÊM field "Thời gian chờ tối thiểu")
      └── Input: min_activation_hours (mặc định 24, range 0-720)
      └── Hint: "Voucher phải chờ X giờ sau khi kích hoạt mới được dùng trong ĐH"
  → Step 3: Quota per channel
  → Step 4: Review
  → Step 5: Chọn Manager
  → Step 6: Chọn Đối tác (MỚI — P3, chỉ hiển thị nếu bật)
  → Submit

Flow 2: Tạo ĐH bị chặn bởi voucher time control (P1)

NV tạo ĐH → Chọn voucher → Submit
  → Backend check: activated_at + min_activation_hours > NOW()
  → CHẶN → Hiện VoucherTimeBlockDialog:
      ┌────────────────────────────────────────────┐
      │  ⚠ Voucher chưa đủ thời gian sử dụng     │
      │                                            │
      │  Mã voucher: CDLF42L4SB                   │
      │  Kích hoạt: 15/03/2026 14:00              │
      │  Thời gian chờ: 24 giờ                     │
      │  Còn lại: 22 giờ 15 phút                  │
      │                                            │
      │  Voucher có thể sử dụng từ:               │
      │  16/03/2026 14:00                          │
      │                                            │
      │  [Yêu cầu Manager duyệt]     [Đóng]      │
      └────────────────────────────────────────────┘

  → NV click "Yêu cầu Manager duyệt"
      ┌────────────────────────────────────────────┐
      │  Yêu cầu duyệt sử dụng voucher sớm       │
      │                                            │
      │  Lý do: [Khách VIP           ▼]           │
      │                                            │
      │  Ghi chú: [________________________]      │
      │           [________________________]      │
      │           (Bắt buộc)                       │
      │                                            │
      │  [Xác nhận duyệt]            [Hủy]       │
      └────────────────────────────────────────────┘
      (Chỉ hiện nút "Xác nhận duyệt" nếu user là Manager/Admin)

  → Manager approve → ĐH được tạo thành công

Flow 3: Xem thống kê nhân viên (P2)

Admin mở /voucher/:id → Tab "Thống kê NV"
  → Hiện filter bar (ngày, chi nhánh, loại NV/đối tác)
  → Hiện bảng thống kê
  → Click 1 hàng → Mở StaffDrillDownDialog
  → Click "Xuất Excel" → Download file

Flow 4: Xem chu kỳ sử dụng (P4)

Admin mở /voucher/:id → Tab "Chu kỳ sử dụng"
  → Hiện filter bar (ngày, chi nhánh, nhóm theo)
  → Hiện 4 KPI cards
  → Hiện horizontal bar chart (bucket distribution)
  → Hiện bảng chi tiết (breakdown theo dimension)
  → Chọn 2+ chiến dịch → chart chồng so sánh

B4. Notification Spec

Notifications hiện có (không đổi)

EventTrigger CodeKênhNgười nhận
Voucher kích hoạt (Print)noti_voucher_activated_printIn-appCustomer
Voucher kích hoạt (Offline)noti_voucher_activated_offlineIn-appCustomer
Voucher claimed (Online)noti_voucher_claimed_onlineIn-appCustomer

Notifications mới

EventTrigger CodeKênhNgười nhậnTemplateDedupe RulePhase
Override được tạonoti_voucher_override_createdIn-appBOD, ITLeader"Voucher {voucher_code} được duyệt sử dụng sớm bởi {approved_by_name} tại {branch_name}. Lý do: {reason_note}"1 lần / overrideP1
Voucher manual sắp hết hạn (3 ngày)noti_voucher_manual_expiringIn-app + PushCustomer"Voucher {voucher_name} ({voucher_code}) sẽ hết hạn vào {expired_at}. Hãy sử dụng trước khi quá hạn!"1 lần / voucherP1
Đối tác gần hết quota (>90%)noti_affiliate_quota_warningIn-appAdmin, Campaign Managers"Đối tác {affiliate_name} đã phát {distributed_count}/{quota} voucher chiến dịch {campaign_name}"1 lần / đối tác / chiến dịchP3

Template Variables

VariableMô tảVD
{voucher_code}Mã voucherCDLF42L4SB
{voucher_name}Tên voucher01 Suất chăm sóc da trắng sáng
{expired_at}Ngày hết hạn (dd/MM/yyyy)15/04/2026
{approved_by_name}Tên Manager duyệtNguyễn Văn A
{branch_name}Tên chi nhánhChi nhánh Q.1 HCM
{reason_note}Ghi chú lý do overrideKhách VIP đặc biệt
{affiliate_name}Tên đối tácKOL Hương
{distributed_count}Số đã phát185
{quota}Quota tối đa200
{campaign_name}Tên chiến dịchTết Kim Cương 2026

B5. Permission Matrix

Màn hình hiện có (không đổi)

Màn hình / Hành độngITLeaderITStaffBODAccLeaderAccStaffCSLeaderCSStaffHRLeaderHRStaffTSLeaderTSStaff
Xem danh sách voucher
Tạo/Sửa/Duyệt campaign
Tab Báo cáo / Lịch sử

Màn hình mới — Phase 1

Màn hình / Hành độngITLeaderITStaffBODAccLeaderManager CNStaff
Config min_activation_hours (tạo campaign)
Dialog chặn: Xem thông tin chặn
Dialog chặn: Nút "Yêu cầu duyệt"✅ (Branch)
Override Dashboard: Xem danh sách✅ (Branch)
Override Dashboard: Export
Settings: Config thời gian chờ mặc định
Settings: Config hạn sử dụng manual

Branch scoping: Manager CN chỉ thấy override tại chi nhánh mình quản lý (branch_id = X-Hasura-Branch-Id)

Màn hình mới — Phase 2

Màn hình / Hành độngITLeaderITStaffBODAccLeaderManager CNStaff
Tab Thống kê NV: Xem bảng✅ All✅ All✅ All✅ All✅ Branch
Tab Thống kê NV: Drill-down✅ (Branch)
Tab Thống kê NV: Export Excel
Partner App: Xem thống kê cá nhân✅ (Self)

Self scoping: Staff trên Partner app chỉ thấy data của chính mình (staff_id = X-Hasura-User-Id)

Màn hình mới — Phase 3

Màn hình / Hành độngITLeaderITStaffBODAccLeaderManager CNAffiliate
Wizard step Đối tác: Gán đối tác
Wizard step Đối tác: Set quota
Tạo ĐH: Xem badge "Do đối tác X phát"
Report: Filter theo đối tác✅ (Branch)

Màn hình mới — Phase 4

Màn hình / Hành độngITLeaderITStaffBODAccLeaderManager CNStaff
Tab Chu kỳ: Xem KPI + chart✅ (Branch)
Tab Chu kỳ: So sánh chiến dịch✅ (Branch)
Tab Chu kỳ: Export Excel

B6. State Matrix

Mỗi màn hình mới × 5 trạng thái

Màn hìnhLoadingEmptyErrorNo PermissionPartial
Override DashboardSkeleton 5 rows"Chưa có override nào" + icon trốngToast "Lỗi tải dữ liệu" + nút Thử lạiRedirect về /voucher + toast "Bạn không có quyền"Filter trả 0 kết quả → "Không tìm thấy override phù hợp"
Tab Thống kê NVSkeleton 5 rows + KPI cards placeholder"Chưa có dữ liệu thống kê. Voucher cần được phát trước khi có thống kê"Toast "Lỗi tải thống kê" + nút Thử lạiTab ẩn (không hiện trong tab list)Filter trả 0 → "Không có NV phát voucher trong khoảng thời gian này"
Staff Drill-downSpinner trong dialog"NV chưa phát voucher nào"Toast trong dialog + nút Thử lạiN/A (kế thừa từ tab cha)Voucher chưa có ĐH → cột DT hiện "—"
Tab Chu kỳSkeleton cards + chart placeholder"Chưa có dữ liệu chu kỳ. Cần ít nhất 1 voucher đã sử dụng"Toast "Lỗi tải dữ liệu" + nút Thử lạiTab ẩnTất cả voucher đang pending → chart hiện 100% "Chưa dùng", KPI hiện "—"
Wizard step Đối tácSpinner loading danh sách đối tác"Chưa có đối tác nào trong hệ thống. Vui lòng tạo đối tác trong module Affiliate trước"Toast "Lỗi tải danh sách đối tác"Step ẩn (chỉ hiện cho Full Access roles)Một số đối tác bị deactivated → hiện nhưng disabled + tooltip "Đối tác đã ngừng hoạt động"
Dialog chặn voucherN/A (data có sẵn từ response lỗi)N/AN/ANút "Yêu cầu duyệt" ẩn nếu không phải Manager/AdminN/A
Voucher SettingsSpinner loading current settingsHiện giá trị mặc định (24h, 30 ngày)Toast "Lỗi tải cấu hình"Redirect + toast "Bạn không có quyền"N/A

B7. Copy Text Dictionary

Phase 1 — Time Control

KeyVietnameseContext
voucher.config.min_activation_hours.labelThời gian chờ tối thiểu (giờ)Label input trong form tạo campaign
voucher.config.min_activation_hours.hintSố giờ tối thiểu từ khi kích hoạt đến khi voucher được dùng trong đơn hàng. 0 = không giới hạnHint text
voucher.time_block.titleVoucher chưa đủ thời gian sử dụngTitle dialog chặn
voucher.time_block.remainingCòn lại: {hours} giờ {minutes} phútThời gian còn lại
voucher.time_block.available_fromVoucher có thể sử dụng từ:Thời điểm cho phép
voucher.time_block.request_overrideYêu cầu Manager duyệtNút CTA
voucher.override.titleYêu cầu duyệt sử dụng voucher sớmTitle dialog override
voucher.override.reason.labelLý doLabel dropdown
voucher.override.reason.vip_customerKhách VIPOption
voucher.override.reason.special_eventSự kiện đặc biệtOption
voucher.override.reason.manager_directiveChỉ đạo cấp trênOption
voucher.override.reason.system_errorLỗi hệ thốngOption
voucher.override.reason.otherKhácOption
voucher.override.note.labelGhi chúLabel textarea
voucher.override.note.placeholderNhập lý do chi tiết (bắt buộc)Placeholder
voucher.override.confirmXác nhận duyệtNút confirm
voucher.override.successOverride đã được duyệtToast success
voucher.settings.min_hours.labelThời gian chờ mặc định (giờ)Settings label
voucher.settings.min_hours.hintÁp dụng cho voucher manual không thuộc chiến dịchSettings hint
voucher.settings.expiry_days.labelHạn sử dụng voucher manual (ngày)Settings label
voucher.settings.expiry_days.hintSố ngày kể từ khi tặng. 0 = không giới hạnSettings hint

Phase 2 — Thống kê NV

KeyVietnameseContext
voucher.staff_stats.tabThống kê NVTab label
voucher.staff_stats.titleThống kê phát voucher theo nhân viênPage title
voucher.staff_stats.filter.distributor_typeLoại người phátFilter label
voucher.staff_stats.filter.allTất cảOption
voucher.staff_stats.filter.internalNhân viênOption
voucher.staff_stats.filter.affiliateĐối tácOption
voucher.staff_stats.col.staff_nameNhân viênColumn header
voucher.staff_stats.col.branchChi nhánhColumn header
voucher.staff_stats.col.distributedĐã phátColumn header
voucher.staff_stats.col.redeemedĐã dùngColumn header
voucher.staff_stats.col.rateTỉ lệ %Column header
voucher.staff_stats.col.revenueDoanh thuColumn header
voucher.staff_stats.col.avg_daysTB ngày dùngColumn header
voucher.staff_stats.emptyChưa có dữ liệu thống kêEmpty state
voucher.staff_stats.exportXuất ExcelButton

Phase 3 — Đối tác

KeyVietnameseContext
voucher.affiliate.step_titleĐối tác phát voucherWizard step title
voucher.affiliate.enableCho phép đối tác phát voucher chiến dịch nàyCheckbox label
voucher.affiliate.selectChọn đối tácDropdown label
voucher.affiliate.quotaQuotaColumn header
voucher.affiliate.unlimitedKhông giới hạnKhi quota = NULL
voucher.affiliate.source_badgeVoucher do {affiliate_name} phátBadge trên tạo ĐH
voucher.affiliate.quota_exceededĐối tác đã hết quota cho chiến dịch nàyError message

Phase 4 — Chu kỳ

KeyVietnameseContext
voucher.cycle.tabChu kỳ sử dụngTab label
voucher.cycle.titleBáo cáo chu kỳ sử dụng voucherPage title
voucher.cycle.avgTrung bìnhKPI label
voucher.cycle.minNhanh nhấtKPI label
voucher.cycle.medianTrung vịKPI label
voucher.cycle.rateTỉ lệ dùngKPI label
voucher.cycle.unitngàyUnit suffix
voucher.cycle.bucket.0_30-3 ngàyBucket label
voucher.cycle.bucket.4_74-7 ngàyBucket label
voucher.cycle.bucket.8_148-14 ngàyBucket label
voucher.cycle.bucket.15_3015-30 ngàyBucket label
voucher.cycle.bucket.31_6031-60 ngàyBucket label
voucher.cycle.bucket.60_plus60+ ngàyBucket label
voucher.cycle.bucket.pendingChưa dùngBucket label
voucher.cycle.group_by.labelNhóm theoFilter label
voucher.cycle.group_by.overallTổng quanOption
voucher.cycle.group_by.campaignChiến dịchOption
voucher.cycle.group_by.branchChi nhánhOption
voucher.cycle.group_by.staffNhân viênOption

B-Desktop. ASCII Wireframes

WF-01: Field min_activation_hours trong VoucherConfigForm (P1)

┌─────────────────────────────────────────────────────────────────┐
│  Cấu hình chiến dịch                                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Tên chiến dịch:  [Tết Kim Cương 2026________________]        │
│                                                                 │
│  Thời gian chạy:  [01/01/2026] → [28/02/2026]                 │
│                                                                 │
│  Hạn sử dụng voucher (ngày):  [30_____]                       │
│  Hint: Số ngày voucher có hiệu lực kể từ khi kích hoạt        │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ ⚡ Kiểm soát thời gian                                  │   │
│  ├─────────────────────────────────────────────────────────┤   │
│  │ Thời gian chờ tối thiểu (giờ):  [24______]             │   │
│  │ Hint: Voucher phải chờ 24 giờ sau khi kích hoạt mới    │   │
│  │ được dùng trong đơn hàng. 0 = không giới hạn           │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│  Giá trị giảm:  [500.000đ________]                            │
│  ...                                                            │
│                                                                 │
│                                   [Quay lại]  [Tiếp theo →]   │
└─────────────────────────────────────────────────────────────────┘

WF-02: Override Dashboard (P1)

┌─────────────────────────────────────────────────────────────────────────┐
│  Giám sát Override Voucher                                             │
├─────────────────────────────────────────────────────────────────────────┤
│  [Từ ngày: 01/03/2026 ▼]  [Đến ngày: 16/03/2026 ▼]                  │
│  [Chi nhánh: Tất cả    ▼]  [Lý do: Tất cả         ▼]                │
│                                                        [Xuất Excel]   │
│                                                                        │
│  Tổng: 12 override | Nhiều nhất: Khách VIP (7) | CN Q.1 HCM (5)     │
├────┬───────────┬──────────┬─────────────┬──────────┬──────────────────┤
│ #  │ Người     │ CN       │ Mã Voucher  │ Lý do    │ Ghi chú         │
│    │ duyệt     │          │             │          │                  │
├────┼───────────┼──────────┼─────────────┼──────────┼──────────────────┤
│ 1  │ Trần B    │ Q.1 HCM  │ CDLF42L4SB  │ Khách    │ KH thân thiết   │
│    │ 15/03     │          │             │ VIP      │ 5 năm           │
│    │ 10:30     │          │             │          │                  │
├────┼───────────┼──────────┼─────────────┼──────────┼──────────────────┤
│ 2  │ Nguyễn A  │ Q.3 HCM  │ CD4YCJ2VC4  │ Sự kiện  │ Event khai      │
│    │ 14/03     │          │             │ đặc biệt │ trương CN mới   │
│    │ 16:45     │          │             │          │                  │
├────┼───────────┼──────────┼─────────────┼──────────┼──────────────────┤
│ 3  │ Lê C      │ Đà Nẵng  │ CDXHYA5PAD  │ Chỉ đạo  │ BGĐ yêu cầu    │
│    │ 13/03     │          │             │ cấp trên │ duyệt           │
│    │ 09:15     │          │             │          │                  │
├────┴───────────┴──────────┴─────────────┴──────────┴──────────────────┤
│  ← 1 / 1 → (20 hàng/trang)                                          │
└───────────────────────────────────────────────────────────────────────┘

WF-03: Tab Thống kê NV (P2)

┌─────────────────────────────────────────────────────────────────────────┐
│  Thống kê phát voucher theo nhân viên                                  │
├─────────────────────────────────────────────────────────────────────────┤
│  [Từ ngày ▼]  [Đến ngày ▼]  [Chi nhánh ▼]  [Loại: Tất cả ▼]        │
│                                                        [Xuất Excel]   │
├────┬───────────┬──────────┬────────┬────────┬─────────┬───────┬───────┤
│ #  │ NV        │ CN       │ Loại   │ Đã phát│ Đã dùng │ Tỉ lệ│ DT    │
├────┼───────────┼──────────┼────────┼────────┼─────────┼───────┼───────┤
│ 1  │ Nguyễn A  │ Q.1 HCM  │ NV     │ 120    │ 45      │ 37.5% │ 22.5tr│
│ 2  │ KOL Hương │ —        │ Đối tác│ 185    │ 62      │ 33.5% │ 31.0tr│
│ 3  │ Trần B    │ Q.3 HCM  │ NV     │ 98     │ 31      │ 31.6% │ 15.2tr│
│ 4  │ PK Đông Y │ —        │ Đối tác│ 76     │ 22      │ 28.9% │ 11.0tr│
│ 5  │ Lê C      │ Đà Nẵng  │ NV     │ 75     │ 28      │ 37.3% │ 14.0tr│
├────┴───────────┴──────────┴────────┴────────┴─────────┴───────┴───────┤
│  Tổng: 554 voucher | 188 đã dùng | 33.9% | 93.7tr                    │
│  ← 1 / 3 → (20 hàng/trang)                                          │
└───────────────────────────────────────────────────────────────────────┘

WF-04: Staff Drill-down Dialog (P2)

┌─────────────────────────────────────────────────────────────────┐
│  Chi tiết phát voucher — Nguyễn A (Q.1 HCM)            [X]    │
├─────────────────────────────────────────────────────────────────┤
│  Tổng phát: 120 | Đã dùng: 45 (37.5%) | DT: 22.5 triệu       │
├────┬───────────┬──────────────┬────────────┬────────┬──────────┤
│ #  │ Mã VC     │ Khách hàng   │ Ngày phát  │ Trạng  │ DT (đ)  │
│    │           │              │            │ thái   │          │
├────┼───────────┼──────────────┼────────────┼────────┼──────────┤
│ 1  │ CDLF42L4S │ Trần Thị Mai │ 15/02/2026 │ ●Đã    │ 500.000 │
│    │           │ 0901234567   │            │  dùng  │          │
│ 2  │ CD4YCJ2VC │ Ngô Văn Hùng │ 16/02/2026 │ ●Kích  │ —       │
│    │           │ 0912345678   │            │  hoạt  │          │
│ 3  │ CDXHYA5PA │ Lê Thị Lan   │ 18/02/2026 │ ●Hết   │ —       │
│    │           │ 0923456789   │            │  hạn   │          │
│ 4  │ CDVRGMLAK │ Phạm Anh Tuấn│ 20/02/2026 │ ●Đã    │ 1.200k  │
│    │           │ 0934567890   │            │  dùng  │          │
├────┴───────────┴──────────────┴────────────┴────────┴──────────┤
│  ← 1 / 6 → (20 hàng/trang)                                   │
└────────────────────────────────────────────────────────────────┘

WF-05: Wizard Step Đối tác (P3)

┌─────────────────────────────────────────────────────────────────┐
│  Bước 6: Đối tác phát voucher                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ☑ Cho phép đối tác phát voucher chiến dịch này                │
│                                                                 │
│  [Chọn đối tác ▼ (Tìm theo tên, mã...)  ]   [+ Thêm đối tác] │
│                                                                 │
├────┬──────────────┬──────────┬──────────────┬───────────────────┤
│ #  │ Đối tác      │ Loại     │ Quota        │ Hành động         │
├────┼──────────────┼──────────┼──────────────┼───────────────────┤
│ 1  │ KOL Hương    │ KOL      │ [200_______] │ [Xóa]             │
│    │ AFF-001      │          │              │                   │
│ 2  │ PK Đông Y    │ Phòng    │ [500_______] │ [Xóa]             │
│    │ AFF-012      │          │ khám         │                   │
│ 3  │ Đại lý Q.7   │ Đại lý   │ [☑ Không    ]│ [Xóa]             │
│    │ AFF-023      │          │ [  giới hạn ]│                   │
├────┴──────────────┴──────────┴──────────────┴───────────────────┤
│  Tổng: 3 đối tác | Quota: 700 + Không giới hạn                 │
│                                                                 │
│                                   [← Quay lại]  [Hoàn tất →]  │
└─────────────────────────────────────────────────────────────────┘

WF-06: Tab Chu kỳ sử dụng (P4)

┌─────────────────────────────────────────────────────────────────────────┐
│  Báo cáo chu kỳ sử dụng voucher                                       │
├─────────────────────────────────────────────────────────────────────────┤
│  [Từ ngày ▼] [Đến ngày ▼] [Chi nhánh ▼] [Chiến dịch ▼]              │
│  Nhóm theo: [◉ Tổng quan ○ Chiến dịch ○ Chi nhánh ○ Nhân viên]      │
│                                                        [Xuất Excel]   │
│                                                                        │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐                 │
│  │ 📊 TB    │ │ ⚡ Nhanh │ │ 📈 Trung │ │ ✅ Tỉ lệ │                 │
│  │ 12 ngày  │ │ nhất     │ │ vị       │ │ dùng     │                 │
│  │          │ │ 1 ngày   │ │ 9 ngày   │ │ 35.2%    │                 │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘                 │
│                                                                        │
│  Phân bổ chu kỳ sử dụng                                               │
│  ┌─────────────────────────────────────────────────────────────┐      │
│  │ 0-3 ngày   ████████████████████████████  125 (28%)         │      │
│  │ 4-7 ngày   ████████████████████          89 (20%)          │      │
│  │ 8-14 ngày  ██████████████████            76 (17%)          │      │
│  │ 15-30 ngày ██████████████                62 (14%)          │      │
│  │ 31-60 ngày ██████████                    45 (10%)          │      │
│  │ 60+ ngày   ██████                        28 (6%)           │      │
│  │ Chưa dùng  ████                          25 (5%)           │      │
│  └─────────────────────────────────────────────────────────────┘      │
│                                                                        │
│  Chi tiết                                                              │
│  ┌────┬──────────────┬───────┬───────┬────────┬────────┬───────┐     │
│  │ #  │ Chiến dịch   │Đã KH │Đã dùng│ TB ngày│Trung vị│ Tỉ lệ│     │
│  ├────┼──────────────┼───────┼───────┼────────┼────────┼───────┤     │
│  │ 1  │ Tết Kim Cương│  95   │  38   │  8.5   │  6     │ 40.0%│     │
│  │ 2  │ Sinh nhật    │  51   │  15   │  14.2  │  12    │ 29.4%│     │
│  │ 3  │ Game con ngựa│  458  │  158  │  11.3  │  8     │ 34.5%│     │
│  │ 4  │ TTCN 3-5 tr  │   1   │   0   │  —     │  —     │  0.0%│     │
│  └────┴──────────────┴───────┴───────┴────────┴────────┴───────┘     │
│  ← 1 / 2 → (20 hàng/trang)                                          │
└──────────────────────────────────────────────────────────────────────┘

WF-07: Badge nguồn đối tác trên màn hình tạo ĐH (P3)

┌──────────────────────────────────────────────┐
│  Chọn voucher cho đơn hàng                   │
├──────────────────────────────────────────────┤
│  Mã voucher: CDLF42L4SB                     │
│  Tên: 01 Suất chăm sóc da trắng sáng        │
│  Giá trị: 500.000đ                          │
│  Trạng thái: ● Đã kích hoạt                 │
│                                              │
│  ┌────────────────────────────────────┐      │
│  │ 🏷 Voucher do KOL Hương phát      │      │
│  │   Mã đối tác: AFF-001             │      │
│  │   💡 Gợi ý: gán affiliate cho ĐH  │      │
│  └────────────────────────────────────┘      │
│                                              │
│              [Áp dụng]       [Hủy]           │
└──────────────────────────────────────────────┘

B-Export. Export Specifications

Export 1: Override Dashboard (P1)

#CộtKeyFormatWidth
1STTindexNumber8
2Người duyệtapproved_by_nameText20
3Chi nhánhbranch_nameText20
4Mã vouchervoucher_codeText15
5Tên vouchervoucher_nameText30
6Khách hàngcustomer_nameText25
7Lý doreason_code_labelText18
8Ghi chúreason_noteText35
9Giờ còn lạihours_remainingNumber (2 decimal)12
10Ngày duyệtcreated_atdd/MM/yyyy HH:mm18
  • File name: Override_Voucher_{from}_{to}.xlsx
  • Sheet name: Override
  • Row cap: 10.000 rows
  • Async threshold: > 5.000 rows → dùng export-api

Export 2: Thống kê NV (P2)

#CộtKeyFormatWidth
1STTindexNumber8
2Nhân viênstaff_nameText25
3Chi nhánhbranch_nameText20
4Loạidistributor_type_labelText15
5Số đã pháttotal_distributedNumber12
6Số đã kích hoạttotal_activatedNumber15
7Số đã dùngtotal_redeemedNumber12
8Số hết hạntotal_expiredNumber12
9Tỉ lệ sử dụng (%)conversion_ratePercent (2 decimal)15
10Doanh thu (đ)total_revenueVND currency18
11TB ngày dùngavg_days_to_redeemNumber (1 decimal)15
  • File name: Thong_ke_NV_Voucher_{campaign}_{from}_{to}.xlsx
  • Sheet 1: Tong_hop (bảng summary trên)
  • Sheet 2: Chi_tiet (drill-down tất cả NV — mã VC, khách hàng, ngày phát, trạng thái, DT)
  • Row cap: 50.000 rows
  • Async threshold: > 5.000 rows

Export 3: Chu kỳ sử dụng (P4)

#CộtKeyFormatWidth
1STTindexNumber8
2Nhómgroup_nameText30
3Tổng kích hoạttotal_activatedNumber15
4Tổng đã dùngtotal_redeemedNumber15
5Tổng hết hạntotal_expiredNumber15
6Đang chờtotal_pendingNumber12
7TB ngàyavg_days_to_redeemNumber (1 decimal)12
8Nhanh nhấtmin_days_to_redeemNumber12
9Lâu nhấtmax_days_to_redeemNumber12
10Trung vịmedian_days_to_redeemNumber (1 decimal)12
  • File name: Chu_ky_su_dung_Voucher_{from}_{to}.xlsx
  • Sheet 1: Tong_hop (bảng summary trên)
  • Sheet 2: Phan_bo (bucket distribution: group_name, bucket, count, percentage)
  • Row cap: 10.000 rows
  • Async threshold: > 5.000 rows

B-Edge Cases

Phase 1 — Kiểm soát thời gian

#Edge CaseHành vi mong đợi
EC-P1-01Voucher kích hoạt đúng boundary (VD: 23h59 trước midnight, min=24h)So sánh bằng giờ thực, không round. 23h59 < 24h → vẫn chặn
EC-P1-02min_activation_hours = 0 (không giới hạn)Bỏ qua check hoàn toàn, voucher dùng ngay được
EC-P1-03Voucher thuộc campaign ĐÃ KẾT THÚC nhưng chưa expiredCheck min_activation_hours vẫn áp dụng. Campaign end ≠ voucher expire
EC-P1-04Override 2 lần cho cùng 1 voucherCho phép — mỗi override là 1 record riêng. ĐH đầu có thể bị hủy, cần override lần 2
EC-P1-05Timezone: NV ở UTC+7, server ở UTCTất cả so sánh dùng Asia/Ho_Chi_Minh. activated_at đã lưu TIMESTAMPTZ
EC-P1-06Voucher manual không thuộc campaign, system default = 0Bỏ qua check, voucher dùng ngay. Nhưng expired_at vẫn set nếu default_expiry_days > 0
EC-P1-07Manager override cho chính ĐH mình tạoCho phép — Manager vừa là người approve override vừa là người tạo ĐH
EC-P1-08Voucher kích hoạt trước khi feature deploy (không có min_activation_hours trong campaign cũ)DEFAULT 24 từ migration. Tất cả campaign cũ tự động có min=24h. Nếu không muốn → Admin set 0

Phase 2 — Thống kê NV

#Edge CaseHành vi mong đợi
EC-P2-01Voucher phát bởi NV đã nghỉ việc (account deactivated)Vẫn hiện trong report với tên NV. Không xóa data lịch sử
EC-P2-02NV phát voucher rồi chuyển chi nhánhReport theo branch_id tại thời điểm phát (lưu trong user_vouchers), không theo branch hiện tại
EC-P2-03staff_id NULL (voucher online tự claim)Không hiện trong report NV. Filter staff_id IS NOT NULL
EC-P2-04NV phát 0 voucher trong khoảng thời gian filterKhông hiện hàng NV đó (chỉ hiện NV có activity)
EC-P2-05Doanh thu = 0 cho voucher đã dùng (voucher tặng miễn phí)Hiện DT = 0đ (không ẩn). Voucher tặng dịch vụ miễn phí vẫn có giá trị
EC-P2-06Partner app: NV xem thống kê nhưng chưa phát voucher nàoHiện empty state: "Bạn chưa phát voucher nào"

Phase 3 — Đối tác

#Edge CaseHành vi mong đợi
EC-P3-01Đối tác bị deactivated (is_active=false) giữa chiến dịchĐối tác không thể phát thêm voucher. Voucher đã phát vẫn hợp lệ
EC-P3-02Đối tác phát voucher đúng lúc hết quota (concurrent)Atomic increment. Chỉ 1 request thành công, request còn lại nhận lỗi "Đã hết quota"
EC-P3-03Admin tăng quota cho đối tác đang hết quotaĐối tác có thể phát tiếp ngay lập tức (upsert update quota)
EC-P3-04Xóa đối tác khỏi campaign đã phát voucherSoft delete (deleted_at). Voucher đã phát vẫn giữ staff_id. Report vẫn hiện
EC-P3-05Campaign không gán đối tác nàoStep wizard bỏ qua. Flow phát voucher hoạt động bình thường (chỉ NV nội bộ)
EC-P3-06Đối tác phát voucher cho chiến dịch KHÔNG được gánTừ chối: "Đối tác không được phép phát voucher chiến dịch này"

Phase 4 — Chu kỳ

#Edge CaseHành vi mong đợi
EC-P4-01Campaign 100% voucher pending (chưa ai dùng)KPI avg/min/max/median hiện "—". Chart hiện 100% bucket "Chưa dùng"
EC-P4-02Voucher redeemed rồi restored (ĐH hủy) rồi redeemed lạiDùng voucher_logs DISTINCT ON → lấy lần redeem cuối cùng. Cycle = lần redeem cuối - activated_at
EC-P4-03Voucher kích hoạt và dùng cùng ngày (cycle = 0)Hợp lệ. Nằm trong bucket "0-3 ngày". KPI min = 0
EC-P4-04Filter trả > 100k vouchersDB-level aggregation. Frontend chỉ nhận kết quả đã aggregate (~10-50 rows). Không ảnh hưởng performance
EC-P4-05So sánh 2 chiến dịch: 1 có data, 1 khôngChiến dịch không data hiện "—" cho tất cả metrics. Bar chart hiện 0% tất cả buckets
EC-P4-06PERCENTILE_CONT trả NULL khi không có data redeemMedian hiện "—". Xử lý COALESCE(median, NULL) trong SQL

Changelog

VersionNgàyThay đổi
1.02026-03-16Initial — B1-B7, Wireframes, Export Spec, Edge Cases (30 cases)