Skip to content

PRD — Nâng cấp Voucher Management: Kiểm soát, Thống kê & Đối tác

Version: 1.0 Ngày: 2026-03-16 Tác giả: PO/BA Trạng thái: Draft — Chờ review


Z) Decision Log

Z1 — Business Decisions

IDQuyết địnhLý doNgày
DEC-B01Chặn cứng thời gian kích hoạt → sử dụng voucher (Option D: chặn + cấu hình + override)Ngăn gian lận tạo ĐH rồi kích hoạt voucher ngay. Cần linh hoạt cho case VIP/đặc biệt2026-03-16
DEC-B02Mặc định min_activation_hours = 24h, cấu hình per campaignPhù hợp ngành spa — voucher mục đích kéo khách quay lại, không phải giảm giá tức thì2026-03-16
DEC-B03Không xây commission mới — chỉ thống kê doanh thu phát sinhHoa hồng đã có cơ chế trong ĐH (nhân viên gán nhãn tỉ lệ). Voucher chỉ cần track ai phát → phát sinh bao nhiêu2026-03-16
DEC-B04Kết nối Affiliate module hiện có (không tạo partner system mới)Affiliate đã có: affiliate_user, order_affiliate, invoice_affiliate. Tái sử dụng thay vì duplicate2026-03-16
DEC-B05Thời hạn voucher manual dùng field expired_at có sẵnuser_vouchers.expired_at đã tồn tại + cron expired_vouchers đã check. Không cần cột mới2026-03-16
DEC-B06Triển khai 4 phase theo priority P0 → P2P0 (chống gian lận) cấp bách nhất, P2 (chu kỳ) có thể chờ2026-03-16

Z2 — UX Decisions

IDQuyết địnhLý doNgày
DEC-U01Dialog chặn + nút "Yêu cầu duyệt" khi voucher chưa đủ thời gianUX rõ ràng: nhân viên hiểu lý do bị chặn + có flow escalate cho Manager2026-03-16
DEC-U02Tab "Thống kê nhân viên" trong voucher management (không tách module report riêng)Giữ context — thống kê liên quan trực tiếp đến voucher, nằm chung cho dễ truy cập2026-03-16
DEC-U03Drill-down: click nhân viên → danh sách voucher đã phátCho manager thấy chi tiết per nhân viên mà không cần navigate ra ngoài2026-03-16
DEC-U04Horizontal bar chart cho phân bổ chu kỳ sử dụngTrực quan, dễ so sánh giữa các bucket. Chart.js đã có sẵn2026-03-16

Z3 — Technical Decisions

IDQuyết địnhLý doNgày
DEC-T01Check thời gian trong validateOrderVouchers() của create_order.goĐây là điểm validation duy nhất khi tạo ĐH với voucher — centralized, không bỏ sót2026-03-16
DEC-T02Override table voucher_activation_overrides riêngTách biệt audit trail, không mix vào voucher_logs. Dễ query dashboard giám sát2026-03-16
DEC-T03Thêm distributor_type vào user_vouchers (không dùng JOIN runtime)Performance tốt hơn khi report — tránh JOIN phức tạp với affiliate_user mỗi lần query2026-03-16
DEC-T04Upsert pattern cho voucher_campaign_affiliates (không delete-then-insert)Junction table có distributed_count — delete-then-insert sẽ mất counter2026-03-16
DEC-T05Dùng voucher_logs để tính cycle time (không dùng redeemed_at trực tiếp)Xử lý đúng case voucher bị restore khi ĐH hủy (Gross vs Net pattern đã proven)2026-03-16
DEC-T06Dùng PERCENTILE_CONT(0.5) PostgreSQL built-in cho medianPostgreSQL 14 hỗ trợ sẵn, không cần extension hay function custom2026-03-16
DEC-T07Bucket aggregation tại DB level (không tính ở frontend)Scale tốt với 100k+ vouchers. Pattern đã proven trong 5 analytics function hiện có2026-03-16
DEC-T08Config mặc định voucher trong app_setting (JSONB pattern)Theo pattern AppSettings struct đã có (nested JSONB). Cho voucher manual không thuộc campaign2026-03-16

Z4 — QA Decisions

IDQuyết địnhLý doNgày
DEC-Q01Test chặn thời gian phải cover: chặn đúng, override đúng, edge case midnightLogic thời gian dễ sai ở timezone + boundary. Vietnam timezone (UTC+7) cần test kỹ2026-03-16
DEC-Q02Test affiliate quota phải cover race condition (concurrent activation)Pattern atomic increment đã có cho offline_used_count nhưng cần verify cho affiliate quota2026-03-16

A) PRD

A1. Blueprint

Thuộc tínhGiá trị
Tên tính năngNâng cấp Voucher Management: Kiểm soát, Thống kê & Đối tác
LoạiEnhancement (nâng cấp module hiện có)
ComplexityLarge (4 phases, cross-module)
Modules ảnh hưởngcms (voucher), ecommerce, affiliate, report, settings
Services ảnh hưởngecommerce-api, controller (Hasura)
PlatformAdmin web + Diva Partner app (API only)

A2. Context

Hiện trạng:

  • Module voucher management đã production-grade: 17 bảng, 11 DB functions, 3 kênh phát (online/offline/print), lifecycle đầy đủ, analytics cơ bản (funnel, branch, product, date)
  • Voucher tặng trực tiếp tại cửa hàng (offline) là loại chính đang sử dụng
  • Không có thời hạn sử dụng cho voucher manual (tặng qua Partner app/POS)
  • Không có cơ chế kiểm soát thời gian giữa kích hoạt và sử dụng
  • Không có thống kê theo nhân viên phát voucher
  • Không có kết nối với module Affiliate cho đối tác bên ngoài
  • Chi phí và doanh thu trên báo cáo hiện tại đều hiển thị 0đ

Vấn đề:

  1. Gian lận: nhân viên có thể tạo ĐH rồi kích hoạt voucher ngay để giảm giá trái phép
  2. Không kiểm soát được ai phát bao nhiêu voucher, hiệu quả ra sao
  3. Đối tác bên ngoài không được quản lý trong hệ thống voucher
  4. Không đo được hiệu quả voucher theo chu kỳ khách quay lại

A3. Goals & Success Metrics

GoalMetricTarget
Ngăn gian lận voucherSố case kích hoạt + tạo ĐH trong < 24hGiảm > 95% so với hiện tại
Kiểm soát nhân viênThống kê lượt phát per nhân viên/chi nhánh100% voucher offline được track
Mở rộng đối tácSố đối tác affiliate tham gia chiến dịch voucher>= 5 đối tác trong 3 tháng đầu
Đo hiệu quảThời gian TB từ phát → sử dụng voucherCó data để so sánh giữa chiến dịch

A4. Personas

PersonaVai tròNhu cầu chính
Admin/BODQuản lý toàn hệ thốngXem báo cáo cross-branch, kiểm soát gian lận, cấu hình policy
Manager chi nhánhQuản lý 1 chi nhánhXem thống kê nhân viên tại chi nhánh, approve override
Nhân viên (Staff)Phát voucher tại cửa hàng/POSTặng voucher cho khách, xem thống kê cá nhân trên Partner app
Đối tác (Affiliate)Phát voucher cho khách hàng riêngPhát voucher theo quota, xem doanh thu phát sinh
Kế toánĐối soátXem báo cáo doanh thu từ voucher, export Excel

A5. Functional Requirements

Phase 1 (P0) — Kiểm soát thời gian + Thời hạn voucher manual

FR IDMô tảAcceptance CriteriaRef
FR-P1-01Cấu hình thời gian chờ tối thiểu (giờ) per campaignAdmin tạo chiến dịch, set "Thời gian chờ tối thiểu = 48 giờ". Voucher kích hoạt lúc 10:00 01/04 → không thể dùng trong ĐH trước 10:00 03/04DEC-B01, DEC-B02
FR-P1-02Chặn tạo ĐH khi voucher chưa đủ thời gianNV tạo ĐH với voucher kích hoạt 2h trước, campaign set 24h → hệ thống chặn, hiển thị "Voucher cần chờ thêm 22 giờ"DEC-T01
FR-P1-03Manager override khi bị chặnManager nhập lý do (chọn dropdown + ghi chú) → approve → ĐH được tạo. Log: manager_id, reason, timestampDEC-B01, DEC-T02
FR-P1-04Dashboard giám sát overrideAdmin/BOD xem danh sách override: ai approve, cho voucher nào, lý do gì, lúc nào. Filter theo chi nhánh, thời gianDEC-T02
FR-P1-05Cấu hình mặc định thời gian chờ trong SettingsAdmin vào Settings → Voucher → set "Thời gian chờ mặc định = 24 giờ". Áp dụng cho voucher manual không thuộc campaignDEC-T08
FR-P1-06Thời hạn sử dụng cho voucher manualAdmin cấu hình "Hạn sử dụng voucher manual = 30 ngày". NV tặng voucher ngày 01/04 → voucher hết hạn 01/05. Cron tự đánh expiredDEC-B05
FR-P1-07Hiển thị hạn sử dụng trên Partner app/POSKhi NV tặng voucher → hiển thị "Hết hạn: 01/05/2026". Khách xem voucher trong app cũng thấy hạnDEC-B05

Phase 2 (P1) — Thống kê nhân viên

FR IDMô tảAcceptance CriteriaRef
FR-P2-01Báo cáo thống kê phát voucher theo nhân viênAdmin xem bảng: NV Nguyễn A (Q.1 HCM) đã phát 120 voucher, 45 đã dùng (37.5%), doanh thu 22.5 triệu. Filter theo chi nhánh, chiến dịch, thời gianDEC-U02
FR-P2-02Drill-down chi tiết per nhân viênClick NV Nguyễn A → danh sách: mã voucher CDLF42L4S, tặng cho Trần Thị Mai ngày 15/02, trạng thái "Đã dùng", DT 500.000đDEC-U03
FR-P2-03Ranking nhân viênSắp xếp theo: số phát, tỉ lệ conversion, doanh thu. Top 10 nhân viên phát voucher hiệu quả nhấtDEC-U02
FR-P2-04API thống kê cá nhân cho Partner appNV đăng nhập Partner app → xem: "Tháng này bạn phát 25 voucher, 8 đã dùng (32%), DT 4 triệu". Chỉ thấy data của mìnhDEC-B03
FR-P2-05Export Excel báo cáo nhân viênAdmin click "Xuất Excel" → file chứa: danh sách NV, số liệu, chi tiết voucher. Async nếu > 5000 rowsDEC-U02

Phase 3 (P1) — Đối tác Affiliate

FR IDMô tảAcceptance CriteriaRef
FR-P3-01Gán đối tác vào chiến dịch voucher (Hybrid Distribution)Admin tạo chiến dịch → bước "Đối tác" → chọn KOL Hương (quota 200, kênh: Both), PK Đông Y (quota 500, kênh: Batch). Mỗi đối tác chọn kênh phát: App (real-time qua Partner App), Batch (generate mã offline QR/tờ rơi), hoặc Both. Đối tác lấy từ module Affiliate hiện cóDEC-B04
FR-P3-01aGenerate batch mã cho đối tác offlineAdmin vào chi tiết campaign → tab "Đối tác" → chọn PK Đông Y (kênh: Batch) → bấm "Generate 500 mã" → hệ thống tạo 500 mã pre-assign cho đối tác → quota trừ ngay → tải PDF/ExcelDEC-B04, DEC-T04
FR-P3-02Quota per đối tác per chiến dịchQuota = tổng cam kết (app + batch). KOL Hương phát app: 12, generate batch: 100 → distributed_count = 112/200. Khách kích hoạt batch → KHÔNG trừ thêm quota (đã trừ khi generate). Hết quota → chặn cả app lẫn generate thêmDEC-T04
FR-P3-03Phân biệt nhân viên nội bộ vs đối tác trong reportBáo cáo thống kê hiển thị cột "Loại": Nhân viên / Đối tác. Filter: "Chỉ xem đối tác"DEC-T03
FR-P3-04Hiển thị "Voucher do đối tác X phát" khi tạo ĐHNV tạo ĐH, chọn voucher → hệ thống hiển thị "Voucher này do KOL Hương phát" → gợi ý gán affiliateDEC-B03
FR-P3-05Log gán/gỡ đối tác vào affiliate_action_logAdmin gán KOL Hương vào chiến dịch → log action campaign_affiliate_assigned với metadata chiến dịchDEC-T04

Phase 4 (P2) — Chu kỳ sử dụng

FR IDMô tảAcceptance CriteriaRef
FR-P4-01KPI tổng quan chu kỳAdmin xem: TB 12 ngày, nhanh nhất 1 ngày, trung vị 9 ngày, tỉ lệ dùng 35.2%DEC-T06
FR-P4-02Phân bổ theo bucketBiểu đồ ngang: 0-3 ngày (28%), 4-7 ngày (20%), 8-14 ngày (17%), 15-30 ngày (14%), 31-60 ngày (10%), 60+ ngày (6%), chưa dùng (5%)DEC-T07, DEC-U04
FR-P4-03So sánh giữa chiến dịchChọn 2+ chiến dịch → biểu đồ chồng so sánh phân bổ chu kỳ. VD: "Tết Kim Cương" TB 8.5 ngày vs "Sinh nhật" TB 14.2 ngàyDEC-T05
FR-P4-04Filter theo dimensionFilter: chiến dịch, chi nhánh, nhân viên, thời gian. Group by: chiến dịch / chi nhánh / nhân viên / tổng quanDEC-T07
FR-P4-05Export Excel chu kỳExport bảng chi tiết + summary. Async nếu data lớnDEC-U02

A6. Assumptions

IDAssumptionImpact nếu sai
ASM-01staff_id trong user_vouchers đã được set đúng cho tất cả voucher offline hiện tạiPhase 2 report sẽ thiếu data lịch sử → cần migration backfill
ASM-02Partner app gọi API qua Hasura GraphQL (cùng endpoint với admin)Nếu Partner app dùng REST riêng → cần tạo REST endpoint thay vì chỉ GraphQL
ASM-03Đối tác trong module Affiliate đã có data (không phải module trống)Nếu trống → Phase 3 cần thêm flow tạo đối tác mới
ASM-04PostgreSQL version >= 14 (hỗ trợ PERCENTILE_CONT)Nếu version cũ → cần implement median function thủ công

A7. Risks

IDRiskLikelihoodImpactMitigation
RSK-01staff_id type mismatch (Go: *string, DB: uuid) gây lỗi khi JOIN reportMediumHighVerify + fix trong Phase 2 migration
RSK-02Override flow bị lạm dụng (Manager approve tràn lan)LowMediumDashboard giám sát + alert khi override > X lần/tháng
RSK-03Race condition khi 2 đối tác phát voucher cùng lúc (quota)LowMediumAtomic increment pattern (đã proven cho offline_used_count)
RSK-04Performance report chậm khi > 100k vouchersLowMediumDB-level aggregation + materialized view nếu cần

A8. Metrics & Monitoring

MetricCách đoAlert threshold
Số lần bị chặn thời gian / ngàyCount từ voucher_activation_overrides attempts> 20 lần/ngày → review
Số lần override / thángCount từ voucher_activation_overrides approved> 50 lần/tháng → escalate BOD
Thời gian TB phát → sử dụngget_voucher_usage_cycle_statistics< 1 ngày → nghi ngờ gian lận
Conversion rate voucherredeemed / activated< 10% → review chiến dịch

B) Impact Map

Modules ảnh hưởng

ModuleThay đổiPhase
cms/voucherThêm field config, tab report mới, wizard step đối tácP1, P2, P3, P4
ecommerce (order)Thêm validation thời gian trong tạo ĐHP1
settingsThêm config mặc định voucherP1
affiliateKết nối vào voucher campaignP3

Services ảnh hưởng

ServiceThay đổiPhase
ecommerce-apiSửa create_order.go, activate_offline_voucher.go. Thêm override actionP1, P3
controller (Hasura)Thêm tables, functions, permissions YAMLP1, P2, P3, P4
export-apiThêm export template cho staff statistics + cycleP2, P4

Database changes

Thay đổiBảng/FunctionPhase
ALTER TABLEvoucher_campaigns + min_activation_hoursP1
CREATE TABLEvoucher_activation_overridesP1
ALTER TABLEuser_vouchers + distributor_typeP2
CREATE FUNCTIONget_voucher_staff_statisticsP2
CREATE TABLEvoucher_campaign_affiliatesP3
CREATE FUNCTIONget_voucher_usage_cycle_statisticsP4
CREATE FUNCTIONget_voucher_usage_cycle_distributionP4
ALTER app_settingThêm VoucherSetting vào AppSettings JSONBP1

C) Technical Notes

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

Điểm chặn: validateOrderVouchers() trong create_order.go (line 707-843).

Thêm check sau validation status + expiration hiện có:

IF campaign.min_activation_hours > 0:
    hours_elapsed = (NOW() AT TIME ZONE 'Asia/Ho_Chi_Minh' - voucher.activated_at).hours
    IF hours_elapsed < campaign.min_activation_hours:
        IF NOT exists override for this voucher:
            remaining = campaign.min_activation_hours - hours_elapsed
            → REJECT "Voucher cần chờ thêm {remaining} giờ nữa mới được sử dụng"

Voucher manual (không thuộc campaign):

  • Đọc AppSettings.VoucherSetting.DefaultMinActivationHours từ app_setting
  • Ưu tiên: campaign config > system default

Override action handler mới: POST /actions/approve_voucher_override

  • Input: voucher_id, reason_code (enum), reason_note (text)
  • reason_code enum values: vip_customer (Khách VIP), special_event (Sự kiện đặc biệt), manager_directive (Chỉ đạo cấp trên), system_error (Lỗi hệ thống), other (Khác)
  • Validate: caller phải có role Manager/Admin
  • Insert vào voucher_activation_overrides

Thời hạn voucher manual:

  • Trong activate_offline_voucher.goupdateVoucherToActivated():
    • Nếu voucher không thuộc campaign → set expired_at = activated_at + default_expiry_days
    • default_expiry_days từ AppSettings.VoucherSetting.DefaultManualVoucherExpiryDays
  • Cron expired_vouchers.go đã check expired_at < NOW() → tự động handle

C2. Data Models

Table: voucher_activation_overrides (Phase 1)

sql
CREATE TABLE voucher_activation_overrides (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_voucher_id UUID NOT NULL REFERENCES user_vouchers(id),
    order_id UUID,                          -- ĐH được tạo sau override (nullable, set sau khi tạo ĐH)
    approved_by UUID NOT NULL,              -- Manager/Admin user ID
    branch_id UUID,                         -- Chi nhánh nơi override xảy ra
    reason_code TEXT NOT NULL CHECK (reason_code IN (
        'vip_customer', 'special_event', 'manager_directive', 'system_error', 'other'
    )),
    reason_note TEXT NOT NULL,              -- Ghi chú bắt buộc
    hours_remaining NUMERIC(8,2),           -- Số giờ còn lại tại thời điểm override
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by UUID NOT NULL
);

CREATE INDEX idx_voucher_overrides_approved_by ON voucher_activation_overrides(approved_by);
CREATE INDEX idx_voucher_overrides_branch_id ON voucher_activation_overrides(branch_id);
CREATE INDEX idx_voucher_overrides_created_at ON voucher_activation_overrides(created_at);

Table: voucher_campaign_affiliates (Phase 3)

sql
CREATE TABLE voucher_campaign_affiliates (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    campaign_id UUID NOT NULL REFERENCES voucher_campaigns(id),
    affiliate_user_id UUID NOT NULL REFERENCES affiliate_user(id),
    quota INTEGER,                          -- NULL = không giới hạn
    distributed_count INTEGER NOT NULL DEFAULT 0,
    is_active BOOLEAN NOT NULL DEFAULT true,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by UUID,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_by UUID,
    deleted_at TIMESTAMPTZ,                 -- Soft delete
    UNIQUE(campaign_id, affiliate_user_id)
);

CREATE INDEX idx_vca_campaign_id ON voucher_campaign_affiliates(campaign_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_vca_affiliate_user_id ON voucher_campaign_affiliates(affiliate_user_id) WHERE deleted_at IS NULL;

-- Auto-update updated_at
CREATE TRIGGER update_voucher_campaign_affiliates_timestamp
    BEFORE UPDATE ON voucher_campaign_affiliates
    FOR EACH ROW EXECUTE FUNCTION update_timestamp();

ALTER: voucher_campaigns (Phase 1)

sql
ALTER TABLE voucher_campaigns
    ADD COLUMN min_activation_hours INTEGER NOT NULL DEFAULT 24;

ALTER: user_vouchers (Phase 2)

sql
ALTER TABLE user_vouchers
    ADD COLUMN distributor_type TEXT CHECK (distributor_type IN ('internal_staff', 'affiliate'));

C3. Phase 2 — Thống kê nhân viên

DB Function pattern: Copy từ get_voucher_branch_statistics (migration 1766800000000):

  • Thay branch_idstaff_id
  • JOIN user_vouchers.staff_id với user table để lấy tên
  • Thêm aggregation: total_distributed, total_redeemed, conversion_rate, total_revenue, avg_days_to_redeem

Hasura permission cho Partner app:

  • Function result filter: staff_id = X-Hasura-User-Id
  • Role: customer hoặc partner role (tùy Partner app setup)

C4. Phase 3 — Đối tác Affiliate

Upsert pattern (không delete-then-insert):

  • INSERT ON CONFLICT (campaign_id, affiliate_user_id) DO UPDATE SET quota, is_active, updated_at, updated_by

Quota check trong activate flow:

IF staff is affiliate:
    find voucher_campaign_affiliates record WHERE deleted_at IS NULL
    IF quota IS NOT NULL AND distributed_count >= quota:
        → REJECT "Đối tác đã hết quota"
    atomic: distributed_count = distributed_count + 1

C5. Phase 4 — Chu kỳ sử dụng

Data source: voucher_logs (không dùng user_vouchers.redeemed_at trực tiếp)

  • Xử lý đúng case restore (ĐH hủy → voucher restored → redeem lại)
  • Pattern: DISTINCT ON (voucher_id) WHERE action = 'voucher_redeemed' ORDER BY created_at DESC

Bucket aggregation tại DB:

sql
CASE
    WHEN cycle_days BETWEEN 0 AND 3 THEN '0-3'
    WHEN cycle_days BETWEEN 4 AND 7 THEN '4-7'
    WHEN cycle_days BETWEEN 8 AND 14 THEN '8-14'
    WHEN cycle_days BETWEEN 15 AND 30 THEN '15-30'
    WHEN cycle_days BETWEEN 31 AND 60 THEN '31-60'
    WHEN cycle_days > 60 THEN '60+'
    ELSE 'pending'
END as cycle_bucket

Median: PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY cycle_days)

C6. Security

  • Override action: chỉ role Manager/Admin (check ctx.Access.Role trong Go handler)
  • Override dashboard: chỉ role có Report Access (ITLeader, BOD, AccountantLeader)
  • Affiliate quota increment: atomic operation, race condition safe (pattern từ offline_used_count)
  • Partner app API: Hasura row-level security filter staff_id = X-Hasura-User-Id
  • Timezone: tất cả time comparison dùng Asia/Ho_Chi_Minh (theo DB pattern)

D) Deployment Plan

Phase Roadmap

PhasePriorityPhụ thuộcScope
Phase 1P0 — UrgentĐộc lậpmin_activation_hours + override + expired_at manual + settings
Phase 2P1 — HighĐộc lậpStaff statistics function + admin report + Partner API
Phase 3P1 — HighPhase 2 (report)voucher_campaign_affiliates + affiliate quota + extend report
Phase 4P2 — MediumNên sau Phase 2Usage cycle function + chart + tab mới

RACI Matrix

PhaseBackend DevFrontend DevQAPO/BATech LeadBOD
Phase 1RRRACI
Phase 2RRRACI
Phase 3RRRACI
Phase 4RRRACI
Partner app UIPartner Dev (R)RACI

R = Responsible, A = Accountable, C = Consulted, I = Informed

Files cần thay đổi (tổng hợp)

Backend — Go:

FileThay đổiPhase
services/ecommerce-api/action/create_order.goThêm time check trong validateOrderVouchers()P1
services/ecommerce-api/action/activate_offline_voucher.goSet expired_at cho manual + affiliate quota checkP1, P3
services/ecommerce-api/action/activate_voucher.goSet distributor_type trong updateVoucherToActivated()P2
services/ecommerce-api/action/approve_voucher_override.goMới — override action handlerP1
services/ecommerce-api/action/create_draft_voucher_campaign.goThêm affiliate insertP3
services/ecommerce-api/action/update_voucher_campaign.goThêm affiliate upsertP3
pkg/store/app_setting.goThêm VoucherSetting structP1
pkg/store/user_vouchers.goThêm DistributorType fieldP2
pkg/store/voucher_campaigns.goThêm MinActivationHours fieldP1

Backend — Hasura:

FileThay đổiPhase
controller/migrations/ecommerce/[new]/up.sqlMigration: alter tables + create tables + create functionsP1-P4
controller/metadata/databases/ecommerce/tables/public_voucher_activation_overrides.yamlMớiP1
controller/metadata/databases/ecommerce/tables/public_voucher_campaign_affiliates.yamlMớiP3
controller/metadata/databases/ecommerce/functions/public_get_voucher_staff_statistics.yamlMớiP2
controller/metadata/databases/ecommerce/functions/public_get_voucher_usage_cycle_statistics.yamlMớiP4
controller/metadata/databases/ecommerce/functions/public_get_voucher_usage_cycle_distribution.yamlMớiP4

Frontend — Admin:

File/ComponentThay đổiPhase
VoucherConfigForm.tsxThêm field "Thời gian chờ tối thiểu (giờ)"P1
Màn hình tạo ĐHDialog chặn + nút "Yêu cầu duyệt"P1
Settings pageConfig mặc định voucher (hạn sử dụng, thời gian chờ)P1
VoucherTab.tsxThêm tab "Thống kê nhân viên" + "Chu kỳ sử dụng"P2, P4
VoucherStaffStatisticsTab.tsxMới — bảng + drill-down + exportP2
VoucherUsageCycleTab.tsxMới — KPI cards + bar chart + bảngP4
VoucherCreate.tsx (wizard)Thêm bước "Đối tác phát voucher"P3
voucher.graphqlThêm queries mớiP1-P4
Override dashboardMới — danh sách overrideP1

Hướng dẫn đọc

AudienceĐọc sectionsMục đích
PO/BAZ, AXác nhận requirements, decisions, scope
Tech LeadZ3, B, CReview kiến trúc, impact, data model
Backend DevC (Technical Notes), D (Files)Implement Go handlers, migrations, Hasura metadata
Frontend DevA5 (FRs + wireframes trong conversation), D (Files)Implement UI components
QAA5 (Acceptance Criteria), Z4Viết test cases dựa trên AC
BODA2, A3, D (Phase Roadmap)Overview, metrics, timeline

Companion Files

FileNội dungTrạng thái
UI SpecPermission Matrix, State Matrix, ASCII Wireframes, Edge Cases, Copy Text, Notification Spec, Export Spec✅ Done
Dev SpecFull SQL migrations, Hasura YAML, API contracts, NFR, Traceability Matrix✅ Done
QA Test Plan76 test cases, Seed data, Entry/Exit criteria✅ Done
Go-Live ChecklistPer-phase deploy steps, Rollback plan, Day-0/Day-1 monitoring✅ Done
Perf-fix Campaign DetailPrerequisite — 3 tasks tối ưu hiệu năng trang chi tiết chiến dịch✅ Done

⚠️ Prerequisite: Perf-fix Campaign Detail PHẢI deploy trước Phase 2 và Phase 4. Trang chi tiết chiến dịch hiện bị chậm (2× function execution mỗi lần đổi trang) — perf-fix giảm 6-8x, đảm bảo 2 tab mới (Thống kê NV + Chu kỳ) hoạt động mượt.


Changelog

VersionNgàyThay đổi
1.02026-03-16Initial draft — 4 phases, reviewed by Tech Lead
1.12026-03-16Fix review: thêm schema voucher_activation_overrides, audit fields cho voucher_campaign_affiliates, RACI, reason_code enum, timezone spec, security section, companion files plan
1.219/03/2026Bổ sung Dev Spec, QA Test Plan, Go-Live Checklist, Handoff. Thêm perf-fix prerequisite reference