Skip to content

v1.11 — 15/05/2026

Thay đổiSectionẢnh hưởng
Đảo DEC-031 — từ Option C (screen riêng /wallet/promotion2) sang Option A (thêm tab Ví khuyến mãi 2 thứ 3 vào wallet_screen.dart cùng cấp 2 tab hiện hữu). Lý do: đồng nhất UX khách — 1 entry point "Ví của tôi"; pattern parity với KM 1 hiện có. Note kỹ thuật: dev tự chốt widget (refactor FolderTabs 3 tab / swap CustomTabbar / swap TabPageWidget — cả 3 đã có trong core/)Z) Nhật ký quyết địnhFE Mobile, QA
Thêm SCR-06-NEW-03 (web admin) — popup mới StatisticWalletPromotion2Popup.tsx lịch sử giao dịch Ví KM 2 (parity với popup KM 1 hiện có StatisticWalletPromotionPopup.tsx). Option α: tạo file riêng, không refactor popup KM 1 thành generic(ui-spec B0.1, B2.6.6A)FE Web, QA
Update dev-spec C11: chia P2-MOBILE-04 thành 4 subtask (P2-MOBILE-04A/B/C/D) — refactor tab + page mới + BLoC extend + balance card switch; thêm P2-01B/C (popup web admin)(dev-spec C11)Mobile, FE, PM
Update QA: 15 TC-SCR09 (thêm TC-SCR09-13 regression 2 tab cũ + TC-SCR09-15 regression FolderTabs các màn khác); 5 TC-006 mở rộng cho popup KM 2; tổng 137 TC(qa-test-plan D2, D4)QA

v1.10 — 15/05/2026

Thay đổiSectionẢnh hưởng
Chốt DEC-031 — Customer app Ví KM 2 dùng screen riêng route mới /wallet/promotion2 (Option C), KHÔNG refactor wallet_screen.dart hiện hữu (2 tab FolderTabs) [ĐÃ ĐẢO ở v1.11 — chốt Option A]Z) Nhật ký quyết địnhFE Mobile, QA
Chốt DEC-032 — Staff app AffiliateFor KHÔNG bổ sung WalletType.promotion2 (Option α) — danh sách ví CTV chọn nhận hoa hồng giữ nguyên [commission, promotion]Z) Nhật ký quyết địnhFE Mobile, BE
Sửa wireframe SAI (Home + Wallet) — Home hiện có 4 card (DIVA/Commission/Reward Point/Bonus Point, KHÔNG có ví KM 1); wallet_screen.dart hiện chỉ 2 tab FolderTabs(ui-spec B2.9.2)FE Mobile
Thêm 2 SCR mới: SCR-09-MOBILE-03 (WalletType enum + l10n shared); SCR-10-STAFF-01 (Staff customer detail balance KM 2)(ui-spec B0.1, B2.10)FE Mobile, QA
Thêm 9 task mobile vào dev-spec C11 Phase 2 (P2-MOBILE-01..09) — enum + l10n + screen mới + BLoC + home card + staff detail(dev-spec C11)Mobile, PM
Thêm 26 TC mobile vào QA (TC-SCR09-13/14 regression + TC-SCR09-ENUM-01..05 + TC-SCR10-01..07) — tổng 131 TC(qa-test-plan D2, D4)QA

v1.9 — 15/05/2026

Thay đổiSectionẢnh hưởng
Chốt DEC-030 — KM 2 inherit cơ chế affiliate_config của KM 1; migration seed 3 row (*, wallet_promotion_2, FALSE) đồng bộ default production hiện hành (không prescribe nghiệp vụ commission, để admin quản lý qua UI matrix)Z) Nhật ký quyết địnhBE, QA, Ops
Cập nhật ASM-005 — KM 2 áp dụng cùng logic rank/loyalty như KM 1 (không thêm ASM riêng cho commission)A6) Giả địnhAll
Cập nhật ASM-001 — khách XEM được KM2 trên app (không tự mua)A6) Giả địnhFE Mobile, QA
Mở PD-002 — KM2 in-scope Flutter customer app Phase 1: tab Ví KM 2 + home widget parity với KM 1 (SCR-09)Quyết định còn mởFE Mobile
Thêm 4 surface UI vào B-FR012: SMS template vnd_wallet_balance, NegativeInvoicesTable, AffiliateConfiguration matrix, SCR-09 mobile(ui-spec)FE, BE

v1.8 — 15/05/2026

Thay đổiSectionẢnh hưởng
Chốt DEC-027 — lot KHÔNG có trạng thái pendingZ) Nhật ký quyết địnhAll
Chốt DEC-028 — schema wallet_km2_payment_attempt merge audit fields + lock primitivesZ) Nhật ký quyết địnhBE, QA
Chốt DEC-029 — balance realtime qua action get_customer_km2_balanceZ) Nhật ký quyết địnhBE, FE
Update FORMULA-001 dùng available_amount realtime (KHÔNG dùng wallet.amount)A10) Công thức nghiệp vụBE, FE
Rename 4 field config canonical (max_percent_per_order, allow_combine_km1, refund_fee_percent, refund_deadline_days)A5) Yêu cầu chức năng (FR/AC)FE, QA
Lint vietnamese clean: chuẩn hoá calque headings + định dạng số tiền VN locale (chi tiết xem lint-vietnamese.sh output)Toàn fileNone

Ví Khuyến Mãi 2 (Promotion Wallet 2)

Phiên bản: 1.11 Ngày: 15/05/2026 Tác giả: PO/BA Loại: Tính năng mới Độ phức tạp: L Module: wallet, ecommerce, settings, user


File này dùng để làm gì: chốt bức tranh business, scope, decision, FR và formulas ở mức PO/BA/TL/QA. Nên đọc trước: decision-brief.md -> EVIDENCE_PACK.md -> SOURCE_OF_TRUTH.md -> Tóm tắt điều hành -> Z) Nhật ký quyết định -> A5) Yêu cầu chức năng.

Quy tắc ưu tiên: nếu nội dung file này xung đột với SOURCE_OF_TRUTH.md, ưu tiên SOURCE_OF_TRUTH.md.

Lịch sử thay đổi

Phiên bảnNgàyTác giảThay đổi
1.1115/05/2026PO/BAĐảo DEC-031: từ Option C (screen riêng /wallet/promotion2) → Option A (thêm tab Ví khuyến mãi 2 thứ 3 vào wallet_screen.dart cùng cấp 2 tab hiện hữu — đồng nhất UX khách). Note kỹ thuật: dev tự chốt widget (refactor FolderTabs 3 tab / swap CustomTabbar / swap TabPageWidget). Thêm SCR-06-NEW-03 (web admin) — popup mới StatisticWalletPromotion2Popup.tsx lịch sử giao dịch Ví KM 2 parity với popup KM 1 (Option α — file riêng). Cập nhật dev-spec C11: chia P2-MOBILE-04 thành 4 subtask + thêm P2-01B/C (popup web admin); QA thêm TC-SCR09-13/15 regression + 5 TC-006 mở rộng (tổng 137 TC).
1.1015/05/2026PO/BAAudit mobile gap (Flutter customer + staff app): phát hiện 2 wireframe SAI ở SCR-09 (Home thực tế 4 card không có ví KM 1; wallet_screen.dart thực tế chỉ 2 tab FolderTabs); chốt DEC-031 (Option C — screen riêng /wallet/promotion2, KHÔNG refactor wallet_screen.dart) [ĐÃ ĐẢO ở v1.11]; chốt DEC-032 (Option α — AffiliateFor KHÔNG bổ sung promotion2, danh sách CTV nhận hoa hồng giữ [commission, promotion]). Thêm 2 SCR mới: SCR-09-MOBILE-03 (enum + l10n shared) và SCR-10-STAFF-01 (Staff customer detail balance KM 2). Cập nhật dev-spec C11 thêm 9 task mobile (P2-MOBILE-01..09); QA thêm 26 TC mobile (tổng 131 TC).
1.915/05/2026PO/BAAudit gap so với ví KM 1 (87 file FE + 114 file BE + Flutter customer): chốt DEC-030 (KM 2 inherit cơ chế affiliate_config của KM 1 — seed 3 row FALSE đồng bộ production hiện hành); cập nhật ASM-001 (khách XEM được KM2 trên app), ASM-005 (KM 2 cùng logic rank như KM 1); mở PD-002 (SCR-09 mobile Phase 1); bổ sung 4 surface UI vào B-FR012: SMS template vnd_wallet_balance, NegativeInvoicesTable, AffiliateConfiguration matrix, SCR-09 mobile customer app. Spec không prescribe nghiệp vụ commission cho ví — đó là quyết định vận hành admin. Xem discussion-commission-affiliate.md.
1.815/05/2026PO/BAPhase 5 review fix-up: chốt DEC-027 (lot không có pending), DEC-028 (schema wallet_km2_payment_attempt merge audit + lock primitives), DEC-029 (balance realtime qua action get_customer_km2_balance); rename 4 field config canonical theo tên Dev; sửa lint vietnamese (calque + format ngàn + heading); thêm B0.9 Bảng kiểm kê tương tác; bổ sung TC-IDEMPOTENT-01..09 + TC-BALANCE-RACE-01..05 vào QA. PRD content business không đổi.
1.730/04/2026PO/BATham chiếu UI Spec v2.0 mới (B-PRE Discovery + B0.4 Field × Surface + B0.5 State × Screen + B0.6-0.8 wireframe/bilingual/schema + B2.x.1A UX nguyên tắc + B2.x.2.0 Variant + B2.x.7B/C/D Form/Concurrency/Network + B2.x.8A Role × Variant + B2.x.10A Error Taxonomy + B5A/B6A/B7A/B8A/B/C contract chuyên sâu + B-Microcopy/B-Voice/B-i18n/B-Versioning/B-Help + 12 nhóm trường hợp cá biệt + B-POST verification + B-QUALITY 45 rủi ro). PRD content business không đổi.
1.628/04/2026PO/BACập nhật Dynamic Permission v2; chốt behavior refund_km2_wallet và luồng Hoàn ví KM2 reuse Yêu cầu hoàn tiền hiện có
1.528/04/2026PO/BAFix sau review đa góc nhìn: đồng bộ Decision Log với SOURCE_OF_TRUTH.md v1.4 và giữ DEC-013..DEC-024 là registry chuẩn cho traceability
1.428/04/2026PO/BAĐồng bộ PRD với template mới nhất: heading tiếng Việt-first, bỏ RACI/sign-off chi tiết khỏi PRD, giữ RACI/timeline canonical ở handoff.md
1.328/04/2026PO/BAFix sau review đa góc nhìn: chốt seed mặc định tắt trước go-live, sửa permission visibility/RLS, polish tiếng Việt-first
1.228/04/2026PO/BAFix theo po-ba-workflow mới: thêm Decision Brief/Go-Live, nâng profile L, đồng bộ route/default config/idempotency/traceability
1.121/04/2026PO/BACanonicalized package: bổ sung EVIDENCE_PACK + SOURCE_OF_TRUTH, review lại theo codebase hiện tại
1.024/03/2026PO/BAInitial

Đầu vào chuẩn (Canonical Inputs)

FileVai trò
EVIDENCE_PACK.mdGhi lại bằng chứng code/screen/config thật, tránh tự suy diễn
SOURCE_OF_TRUTH.mdKhóa nguồn sự thật chuẩn và khóa giải pháp cho toàn package
decision-brief.mdCửa vào package cho PO/TL/Sếp; chỉ tóm tắt và trỏ sang file chịu trách nhiệm

Hướng dẫn đọc (người đọc / trách nhiệm)

Người đọcĐọc phần nàoTrách nhiệm
PO/BAPRD: A0 (tổng quan) → Z → A5 (FR) → A10 (công thức). Đặc tả UI: B0-B1 (kiểm kê + bản đồ màn hình)Duyệt yêu cầu nghiệp vụ và luồng UX
UI/UX DesignerPRD: A0 (kiến trúc) → A5 FR-012 (màn hình ảnh hưởng). Đặc tả UI: B0-B9 + B-FR012 (delta màn hình hiện có)Thiết kế màn mới (SCR-01,06,07,08) và rà delta (FR-012)
Tech LeadPRD: A0 → Z (quyết định). Đặc tả kỹ thuật: C1-C4 (phạm vi, ảnh hưởng, mô hình dữ liệu)Duyệt kiến trúc và rủi ro triển khai
FE DevĐặc tả UI: B2.1-B9 + B-FR012. Đặc tả kỹ thuật: C5-C6 (API, components)Triển khai UI cho màn mới và delta
BE DevĐặc tả kỹ thuật: C1-C12. PRD: A10 (công thức)Triển khai backend
QAPRD: A5 (FR + AC). Kế hoạch kiểm thử: D1-D5Viết và chạy ca kiểm thử

Tóm tắt điều hành

Ví KM 2 là cơ chế khuyến mãi theo gói nạp ưu đãi: khách mua Gói Ví KM2 với giá nhỏ → được cộng số tiền lớn vào ví → mỗi lần dùng dịch vụ chỉ được trả tối đa X% đơn hàng. Tái sử dụng flow thẻ trả trước hiện có, thêm wallet type mới VND_PROMOTION_2 với theo dõi theo từng lần mua, FIFO deduction và auto-expiry.

Tóm tắt bàn giao

Timeline chi tiết và RACI chuẩn nằm ở handoff.md; cổng phát hành và rollback chuẩn nằm ở go-live-checklist.md.

MốcMục tiêuPhụ tráchĐiều kiện
Phase 1 — Core (bán Gói Ví KM2 + thanh toán)T+15 ngàyBE + FE Dev
Phase 2 — Customer-facing (profile + refund)T+25 ngàyBE + FE DevSau Phase 1 deploy
Phase 3 — Report dashboardT+35 ngàyBE + FE DevSau Phase 2 deploy

Quyết định còn mở

PDCâu hỏiLựa chọn / giả địnhPhụ tráchHạn chótTrạng tháiKết luận
PD-001Vị trí Report dashboard trong menuA) Module Report B) Settings → Ví KM 2 C) Module WalletPOTrước Phase 3Mở
PD-002App khách Flutter hiển thị KM 2A) In-scope Phase 1 — full parity với KM 1 (tab Ví KM 2 + home widget); B) Defer Phase 2POTrước Phase 1 freezeĐã chốtA — In-scope Phase 1; spec ở SCR-09; backlog bỏ row "khách tự mua app" vì khách vẫn không tự mua (chỉ xem)

Backlog giai đoạn sau (ngoài phạm vi hiện tại)

#Tính năngLý do defer
1% max per Gói Ví KM2 (thay vì chung)Hiện chỉ 1 config chung. Nâng cấp khi có nhiều Gói Ví KM2 với chiến lược khác nhau
2Push notification nhắc hết hạnPhase 1-2 chỉ hiển thị text trong profile. Push notification cần tích hợp OneSignal
3Khách tự mua Gói Ví KM2 trên appHiện chỉ NV bán tại POS (ASM-001). Phase 1 app khách chỉ XEM (SCR-09); flow checkout in-app cần Phase 2+
5Cấu hình promotion_wallet_2_percent per tierDEC-030 dùng cùng % với KM 1; nếu Diva muốn % riêng cho Gói KM 2 → Phase 2 thêm cột policy_tag.promotion_wallet_2_percent
6UI mới commission_enabled per Gói (toggle ở SCR-02)Hiện dùng affiliate_config matrix admin-level đủ kiểm soát; nếu cần granular per Gói → Phase 2
4Route/menu report dashboardPD-001 chốt trước Phase 3, không block Phase 1/2

Z) Nhật ký quyết định

IDNhómQuyết địnhLý doNgàyTrạng thái
DEC-001Kỹ thuậtTạo loại ví riêng VND_PROMOTION_2, không mở rộng VND_PROMOTION(A) Mở rộng VND_PROMOTION: trộn 2 nghiệp vụ, mọi query phải thêm điều kiện phân loại, rủi ro hồi quy KM1. (B) Loại ví riêng: tách biệt hoàn toàn, không ảnh hưởng production. Chọn B.24/03/2026Đã khóa
DEC-002Nghiệp vụNhiều Gói Ví KM2, khác giá bán + giá trị nạp + hạn sử dụng(A) 1 Gói Ví KM2 cố định: đơn giản nhưng không linh hoạt. (B) Nhiều loại: spa tạo Silver/Gold/Diamond với ưu đãi khác nhau. Chọn B — linh hoạt hơn, admin quản lý.24/03/2026Đã khóa
DEC-003Nghiệp vụ% max chung cho cả Ví KM 2, không cấu hình riêng theo từng Gói Ví KM2(A) Theo từng Gói Ví KM2: Silver 20%, Gold 40%. Phức tạp khi khách có nhiều lần mua với % khác nhau. (B) Chung: 1 config admin. Chọn B — đơn giản, nhất quán. Nâng cấp sau nếu cần.24/03/2026Đã khóa
DEC-004Kỹ thuậtMỗi lần mua Gói Ví KM2 tạo 1 bản ghi wallet_km2_lot riêng, FIFO, có hạn sử dụng. Qty=N → N bản ghi(A) Cộng dồn 1 ví: đơn giản nhưng không theo dõi được từng lần mua, khó gắn hạn sử dụng. (B) Có bản ghi theo từng lần mua: rõ hạn sử dụng, rõ số dư, hỗ trợ FIFO. Chọn B — UI vẫn hiển thị như 1 ví tổng; engine bên trong dùng lot.24/03/2026Đã khóa
DEC-005Nghiệp vụHết hạn = mất số dư(A) Mất luôn: tạo urgency. (B) Chuyển sang KM1: khách vẫn dùng, nhưng thay đổi bản chất tiền. (C) Gia hạn: phức tạp. Chọn A — phổ biến trong ngành, khuyến khích khách quay lại sớm.24/03/2026Đã khóa
DEC-006UXCảnh báo hết hạn = nội dung cảnh báo trong tab Ví KM 2, không gửi thông báo đẩy(A) Thông báo đẩy: cần tích hợp OneSignal, phức tạp. (B) Nội dung cảnh báo trong hồ sơ khách: đơn giản, NV thấy khi mở khách. Chọn B — Phase 1-2 đủ dùng. Thông báo đẩy đưa vào backlog.24/03/2026Đã khóa
DEC-007Kỹ thuậtTái sử dụng luồng thẻ trả trước, thêm wallet_target để rẽ nhánh(A) Tái sử dụng: 80% code có sẵn, NV không cần học lại. (B) Luồng mới: tách biệt nhưng dev gấp đôi. (C) Module riêng: quá mức cần thiết, trùng logic. Chọn A.24/03/2026Đã khóa
DEC-008Nghiệp vụToggle KM1+KM2: tắt = chỉ dùng 1 trong 2, bật = cả 2 cùng đơn(A) Luôn cho phép cả 2: rủi ro double discount. (B) Toggle: admin kiểm soát. Chọn B — mặc định tắt, bật khi spa muốn ưu đãi mạnh.24/03/2026Đã khóa
DEC-009Nghiệp vụFlag allow_promo_wallet_2 theo từng sản phẩm/dịch vụ(A) Không giới hạn: mọi sản phẩm đều dùng KM2. (B) Flag theo từng sản phẩm: giống KM1, dễ kiểm soát. Chọn B — nhất quán với pattern KM1.24/03/2026Đã khóa
DEC-010Nghiệp vụHoàn tiền đơn dịch vụ: hoàn vào bản ghi mua cũ, bản ghi hết hạn thì gia hạn, bản ghi đã hoàn thì tạo bản ghi mới(A) Hoàn KM1 như phương án dự phòng: thay đổi bản chất tiền. (B) Hoàn vào bản ghi mua cũ + gia hạn: nhất quán và công bằng. (C) Tạo bản ghi mới khi bản ghi gốc đã hoàn. Chọn B+C — dùng audit trail (deduction records) để hoàn chính xác.24/03/2026Đã khóa
DEC-011Nghiệp vụHoàn ví KM2: tỉ lệ hoàn (giá mua gói/giá trị ví) + phí xử lý hoàn ví có thể cấu hình(A) Hoàn toàn bộ bằng tiền mặt: spa lỗ (300k mua → 3tr còn dư → hoàn 3tr?). (B) Tỉ lệ hoàn + phí xử lý hoàn ví: công bằng cho cả spa và khách. (C) Không cho hoàn: dễ gây phản ứng. Chọn B.24/03/2026Đã khóa
DEC-012Kỹ thuậtAudit trail wallet_km2_lot_deduction cho mọi thao tác trừ/hoàn(A) Không có audit: khi hoàn phải suy ngược FIFO, dễ sai. (B) Có audit: truy đúng bản ghi đã trừ và số tiền đã trừ. Chọn B — truy vết đầy đủ.24/03/2026Đã khóa
DEC-013Nghiệp vụNhân sự có quyền xem thông tin khách được xem Ví KM2 khi truy cập profile khách(A) Chỉ nhóm quyền cao: an toàn nhưng NV không tư vấn được. (B) Theo quyền customer_management:access + branch scope: nhất quán với quyền xem thông tin khách hiện tại. Chọn B.24/03/2026Đã khóa
DEC-014Nghiệp vụKhách mua Gói Ví KM2 ở chi nhánh A, dùng được ở bất kỳ chi nhánh nào(A) Chỉ dùng ở chi nhánh mua: hạn chế. (B) Dùng khác chi nhánh: linh hoạt cho khách. Chọn Bbranch_id trên bản ghi mua chỉ phục vụ báo cáo.24/03/2026Đã khóa
DEC-015Kỹ thuậtexpired_at lưu dạng end_of_day (23:59:59) ngày cuối cùng(A) Timestamp chính xác: cron có thể miss. (B) End-of-day: cron 00:05 luôn bắt đúng. Chọn B.24/03/2026Đã khóa
DEC-016Kỹ thuậtSnapshot giá mua gói vào wallet_km2_lot (trùng prepaid_card info)(A) Join trực tiếp prepaid_card: đơn giản nhưng vướng cross-DB wallet↔ecommerce, Hasura không hỗ trợ cross-DB join. Nếu Admin sửa giá sau khi bán → dữ liệu cũ sai. (B) Snapshot vào wallet_km2_lot: thêm cột package_name, package_price, wallet_value. Giải quyết cả cross-DB lẫn tính toàn vẹn dữ liệu. Chọn B.24/03/2026Đã khóa
DEC-017Nghiệp vụKHÔNG cho mua Gói Ví KM2 bằng trả góp (installment)(A) Cho phép: khách trả góp 500k/3 tháng nhưng ví đã có 5tr ngay → rủi ro khách dùng hết rồi ngưng trả góp. (B) Không cho phép: ẩn lựa chọn trả góp khi đơn có Gói Ví KM2. Chọn B — rủi ro tài chính quá lớn.24/03/2026Đã khóa
DEC-018Kỹ thuậtFIFO deduction dùng Go action handler với direct PostgreSQL connection (không qua Hasura GraphQL)(A) Hasura mutations: không atomic, partial deduction gây inconsistency. (B) Raw SQL transaction trong Go handler: BEGIN; SELECT FOR UPDATE; UPDATE lots; INSERT deductions; UPDATE wallet; COMMIT. Chọn B — đảm bảo atomicity.24/03/2026Đã khóa
DEC-019Nghiệp vụThanh toán nhiều lần cho Gói Ví KM2: tiền vào ví theo tỉ lệ đã trả, bản ghi mua tạo từ lần trả đầu tiên(A) Chỉ tạo bản ghi khi trả đủ 100%: đơn giản nhưng khách không thấy giá trị ví khi trả một phần. (B) Tạo bản ghi ngay lần trả đầu, số dư tăng dần: giống luồng thẻ trả trước hiện tại, hạn sử dụng bắt đầu từ lần trả đầu. Chọn B.24/03/2026Đã khóa
DEC-020Bàn giaoPackage vận hành theo profile L vì chạm wallet/payment, scheduler, report/fund/print và rollback(A) Giữ M: thiếu cổng go-live cho tiền/ví. (B) Nâng L: thêm checklist phát hành, rollback, monitoring. Chọn B.28/04/2026Đã khóa
DEC-021UX/Cấu hìnhSettings KM2 dùng lại route hiện có /s/internal-settings/promotion-wallet; report route là PD-001 cho Phase 3(A) Tạo route settings/report mới ngay: nhanh ghi spec nhưng lệch code/menu hiện có. (B) Dùng lại màn settings hiện có và để report route cho PO chốt. Chọn B.28/04/2026Đã khóa
DEC-022Kỹ thuậtFIFO deduction phải có idempotency key và không trừ lot đã hết hạn dù scheduler miss(A) Chỉ dùng DB lock: chống race nhưng chưa chống retry/double submit và expired miss. (B) Thêm idempotency + expiry guard trong transaction. Chọn B.28/04/2026Đã khóa
DEC-023Vận hành/Cấu hìnhMigration seed cấu hình KM2 ở trạng thái tắt; PO/Admin chỉ bật ở go-live sau khi kiểm tra sẵn sàng đạt(A) Seed bật ngay: nhanh nhưng rủi ro phương thức thanh toán xuất hiện trước khi seed/test đủ. (B) Seed disabled=true, bật ở E4.7: an toàn hơn cho tính năng ví/tiền. Chọn B.28/04/2026Đã khóa
DEC-024Bảo mậtCross-branch usage không đồng nghĩa với cross-branch visibility qua GraphQL(A) Role user đọc mọi lot/deduction: dễ dev nhưng lộ dữ liệu. (B) UI/GraphQL giữ Staff/Manager/Admin scope; Go action dùng service permission để redeem cross-branch. Chọn B.28/04/2026Đã khóa
DEC-025Bảo mật/PermissionKM2 áp dụng Dynamic Permission v2, không hard-code role nameQuyền thực tế phải lấy từ role_module.actions + portal + branch_mode + backend enforcement. Role chỉ là default seed; grant/revoke qua Dynamic Permission UI phải ảnh hưởng menu/button/API sau refresh/relogin.28/04/2026Đã khóa
DEC-026Nghiệp vụ/Hoàn tiềnDùng refund_km2_wallet, nhãn "Hoàn ví KM2", dùng lại luồng Yêu cầu hoàn tiền hiện cóTránh lẫn với CRM case. KM2 chỉ thêm loại xử lý mới vào engine Yêu cầu hoàn tiền hiện có; không dựng module duyệt riêng.28/04/2026Đã khóa
DEC-027Kỹ thuật/Lifecyclewallet_km2_lot KHÔNG có trạng thái pending. ENUM chỉ gồm active/exhausted/expired/refunded. Lot chỉ được tạo khi khách trả ≥ 1 đồng cho đơn nạp Gói Ví KM2(A) Có pending: tạo lot ngay khi tạo prepaid_order để track sớm, nhưng phải xử lý lot rác khi đơn huỷ, schema activated_at phải NULL → break dev-spec, thêm state cần test/migrate. (B) Không pending: lot chỉ tồn tại khi có thanh toán thật, đơn còn nợ thì badge ở prepaid_order. Chọn B — đơn giản state machine, không lot dangling, đồng bộ với DEC-019. Đơn còn nợ hiển thị qua prepaid_order.status='pending' ở SCR-03 và inline "Đã nạp X/Y" trong cột "Đã nạp" của SCR-06.15/05/2026Đã khóa
DEC-028Kỹ thuật/SchemaSchema wallet_km2_payment_attempt chốt merge: PK id UUID riêng + UNIQUE payment_attempt_id UUID; có đủ lock_holder, lock_expires_at, requested_at, error, request_hash; status enum processing/completed/failed/expired; cross-check verify (order_id, customer_id, user_id) chống bypass FE(A) Schema cũ (composite (order_id, payment_attempt_id) UNIQUE, status in_progress/succeeded/failed): không cover lock TTL → fail TG-003 test scenarios. (B) Schema mới chỉ có lock primitives nhưng thiếu Diva audit fields: vi phạm pattern. (C) Merge: lấy điểm mạnh cả hai — Diva pattern (id, deleted_at, created_by/updated_by) + lock primitives (TG-003) + cross-check verify (chống bypass). Chọn C — không thay nghiệp vụ, khoá TG-003 gate, không phải refactor schema sau Phase 1.15/05/2026Đã khóa
DEC-029Kỹ thuật/Nguồn balanceBalance Ví KM 2 cho UI auto-fill + chip eligibility check lấy realtime từ action get_customer_km2_balance (SUM(lot.balance) WHERE status='active' AND expired_at > NOW()), KHÔNG đọc wallet.amount trực tiếp. wallet.amount chỉ là cached value cho report/KPI/dashboard(A) Dùng wallet.amount: đơn giản nhưng có race window giữa lot expired và scheduler 00:05 → UI hiển thị 5tr, BE deduct chỉ được 3tr → sai tiền + UX bug. (B) Sync wallet.amount realtime qua trigger: phức tạp, ảnh hưởng pattern wallet hiện có. (C) Action realtime: đóng race window vĩnh viễn, reuse pattern Diva (get_customer_km2_lots đã có), không phải refactor wallet.amount. Chọn C — match DEC-005 "hết hạn = mất số dư" enforce realtime, không phụ thuộc scheduler.15/05/2026Đã khóa
DEC-030Kỹ thuật/AffiliateKM 2 áp dụng cùng cơ chế affiliate_config matrix như KM 1 (gate ở InsertInvoiceAffiliateservices/ecommerce-api/event/invoice_insert_update.go:1287-1298). Migration KM 2 seed 3 row `(servicecosmeticprepaid, wallet_promotion_2, FALSE)— giá trịFALSEđồng bộ với default production hiện hành của Diva chowalletwallet_promotion(cả 6 ô đang FALSE — xem screenshot UIAffiliateConfiguration.tsx). Admin có thể bật/tắt từng ô qua UI /s/internal-settings/affiliate` → tab "Cấu Hình Chung" → block "Cấu hình hình thức nhận hoa hồng".(A) Tạo cơ chế skip mới riêng cho KM 2 (hard-code trong code Go): tăng phức tạp, drift khỏi pattern hiện hữu, dead config UI. (B) Inherit cơ chế KM 1 — seed config FALSE đồng bộ với KM 1: không touch code Go, admin có quyền điều chỉnh, dùng UI matrix sẵn có. Chọn B. Spec KHÔNG prescribe nghiệp vụ "ví có/không có commission" — đó là quyết định vận hành của admin.
DEC-031UX/MobileCustomer Flutter app Ví KM 2 thêm tab thứ 3 vào wallet_screen.dart hiện có (cùng cấp với 2 tab hiện hữu: "Ví thẻ trả trước" + "Ví khuyến mãi"). Card Home "Ví khuyến mãi 2" (home_wallet.dart:49-77 — card thứ 5 trong list 4 card hiện có) tap → mở wallet_screen.dart với initialTabIndex trỏ tab Ví KM 2. KHÔNG tạo screen riêng / route mới. Note kỹ thuật cho dev (KHÔNG phải decision nghiệp vụ): wallet_screen.dart:213-227 đang dùng widget FolderTabs (2 tab cứng — core/lib/presentation/common_widget/folder_tabs.dart); để thêm tab thứ 3 dev tự chốt 1 trong 3 phương án implementation: refactor FolderTabs mở rộng 3 tab, hoặc swap sang CustomTabbar (chip pill) / TabPageWidget (Material TabBar) — cả 3 đã có trong codebase core/. Decision này thuộc dev/tech-lead, KHÔNG ràng buộc PO.(A) Thêm tab thứ 3 vào wallet_screen.dart: nhất quán UX khách — khách quen mở 1 màn "Ví của tôi" và switch giữa các ví; tap card home → mở wallet_screen ở đúng tab; phù hợp PD-002 "parity với pattern KM 1 hiện có". (B) Dropdown filter trong tab Promotion hiện có: phải tách logic balance theo lựa chọn dropdown; semantics rối (1 tab 2 loại ví khác nhau). (C) Screen riêng route mới: tách biệt nhưng KHÔNG nhất quán UX hiện hữu — khách phải nhớ thêm 1 entry point ngoài "Ví của tôi". Chọn A — đồng nhất UX, đồng bộ pattern KM 1.15/05/2026Đã khóa
DEC-032Nghiệp vụ/AffiliateApp staff (affiliate_for.dart:103-116) KHÔNG bổ sung WalletType.promotion2 vào danh sách ví CTV chọn nhận hoa hồng. Danh sách giữ nguyên [commission, promotion]. Khi admin bật affiliate_config(*, wallet_promotion_2, TRUE) qua UI matrix, BE vẫn ghi nhận hoa hồng vào ví commission mặc định của CTV (theo user.walletReceiveCommission đã chọn) — KHÔNG cho CTV chọn nhận hoa hồng VÀO ví KM 2.(α) Giữ nguyên [commission, promotion]: KM 2 chỉ là benefit khách (parity nghiệp vụ KM 1 — KM 1 cũng có wallet_promotion trong list nhưng business intent là "ví KM của CTV", không phải "ví KM khách"); Ví KM 2 là dạng "Gói trả trước" thuộc khách, không phù hợp làm ví commission của CTV. (β) Mở rộng thành [commission, promotion, promotion2]: cho CTV chọn nhận hoa hồng vào ví KM 2 — drift semantics (ví khách thành ví CTV), risk schema (chưa rõ lot ownership). Chọn α — semantics rõ ràng, không vỡ pattern hiện có; nếu Phase 2+ admin muốn CTV nhận vào KM 2 thì cân nhắc decision riêng.15/05/2026Đã khóa

A) PRD

A0) Tổng quan tính năng

Đọc section này trước — nắm toàn bộ feature trong 2 phút. Chi tiết xem các section A1-A10 bên dưới.

Ý tưởng cốt lõi

Khách mua Gói Ví KM2 (VD: 500k) → được 5 triệu vào Ví KM 2 → mỗi lần dùng dịch vụ hệ thống chỉ cho trả tối đa X% đơn hàng (VD: max 20%) → khách phải quay lại nhiều lần để dùng hết → tăng giữ chân khách và tạo cảm giác "đã có tiền trong ví" thay vì chỉ được giảm giá một lần.

Kiến trúc tổng thể

                    +-----------------------------------+
                    |         Cấu hình Admin              |
                    |  - Tạo Gói Ví KM2 (prepaid card)      |
                    |  - Cấu hình Ví KM2 (max%, toggle)  |
                    |  - Chính sách Hoàn ví KM2          |
                    +-----------------+-----------------+
                                      |
    +---------------------------------+--------------------------------+
    |                                 |                                |
    v                                 v                                v
+---------+               +--------------+              +--------------+
| Bán Gói Ví KM2  |               | Thanh toán   |              | Hoàn ví KM2  |
| (POS)   |               | đơn hàng     |              |   hoàn ví KM2    |
|         |               |              |              |              |
| Dùng lại|               | Logic max%   |              | Yêu cầu hoàn |
| đơn nạp |               | FIFO lot    |              | + loại KM2   |
| tiền    |               | Toggle KM1+2 |              |              |
+----+----+               +------+-------+              +------+-------+
     |                           |                              |
     v                           v                              v
+---------------------------------------------------------------------+
|                     Wallet Engine                                     |
|  wallet_type: VND_PROMOTION_2                                        |
|  wallet_km2_lot: theo dõi từng lần mua Gói Ví KM2, hạn dùng, FIFO         |
|  wallet_km2_lot_deduction: audit trail từng lần trừ/hoàn                 |
|  transaction / transaction_request: ghi log như các ví khác          |
|  Expiry Scheduler: Hasura cron trigger đánh dấu bản ghi hết hạn           |
+---------------------------------------------------------------------+

Mapping sang tài liệu chi tiết:

KhốiPRDĐặc tả UIĐặc tả kỹ thuật
Cấu hình AdminFR-001, FR-002SCR-01, SCR-02C4, C5, C8
Bán Gói Ví KM2 (POS)FR-003SCR-03C5, C6
Thanh toánFR-004, FR-005SCR-04, SCR-05C3 FORMULA-001, C5
Hoàn tiền / Hoàn ví KM2FR-008, FR-009SCR-07C3 FORMULA-002, C5
Wallet EngineFR-007SCR-06C4, C7, C9

Quyết định chính (tóm tắt theo nhóm)

NhómQuyết địnhRef
Kiến trúcLoại ví riêng VND_PROMOTION_2 (tách biệt KM1). Tái sử dụng luồng thẻ trả trước + rẽ nhánh wallet_target. Snapshot giá mua gói vào bản ghi mua (tránh cross-DB + sai dữ liệu lịch sử). FIFO dùng Go action handler + direct PostgreSQL tx, idempotency và expiry guardDEC-001, DEC-007, DEC-016, DEC-018, DEC-022
Gói Ví KM2Nhiều gói bán cho khách (Silver/Gold/Diamond), khác giá + giá trị ví + hạn sử dụng. % max chung cho cả ví, không cấu hình riêng theo từng gói. KHÔNG cho mua trả gópDEC-002, DEC-003, DEC-017
Lần mua Ví KM2 (lot kỹ thuật)Mỗi lần khách mua 1 Gói Ví KM2 tạo 1 bản ghi wallet_km2_lot; qty=N → N bản ghi. FIFO trừ bản ghi cũ trước. Hết hạn = mất số dư. Audit trail cho mọi thao tác. Thanh toán nhiều lần: bản ghi tạo từ lần trả đầu, balance tăng dần theo tỉ lệDEC-004, DEC-005, DEC-012, DEC-019
Thanh toánMax X% trên eligible items. Toggle KM1+KM2 (tắt = chỉ 1 trong 2). Cross-branch OKDEC-003, DEC-008, DEC-009, DEC-014
Hoàn tiềnĐơn DV: hoàn vào bản ghi mua cũ (query audit trail), hết hạn → gia hạn, đã hoàn → tạo bản ghi mới. Hoàn ví KM2: tỉ lệ (giá mua gói / giá trị ví) + phí xử lý hoàn ví có thể cấu hìnhDEC-010, DEC-011
Bàn giao/Go-liveVì liên quan tiền/ví, package phải có Go-Live checklist, seed cấu hình mặc định tắt, rollback bằng cách tắt cấu hình/phương thức thanh toán và monitoring ngày đầuDEC-020, DEC-023
Quyền dữ liệuCross-branch usage chỉ xử lý trong Go action; UI/GraphQL vẫn giữ Staff/Manager/Admin scope theo quyền truy cập khách/branchDEC-014, DEC-024

Logic kế toán quan trọng

Khi mua Gói Ví KM2 — ghi nhận giống thẻ trả trước hiện tại:

Khách trả 500k mua Gói Gold → Thực thu = 500k (giá mua gói)
                             → Ví KM 2 = +5.000.000đ (toàn bộ vào KM2)
                             → Ví VND = 0đ, Ví KM 1 = 0đ
(Khác KM1: KM1 chia base_value→VND + bonus→KM1. KM2: toàn bộ vào KM2)

Khi dùng KM2 thanh toán dịch vụ — giống wallet/wallet_promotion:

Đơn 1tr, eligible 700k, max 20%
→ KM2 trả 140k (không tính thực thu, không tính customer points)
→ Tiền mặt 860k (tính thực thu bình thường)
→ Doanh số = 1.000.000đ | Thực thu = 860,000đ

Bảng FR tóm tắt

FRMô tảMức ưu tiênGiai đoạn
FR-001Config Ví KM 2 (max%, toggle, refund policy)Must1
FR-002Tạo/sửa Gói Ví KM2 (thêm wallet_target + expiry)Must1
FR-003Bán Gói Ví KM2 → tạo bản ghi theo từng lần mua (reuse flow nạp tiền, conditional cột KM2)Must1
FR-004Thanh toán bằng KM2 (auto-fill max%, FIFO, deduction records)Must1
FR-005Flag allow_promo_wallet_2 per sản phẩm/dịch vụMust1
FR-006Tab Ví KM 2 trong profile khách (danh sách Gói Ví KM2 đã mua, cảnh báo hết hạn)Must2
FR-007Scheduler tự động hết hạn lot (cron daily)Must1
FR-008Refund đơn DV → hoàn vào lot (query deduction records)Should2
FR-009Hoàn ví KM2 qua Yêu cầu hoàn tiềnShould2
FR-010Report dashboard (KPI, top khách, Gói Ví KM2 sắp hết hạn)Could3
FR-011ZNS thông báo (kích hoạt Gói Ví KM2, nhắc hết hạn)Could2+
FR-012Hiển thị Ví KM 2 trong đơn hàng hiện có (summary, bảng, hoá đơn in)Must1

Mô hình dữ liệu tóm tắt

BảngLoạiMục đích
wallet_km2_lotMỚIBản ghi theo từng lần mua Gói Ví KM2: balance, expiry, status, branch_id
wallet_km2_lot_deductionMỚIAudit trail mỗi lần trừ/hoàn: lot_id, order_id, amount, type
wallet_km2_configMỚIConfig chung: max%, toggle KM1+KM2, refund policy
prepaid_cardSỬA+wallet_target, +expiry_months
product / serviceSỬA+allow_promo_wallet_2
wallet_type + payment_gatewaySEEDINSERT VND_PROMOTION_2 + wallet_promotion_2

Ảnh hưởng code hiện tại

  • 2 hàm Go High-risk: CheckWalletBalanceEnough + GetWalletTypeFromPaymentMethod (hardcode wallet types)
  • 10+ files hardcode "wallet_promotion" cần grep + review (xem dev-spec C2)
  • Invoice struct thiếu field WalletPromotionAmount — bug tiềm ẩn, fix luôn Phase 1

Ma trận ảnh hưởng tính năng liên quan

Nguồn chuẩn chi tiết nằm ở SOURCE_OF_TRUTH.md mục 5.2-5.3. PRD chỉ chốt hành vi PO/BA cần duyệt để team không mở rộng scope sai.

Tính năng hiện cóẢnh hưởng?Bổ sung bắt buộc cho KM2Không ảnh hưởng / không đổi
Ví khuyến mãi hiện tại (KM1)Có, vì shared wallet/payment/report codeThêm wallet/payment method KM2 riêng, label/report riêngKhông đổi số dư, công thức, dữ liệu KM1
Settings Ví khuyến mãiMở rộng page hiện có với config KM2, readiness và default disabledKhông tạo route settings mới Day-1
Thẻ trả trước / đơn nạp tiềnThêm loại Gói Ví KM2, wallet_target, expiry_months, summary "Tiền vào Ví KM2", cấm trả gópKhông đổi thẻ VND/KM1
Thanh toán đơn DV/mỹ phẩmThêm payment method KM2, max%, eligibility, FIFO deduction, idempotencyKhông đổi cash/card/bank/wallet/KM1
Product/Service detailThêm allow_promo_wallet_2 riêngKhông overload allow_promo_wallet của KM1
Customer profileThêm tab/card/danh sách Gói Ví KM2 đã mua, expiry warning, CTA "Hoàn ví KM2" theo quyềnKhông mở quyền xem khách ngoài scope
Yêu cầu hoàn tiềnCó, bắt buộcThêm option "Hoàn ví KM2", behavior refund_km2_wallet, field lot_id, km2_deduct_amount, suggested_refund_amount, refund_method, policy_snapshot; approve/payment đi qua flow hiện cóKhông tạo approval module riêng; không dùng tên legacy cũ
Refund đơn đã dùng KM2Hoàn lại đúng lot dựa trên wallet_km2_lot_deduction; hết hạn thì gia hạn, lot refunded thì tạo lot mớiKhông hoàn về ví chính/KM1
Hoá đơn, quỹ, actual revenueThêm amount/label KM2; KM2 paid amount không tính thực thu/fund, bán Gói Ví KM2 tính thực thu theo giá mua góiKhông cộng value_into_wallet vào doanh thu
Báo cáo/exportThêm cột/field KM2 riêng cho prepaid, DV/NV, actual revenue, ERP/exportKhông đổi nghĩa wallet_promotion_amount của KM1
Notification/ZNS/SMSCó điều kiệnThêm biến/template KM2 cho nạp Gói Ví KM2, thanh toán, expiry nếu bật thông báoKhông reuse biến KM1 cho KM2
Rank/loyalty, appointment, kho, affiliate/referral, CMS gift/event, HR cơ bảnKhông trực tiếpChỉ kiểm tra skip list doanh thu nếu module đọc payment methodKhông thêm rule nghiệp vụ mới cho các module này

Tóm tắt giai đoạn

Timeline chi tiết, RACI và blocker chuẩn nằm trong handoff.md; readiness/deploy/rollback chuẩn nằm trong go-live-checklist.md.

PhaseNội dungĐiều kiện chuyển phase
Phase 1 — CoreDB + bán Gói Ví KM2 + thanh toán + scheduler + config + FR-012 Phase 1Go-live E1/E3 pass, payment smoke test pass
Phase 2 — Customer-facingProfile khách + refund + hoàn ví KM2 + FR-012 Phase 2Phase 1 QA pass, không còn bug critical/major
Phase 3 — ReportDashboard báo cáoPO chốt PD-001 route/menu report

A1) Bản thiết kế tóm tắt

TrườngGiá trị
FeatureVí Khuyến Mãi 2 (Promotion Wallet 2)
LoạiNew Feature
PlatformWeb Admin (diva-admin) — POS + Admin
Module ảnh hưởngFE: settings, ecommerce, user / BE: wallet-api, ecommerce-api / DB: wallet, ecommerce

A2) Bối cảnh

Hiện trạng

  • Ví KM 1 (VND_PROMOTION): nạp bao nhiêu dùng bấy nhiêu, trừ thẳng 100% vào đơn hàng
  • Thẻ trả trước: mua thẻ → tiền chia vào Ví VND (base) + Ví KM 1 (bonus)
  • Không có cơ chế giới hạn % sử dụng per đơn, không có lot tracking, không có auto-expiry

Kỳ vọng

  • Ví KM 2 (VND_PROMOTION_2): mua Gói Ví KM2 → toàn bộ tiền vào Ví KM 2 (theo lần mua Ví KM2, có hạn sử dụng)
  • Mỗi lần thanh toán chỉ trừ tối đa X% đơn hàng eligible → khách quay lại nhiều lần
  • FIFO deduction across lots, auto-expiry, audit trail cho refund chính xác

Kiến trúc tổng thể, quyết định chính, logic kế toán, bảng FR, data model, timeline → xem A0) Tổng quan tính năng phía trên.

A3) Mục tiêu và chỉ số thành công

Mục tiêuChỉ sốMục tiêu đo
Tăng tần suất quay lạiSố lần visit/khách/tháng (khách có Gói Ví KM2)+30% so với khách không có Ví KM2
Thu trước tiềnDoanh thu bán Gói Ví KM2/tháng> 0 (baseline Phase 1)
Tăng retentionTỉ lệ sử dụng Ví KM2 (% đã dùng/tổng nạp)> 50% sau 3 tháng
Kiểm soát discount% discount thực tế per đơnKhông vượt max% config

A4) Chân dung người dùng

Chân dungVai tròViệc cần làm (JTBD)Tần suất
Khách hàngNgười dùng Ví KM2Muốn được ưu đãi khi dùng dịch vụ, cảm giác "xài tiền mình"Mỗi lần đến spa
NV bán hàng (Sale/Staff)Bán Gói Ví KM2 + thanh toánTư vấn khách mua Gói Ví KM2, thanh toán đơn hàng bằng Ví KM 2Hàng ngày
ManagerDuyệt refund + xem báo cáoKiểm soát hoạt động bán Gói Ví KM2, duyệt hoàn tiền/hoàn ví KM2Hàng tuần
AdminCấu hình hệ thốngTạo Gói Ví KM2, cấu hình max%, toggle, refund policyKhi cần thay đổi

A5) Yêu cầu chức năng

FR-001: Cấu hình Ví KM 2 (Ref: DEC-003, DEC-008)

Mức ưu tiên: Must | SCR: SCR-01

AC:

  • [ ] Admin vào Settings → Ví KM 2, bật/tắt Ví KM 2 (disabled toggle)
  • [ ] Admin nhập tối đa % trên đơn hàng (1-100, số nguyên, mặc định 20)
  • [ ] Admin bật/tắt toggle "Cho phép dùng KM1 + KM2 cùng đơn" (mặc định: tắt)
  • [ ] Default sau migration: config KM2 được seed ở trạng thái tắt (disabled=true). PO/Admin chỉ bật sang disabled=false trong go-live khi readiness pass
  • [ ] Default rule sau khi bật: max% = 20, không cho dùng KM1+KM2 cùng đơn, cho phép hoàn ví KM2, phí xử lý hoàn ví = 20%, thời hạn hoàn = 30 ngày, gia hạn refund DV = 30 ngày
  • [ ] Admin bật/tắt "Cho phép Hoàn ví KM2", nhập phí xử lý hoàn ví % (0-100), thời hạn hoàn (ngày), gia hạn lot khi refund DV (ngày)
  • [ ] Lưu config thành công → toast "Lưu cài đặt thành công"
  • [ ] Config áp dụng ngay lập tức cho mọi đơn hàng mới

FR-002: Tạo/sửa Gói Ví KM2 (Ref: DEC-002, DEC-007)

Mức ưu tiên: Must | SCR: SCR-02

AC:

  • [ ] Admin vào Settings → Thẻ trả trước → Tạo mới, thấy thêm radio "Ví đích": Ví VND (mặc định) / Ví KM 2
  • [ ] Khi chọn "Ví KM 2", hiện thêm field "Hạn sử dụng" (số tháng, bắt buộc, > 0)
  • [ ] Giá bán (value) và Giá trị vào ví (value_into_wallet) nhập độc lập, value_into_wallet >= value
  • [ ] Lưu thành công → thẻ xuất hiện trong danh sách với cột "Ví đích" = KM 2

FR-003: Bán Gói Ví KM2 qua flow thẻ trả trước (Ref: DEC-004, DEC-007)

Mức ưu tiên: Must | SCR: SCR-03

Luồng (theo layout UI thực tế — form "Tạo Mới Nạp Tiền"):

NV mở POS → Nạp Tiền → Tạo Mới Nạp Tiền


Chọn chi nhánh, nguồn đơn hàng, chiến dịch tiếp thị (giữ nguyên)


THÔNG TIN NẠP TIỀN — "+ Thêm thẻ nạp" (nhiều dòng, mỗi dòng 1 loại thẻ)

  │  LOẠI THẺ NẠP         │ SL │ CẦN TT   │ NẠP VÍ DIVA │ NẠP VÍ KM │ NẠP VÍ KM 2
  │  Thẻ trả trước 10tr   │ 1  │ 10,000k  │ 10,000k     │ 1,500k    │ 0
  │  Gói Gold (6 tháng)    │ 2  │ 1,000k   │ 0           │ 0         │ 10,000k
  │  TỔNG                  │    │ 11,000k  │ 10,000k     │ 1,500k    │ 10,000k

  │  ★ Cột "NẠP VÍ KM 2" chỉ hiện khi có ≥1 thẻ wallet_target = KM2


PHƯƠNG THỨC THANH TOÁN (sidebar phải):
  │  [Chuyển khoản] [Thanh toán thẻ] [Tiền mặt] [Trả góp]
  │  Tiền vào ví DIVA:         10.000.000đ
  │  Tiền vào ví khuyến mãi:    1,500.000đ
  │  Tiền vào ví KM 2:         10.000.000đ   ← chỉ hiện khi > 0
  │  Số tiền cần thanh toán:   11.000.000đ


Bấm hoàn thành → Backend xử lý theo từng dòng thẻ:

  ├─ Dòng 1 (wallet_target='VND') → flow cũ (chia VND + KM1)

  └─ Dòng 2 (wallet_target='VND_PROMOTION_2') → flow mới:
        ├─ Cộng 10.000.000đ vào wallet VND_PROMOTION_2
        ├─ Tạo 2 bản ghi mua riêng biệt (mỗi bản ghi 5tr, HSD +6 tháng)
        ├─ Gửi ZNS: "Kích hoạt 2 Gói Gold, tổng 10tr, HSD: 24/09/2026"
        └─ Commission NV → giữ nguyên flow hiện tại

AC:

  • [ ] NV tạo đơn nạp tiền, chọn Gói Ví KM2 từ dropdown (VD: Gói Gold 500k → 5.000.000đ)
  • [ ] NV chọn qty (VD: 2), hệ thống tính: CẦN TT = 1,000k, NẠP VÍ KM 2 = 10,000k
  • [ ] Cột "NẠP VÍ KM 2" chỉ hiện khi đơn có ≥1 thẻ có wallet_target = VND_PROMOTION_2
  • [ ] Sidebar: dòng "Tiền vào ví KM 2" chỉ hiện khi tổng > 0
  • [ ] Cho phép mix thẻ VND + Gói Ví KM2 trong cùng 1 đơn — mỗi dòng xử lý theo wallet_target riêng
  • [ ] NV thanh toán lần đầu → hệ thống tạo bản ghi mua ngay (mỗi Gói Ví KM2 1 bản ghi wallet_km2_lot, hạn sử dụng tính từ lần trả đầu tiên)
  • [ ] Thanh toán nhiều lần: tiền vào Ví KM 2 theo tỉ lệ đã trả (giống flow thẻ trả trước hiện tại)
  • [ ] VD: Gói Gold 500k → 5tr, qty=2, tổng 1,000k. Trả lần 1: 600k (60%) → ví nhận 6tr. Trả lần 2: 400k → ví nhận 4tr. Tổng 10tr ✓
  • [ ] Công thức per lần trả: km2_credit = (customer_paid_amount / total_order_amount) × total_value_into_wallet
  • [ ] Số dư bản ghi mua tăng dần theo mỗi lần trả. Trạng thái wallet_km2_lot.status = active từ lần trả đầu tiên
  • [ ] Gửi ZNS: "Bạn đã kích hoạt 2 Gói Gold, tổng 10.000.000đ, HSD: 24/09/2026" (gửi lần trả đầu tiên)
  • [ ] Commission NV tính bình thường trên giá mua gói (giống flow thẻ trả trước hiện tại)
  • [ ] Khi Gói Ví KM2 được chọn, cột NẠP VÍ DIVA và NẠP VÍ KM hiển thị 0 cho dòng đó

Logic kế toán (QUAN TRỌNG — phân biệt thực thu vs giá trị ví):

Ghi nhận giống module thẻ trả trước hiện tại. Khác biệt duy nhất: toàn bộ bonus vào Ví KM 2 thay vì chia VND + KM1.

Khoản mụcGiá trịGiải thích
Thực thu (actual_revenue)= giá mua gói (VD: 500.000đ)Số tiền khách thực trả — KHÔNG phải 5.000.000đ
Doanh số (sales)= giá mua gói (VD: 500.000đ)Ghi nhận doanh số theo giá mua gói
Fund/Quỹ= giá mua góiTạo fund entry theo giá mua gói (ProcessInvoicePayment)
Commission NVTính trên giá mua góiVD: 500k × 5% = 25k (KHÔNG tính trên 5tr)
Tiền vào Ví VND= Khác KM1: KM1 nạp base_value vào VND
Tiền vào Ví KM 1= Khác KM1: KM1 nạp bonus vào KM1
Tiền vào Ví KM 2= value_into_wallet (VD: 5.000.000đ)Toàn bộ giá trị ví vào KM2

So sánh nhanh với thẻ trả trước hiện tại:

Thẻ trả trước KM1:  mua 10tr → VND: 10tr + KM1: 1.5tr  → thực thu = 10tr
Gói Ví KM2:             mua 500k → VND: 0   + KM2: 5tr     → thực thu = 500k
                                          ↑ toàn bộ vào KM2, không chia

FR-004: Thanh toán đơn hàng bằng Ví KM 2 (Ref: DEC-003, DEC-008, DEC-009, DEC-012, DEC-014)

Mức ưu tiên: Must | SCR: SCR-04

Luồng:

NV tạo đơn dịch vụ → chọn phương thức thanh toán


Hiển thị "Ví KM 2" nếu: cấu hình bật + số dư > 0 + có sản phẩm/dịch vụ hợp lệ


NV chọn Ví KM 2 → hệ thống tự điền:
  │  Đơn hàng:           1.000.000đ
  │  Tổng hợp lệ:          700,000đ (chỉ item có allow_km2 = true)
  │  Ví KM 2 (max 20%):   140,000đ  [tự điền, chỉ đọc]
  │  Số dư KM 2:         4,600,000đ
  │  Còn lại:              860,000đ  [NV chọn tiền mặt/CK/...]


Bấm thanh toán → FIFO Deduction (atomic):

  │  Cần trừ: 140,000đ
  │  Lot Silver (balance 300k): trừ 140k → balance = 160k ✓
  │  (Nếu lot chỉ còn 100k: trừ 100k → exhausted,
  │   sang lot tiếp: trừ 40k)

  ├─ Ghi wallet_km2_lot_deduction cho MỖI lot bị trừ
  ├─ Cập nhật wallet.amount -= 140,000
  └─ Tạo invoice (payment_method_id = 'wallet_promotion_2')

AC:

  • [ ] Ví KM 2 hiển thị trong danh sách phương thức thanh toán khi: cấu hình bật + khách có số dư > 0 + ít nhất 1 item hợp lệ
  • [ ] Hệ thống tự điền: MIN(eligible_total × max% / 100, total_balance). VD: đơn hợp lệ 700k, max 20% → tự điền 140k
  • [ ] NV không tự nhập số tiền KM2 — chỉ có số tiền hệ thống tự điền hoặc bỏ chọn KM2
  • [ ] Toggle KM1+KM2 tắt: chọn KM2 → ẩn KM1 (và ngược lại)
  • [ ] Bấm thanh toán → FIFO deduction: trừ bản ghi mua cũ nhất trước, ghi wallet_km2_lot_deduction cho mỗi bản ghi bị trừ
  • [ ] Bản ghi mua dùng hết (balance = 0) → tự động chuyển status exhausted
  • [ ] Khách mua Gói Ví KM2 chi nhánh A, dùng được chi nhánh B (cross-branch) qua Go action; UI/GraphQL không mở quyền đọc toàn bộ lot/deduction cho Staff/Sale

Logic kế toán khi thanh toán bằng Ví KM 2 (giống Ví KM 1 hiện tại):

Khoản mụcGiá trịGiải thích
Phần KM2 trảVD: 140,000đTrừ từ ví, KHÔNG tính thực thu (giống wallet/wallet_promotion hiện tại)
Phần tiền mặt/CK trảVD: 860,000đTính thực thu bình thường
Doanh số (sales)= tổng đơn (1.000.000đ)Ghi nhận toàn bộ giá trị đơn
Thực thu (actual_revenue)= phần tiền thật (860,000đ)Chỉ tính tiền mặt/CK, KHÔNG tính KM2
Customer pointsSkip cho phần KM2Giống logic ProcessInvoicePayment hiện tại

Tóm lại: Ví KM 2 thanh toán xử lý giống Ví KM 1 — tiền ví không tính thực thu, không tính customer points, không ảnh hưởng hạng thành viên (hạng tính dựa trên thực thu). ProcessInvoicePayment thêm "wallet_promotion_2" vào skip list. Không cần sửa rank/loyalty module.

FR-005: Flag cho phép Ví KM 2 per sản phẩm/dịch vụ (Ref: DEC-009)

Mức ưu tiên: Must | SCR: SCR-05

AC:

  • [ ] Form sản phẩm/dịch vụ có thêm checkbox "Cho phép Ví KM 2" (mặc định: tắt)
  • [ ] Sản phẩm không bật flag → không tính vào eligible_total khi thanh toán KM2
  • [ ] Đơn hàng mix: chỉ item eligible mới được tính max%. VD: Gội đầu 200k (eligible) + Kem 300k (không eligible) → max% chỉ tính trên 200k

FR-006: Tab Ví KM 2 trong profile khách (Ref: DEC-006, DEC-013)

Mức ưu tiên: Must | SCR: SCR-06

AC:

  • [ ] Profile khách hiển thị card "Ví KM 2" với tổng số dư (tổng balance các bản ghi lần mua đang hoạt động)
  • [ ] Click vào → mở tab chi tiết: bảng Gói Ví KM2 đã mua (tên gói, nạp, đã dùng, còn lại, hạn), phân đang hoạt động vs đã hết
  • [ ] Gói Ví KM2 đã mua sắp hết hạn (≤ 30 ngày) + còn số dư → hiển thị text cảnh báo phía trên bảng
  • [ ] Lot hết hạn ≤ 7 ngày → text cảnh báo màu đỏ
  • [ ] Staff/Sale truy cập profile khách được phân quyền → thấy tab Ví KM 2 của khách đó; không được đọc lot/deduction của khách ngoài phạm vi quyền

FR-007: Tác vụ tự động xử lý bản ghi mua hết hạn (Ref: DEC-005, DEC-015)

Mức ưu tiên: Must | SCR:

Luồng:

Cron 00:05 hàng ngày (Asia/Ho_Chi_Minh)


Query: `wallet_km2_lot.status = 'active' AND expired_at <= NOW()`


Cho mỗi bản ghi mua hết hạn:
  ├─ UPDATE status = 'expired'
  ├─ Trừ wallet.amount -= lot.balance
  ├─ Ghi transaction log: "Gói {tên} hết hạn, {balance}đ bị xóa"
  └─ Ghi deduction record (type = 'expiry_cancel')

AC:

  • [ ] Cron chạy mỗi ngày 00:05 (Asia/Ho_Chi_Minh)
  • [ ] Tìm wallet_km2_lot.status = activeexpired_at <= NOW()
  • [ ] Đánh dấu status = expired, trừ wallet.amount -= lot.balance
  • [ ] Ghi transaction log với note "Gói {tên} hết hạn, số dư {balance}đ bị xóa"
  • [ ] Ghi wallet_km2_lot_deduction (type = expiry_cancel)

FR-008: Hoàn tiền đơn dịch vụ — phần Ví KM 2 (Ref: DEC-010, DEC-012)

Mức ưu tiên: Should | SCR: SCR-04 (reuse payment screen)

Luồng:

Manager duyệt hoàn tiền đơn dịch vụ đã dùng Ví KM 2


Query deduction records → biết chính xác bản ghi mua nào đã bị trừ bao nhiêu
  │  VD: Lot Silver trừ 150k + Lot Gold trừ 50k = 200k


Hoàn vào từng bản ghi mua:
  ├─ Bản ghi còn hạn → cộng lại balance
  ├─ Bản ghi hết hạn → gia hạn +{refund_extend_days} ngày, status = 'active'
  └─ Bản ghi đã hoàn → tạo bản ghi MỚI (amount = hoàn, hạn = +N ngày)


Bút toán âm hoa hồng NV (nếu có) → luồng hiện tại

AC:

  • [ ] Khi hoàn tiền đơn đã dùng Ví KM 2, hệ thống query wallet_km2_lot_deduction để biết chính xác bản ghi mua nào bị trừ bao nhiêu
  • [ ] Hoàn vào đúng bản ghi gốc, cộng lại balance
  • [ ] Nếu bản ghi đã hết hạn → gia hạn thêm refund_extend_days ngày (config), status = active lại
  • [ ] Nếu bản ghi đã refunded → tạo bản ghi MỚI với amount = số tiền hoàn, hạn = NOW() + refund_extend_days
  • [ ] Bút toán âm hoa hồng NV: giữ nguyên luồng hiện tại

FR-009: Hoàn ví KM2 qua Yêu cầu hoàn tiền (Ref: DEC-011, DEC-019, DEC-026)

Mức ưu tiên: Should | SCR: SCR-07

Luồng (dùng lại chức năng Yêu cầu hoàn tiền hiện có, thêm loại xử lý mới):

User có quyền mở Gói Ví KM2 đã mua còn hiệu lực (balance > 0) → bấm "Hoàn ví KM2"


Form tạo Yêu cầu hoàn tiền (dùng lại pattern `WithdrawForm`, thêm loại xử lý KM2):

  │  ── Thông tin Gói Ví KM2 (chỉ đọc) ───────────────────────────────
  │  Gói Gold | Giá mua gói: 500k | Đã trả: 300k/500k
  │  Ví đã nhận: 3,000,000đ | Đã dùng: 1.000.000đ | Còn: 2,000,000đ

  │  ── Người tạo yêu cầu nhập ────────────────────────────
  │  Loại yêu cầu:       Hoàn ví KM2
  │  Số tiền KM2 trừ:    [2,000,000] đ  ← tự điền = balance, sửa được (≤ balance)
  │  Số tiền hoàn khách: [  100,000] đ  ← tự điền theo FORMULA-002, sửa được (≤ đã trả)
  │  PTTT hoàn: [Tiền mặt] [Ví VND] [Chuyển khoản]
  │  Ghi chú: [_______________]


Tạo `transaction_request` behavior = `refund_km2_wallet`, label UI = "Hoàn ví KM2"


Người duyệt xử lý trên màn Yêu cầu hoàn tiền hiện có


Backend xử lý sau khi duyệt:
  ├─ lot.balance -= số tiền KM2 trừ
  │  (balance = 0 → status = 'refunded', > 0 → vẫn active — partial refund)
  ├─ wallet.amount -= số tiền KM2 trừ
  ├─ Hoàn tiền thật cho khách (cash/VND/CK)
  ├─ Bút toán âm hoa hồng NV nếu chính sách yêu cầu truy thu
  └─ Nếu đơn mua Gói Ví KM2 còn nợ → xoá nợ tương ứng với phần hoàn

AC:

  • [ ] User có refund_request_management_submenu:access/create + quyền xem customer mở Gói Ví KM2 đã mua → nút "Hoàn ví KM2" (chỉ hiện khi status = active, balance > 0 và config allow_refund = true)
  • [ ] Form hiển thị chỉ đọc: tên Gói Ví KM2, giá mua gói, đã trả, ví đã nhận, đã dùng, còn lại
  • [ ] Bấm tạo yêu cầu tạo transaction_request với behavior/action kỹ thuật refund_km2_wallet, label nghiệp vụ "Hoàn ví KM2"; không dùng tên legacy cũ
  • [ ] Số tiền KM2 trừ: hệ thống tự điền = lot.balance, Manager sửa được (0 < value ≤ balance)
  • [ ] Số tiền hoàn khách: hệ thống tự điền theo FORMULA-002 (tỉ lệ × KM2 trừ - phí), Manager sửa được (0 ≤ value ≤ tổng đã trả)
  • [ ] FORMULA-002 chỉ là số tiền gợi ý — Manager có quyền sửa tuỳ tình huống (VD: khách VIP hoàn nhiều hơn)
  • [ ] PTTT hoàn: tiền mặt / Ví VND / chuyển khoản
  • [ ] Yêu cầu xuất hiện trong danh sách Yêu cầu hoàn tiền hiện có, lọc/tìm kiếm theo loại "Hoàn ví KM2"
  • [ ] Người duyệt dùng cấu hình duyệt và quyền refund_request_management_submenu:approve/payment hiện có để duyệt/từ chối/hủy và thực hiện hoàn tiền; quyền update chỉ dùng cho chỉnh thông tin/chứng từ yêu cầu trước khi duyệt
  • [ ] Truy thu hoa hồng: nếu áp dụng, dùng lại form/bảng truy thu của luồng hoàn tiền đơn dịch vụ hiện tại (CommissionRefundConfirmForm)
  • [ ] Duyệt → hệ thống trừ KM2, hoàn tiền khách, ghi bút toán âm hoa hồng nếu có
  • [ ] Hoàn một phần: nếu KM2 trừ < balance → bản ghi mua vẫn active (balance giảm, chưa refunded)
  • [ ] Hoàn toàn bộ: nếu KM2 trừ = balance → wallet_km2_lot.status = refunded
  • [ ] Nếu đơn mua Gói Ví KM2 còn nợ → xoá nợ tương ứng
  • [ ] Nút "Hoàn ví KM2" ẩn khi: Gói Ví KM2 đã hết hạn (expired), đã dùng hết (exhausted), đã hoàn hết (refunded), hoặc user bị thu hồi quyền qua Dynamic Permission

FR-010: Báo cáo tổng hợp Ví KM 2 (Ref: DEC-003)

Mức ưu tiên: Could | SCR: SCR-08

AC:

  • [ ] Dashboard hiển thị 4 KPI cards: Gói Ví KM2 bán ra, Doanh thu, Tỉ lệ sử dụng Ví KM2, Số dư sắp cháy (30 ngày)
  • [ ] Bảng chi tiết theo Gói Ví KM2: số bán, doanh thu, tổng nạp, đã dùng, tỉ lệ sử dụng
  • [ ] Bảng Gói Ví KM2 sắp hết hạn (30 ngày tới): khách, Gói Ví KM2, số dư, hết hạn
  • [ ] Bảng top khách dùng nhiều nhất: tổng nạp, đã dùng, số lần, rate
  • [ ] Filter: khoảng thời gian, chi nhánh (Manager: branch, Admin: all)

FR-011: ZNS thông báo (Ref: DEC-006)

Mức ưu tiên: Could | SCR:

AC:

  • [ ] Mua Gói Ví KM2 thành công → ZNS trigger on_km2_wallet_activated: "Bạn đã kích hoạt {N} Gói {tên}, tổng {amount}đ, HSD: {expired_at}"
  • [ ] 7 ngày trước hết hạn (nếu balance > 0) → ZNS trigger on_km2_wallet_expiring_7d: "Gói {tên} còn {balance}đ sẽ hết hạn ngày {expired_at}"

FR-012: Hiển thị + tích hợp Ví KM 2 trong các module hiện có (Ref: DEC-001)

Mức ưu tiên: Must (Phase 1) + Should (Phase 2) | SCR: SCR-03, SCR-04 + các màn hiện có

Nguyên tắc: dựa trên UI hiện tại, thêm KM2 theo đúng pattern có sẵn. KHÔNG thay đổi layout.

AC — Phase 1: Chi tiết đơn nạp tiền (PrepaidOrderPayments):

  • [ ] Summary header thêm dòng "Nạp vào ví KM 2: {amount}đ" — conditional, chỉ hiện khi > 0
  • [ ] Bảng thanh toán (PrepaidOrderPaymentTable) thêm cột "Ví KM 2" — conditional khi đơn có Gói Ví KM2
  • [ ] Phần "Số tiền nạp vào ví KM 2" riêng biệt với "Nạp vào ví khuyến mãi" (KM1)

AC — Phase 1: Đơn mỹ phẩm (CosmeticOrderFormPayment):

  • [ ] Thêm payment method wallet_promotion_2 vào flow thanh toán mỹ phẩm (giống cách wallet_promotion đã có)
  • [ ] CosmeticOrderFormPaymentTable.tsx: thêm hiển thị wallet_promotion_2_amount
  • [ ] CosmeticOrderPaymentFormMultiple.tsx: thêm KM2 vào thanh toán nhiều lần

AC — Phase 1: Đơn sản phẩm (ProductOrder):

  • [ ] ProductOrderItems.tsx: thêm check allow_promo_wallet_2 per sản phẩm (giống allow_promo_wallet)
  • [ ] ProductOrderCreate.tsx: inject customerWalletValue["VND_PROMOTION_2"]

AC — Phase 1: Hoá đơn in:

  • [ ] print_invoice_popup.go + print_invoice_preview.go: thêm case "wallet_promotion_2" → label "Ví KM 2"
  • [ ] InvoiceTemplatePopupPrint.tsx + InvoiceTemplatePreview.tsx: thêm KM2 vào template

AC — Phase 1: Fund/Quỹ:

  • [ ] FundTable.tsx + FundInvoicePopup.tsx: thêm hiển thị wallet_promotion_2_amount (giống wallet_promotion_amount)

AC — Phase 2: Withdraw/Refund detail:

  • [ ] WithdrawRequestDetail.tsx: hiện label "Ví KM 2" khi payment_method = wallet_promotion_2

AC — Phase 2: Report DV/NV hiện có:

  • [ ] ServiceReportTable.tsx + ServiceGroupReportTable.tsx: thêm cột wallet_promotion_2_revenue
  • [ ] EmployeeRevenueReportDoughnutChart.tsx: thêm KM2 vào biểu đồ
  • [ ] report_service.graphql + report_employee_revenue.graphql: thêm fields KM2

AC — Phase 2: CRM Customer:

  • [ ] CustomerEWalletInformation.tsx: thêm hiển thị số dư Ví KM 2
  • [ ] CustomerVisitSumcard.tsx: tooltip thêm mention Ví KM 2 nếu có

AC — KHÔNG thay đổi (giữ nguyên):

  • [ ] OrderCard.tsx + CustomerOrderHistory.tsx: card không breakdown — giữ nguyên
  • [ ] Filter "Đơn hàng nạp tiền": đã cover Gói Ví KM2 — không thêm filter
  • [ ] Dashboard (SalesCards, RevenuePercentageByPaymentMethod): Phase 3 report KM2 handle
  • [ ] Rank/Loyalty: KM2 thanh toán không tính thực thukhông ảnh hưởng hạng (giống KM1). Không cần sửa rank.graphql, profile.graphql

A6) Giả định

IDGiả địnhNgười xác nhận
ASM-001Khách chỉ mua Gói Ví KM2 tại POS (NV bán), không tự mua trên app. App khách có hiển thị tab Ví KM 2 (read-only, parity với KM 1) — xem SCR-09 và PD-002PO
ASM-002% max là số nguyên (20%, 40%), không cần thập phânPO
ASM-003Hệ thống wallet hiện tại (transaction_request → transaction → event trigger) đủ handle thêm 1 wallet typeTech Lead
ASM-004ZNS template mới cần đăng ký với provider (Zalo) trước khi dùngOps
ASM-005KM 2 áp dụng cùng logic rank/loyalty như KM 1 — không thay đổi rank.graphql / profile.graphql; RankCreate.tsx KHÔNG cần field mớiPO

A7) Rủi ro

IDRủi roẢnh hưởngXác suấtCách giảm thiểu
RSK-001Tranh chấp FIFO deduction khi 2 NV thanh toán cùng lúc cho 1 kháchCaoThấpSELECT FOR UPDATE lock bản ghi mua trong transaction
RSK-00210+ file Go hard-code wallet_promotion → dev bỏ sót fileCaoTrung bìnhGrep toàn bộ "wallet_promotion" trước khi deploy, dùng checklist code review
RSK-003Scheduler bỏ sót bản ghi mua hết hạnTrung bìnhThấpexpired_at lưu end_of_day (23:59:59), cron chạy 00:05, retry 3 lần
RSK-004Invoice struct Go thiếu field WalletPromotionAmount (bug tiềm ẩn hiện tại)Trung bìnhCaoFix luôn trong Phase 1

A8) Chỉ số sau phát hành

Chỉ sốCách đoMục tiêuKhi nào đo
Số Gói Ví KM2 bán raCOUNT(wallet_km2_lot) theo thángBaseline tháng 1, tăng 20% tháng 2Hàng tháng
Doanh thu bán Gói Ví KM2SUM(order.total) WHERE wallet_target = KM2> 0Hàng tháng
Tỉ lệ sử dụng Ví KM2SUM(used_amount) / SUM(initial_amount) × 100%> 50% sau 3 thángHàng tháng
Tần suất khách quay lại (khách có Ví KM2)COUNT(orders) / COUNT(DISTINCT customer) WHERE used KM2+30% so với baselineHàng quý
Số dư cháy (expired)SUM(balance) WHERE status = expired< 30% tổng nạpHàng tháng

A9) Bảng thuật ngữ

Thuật ngữ (VI)Thuật ngữ (EN)Định nghĩaPhân biệt với
Ví KM 2Promotion Wallet 2Ví khuyến mãi dùng theo giới hạn max X% trên mỗi đơn≠ Ví KM 1 (trả 100% trực tiếp)
Gói Ví KM2KM2 wallet packageGói nạp ưu đãi bán cho khách. VD: khách trả 500k, nhận 5.000.000đ vào Ví KM 2, có hạn sử dụng 6 tháng≠ Thẻ trả trước thường (ví đích = VND)
Lần mua Ví KM2 (lot)KM2 wallet lotBản ghi kỹ thuật được tạo sau mỗi lần khách mua Gói Ví KM2; lưu số đã nạp, đã dùng, còn lại, hạn sử dụng và trạng thái≠ Gói Ví KM2 là cấu hình/loại gói bán
FIFOFirst In First OutKhi thanh toán, hệ thống trừ bản ghi lần mua cũ nhất (activated_at ASC) trước
Tổng hợp lệ để dùng KM2Eligible totalTổng giá trị sản phẩm/dịch vụ trong đơn có allow_promo_wallet_2 = true≠ Tổng đơn hàng
Tỉ lệ hoànRefund ratioGiá mua gói / giá trị ví. VD: 500k/5tr = 10%≠ Hoàn 100%
Phí xử lý hoàn víRefund processing fee% giá mua gói bị trừ khi hoàn ví KM2. VD: 20% × 500k = 100k
Hoàn ví KM2KM2 wallet refundYêu cầu hoàn tiền behavior refund_km2_wallet, dùng để tất toán/hoàn phần số dư KM2 còn lại cho khách≠ Refund đơn DV hoàn KM2 về bản ghi lần mua gốc
Yêu cầu hoàn tiềnRefund requestLuồng transaction_request hiện có để tạo, duyệt, từ chối, hủy hoàn tiền≠ Module approval riêng cho KM2
Tỉ lệ sử dụng Ví KM2% tiền đã dùng so với tổng nạp
Số dư cháyBurned balanceSố dư bị xóa do bản ghi lần mua Ví KM2 hết hạn

A10) Công thức nghiệp vụ

FORMULA-001: Số tiền Ví KM 2 thanh toán trên mỗi đơn hàng

  • Mô tả: Tính số tiền tối đa Ví KM 2 được trả cho 1 đơn hàng
  • Công thức: km2_pay = MIN(eligible_total × max_percent_per_order / 100, available_amount)
  • Biến số:
    • eligible_total: tổng giá trị sản phẩm/dịch vụ có allow_promo_wallet_2 = true — nguồn: SUM(order_item.price × quantity) WHERE product.allow_promo_wallet_2 = true
    • max_percent_per_order: % tối đa mỗi đơn — nguồn: wallet_km2_config.max_percent_per_order
    • available_amount: tổng số dư Ví KM 2 khả dụng realtime — nguồn: Action get_customer_km2_balance.available_amount = SUM(wallet_km2_lot.balance) WHERE status='active' AND expired_at > NOW() (DEC-029: KHÔNG dùng wallet.amount trực tiếp, để đóng race window scheduler miss)
  • Đơn vị: VND (số nguyên, không làm tròn)
  • Ví dụ: Tổng hợp lệ của đơn là 700k, max_percent_per_order = 20%, available_amount = 4.6tr → MIN(700k × 20%, 4.6tr) = MIN(140k, 4.6tr) = 140.000đ
  • Trường hợp cá biệt:
    • eligible_total = 0 (không có sản phẩm/dịch vụ hợp lệ) → km2_pay = 0, ẩn lựa chọn KM2
    • available_amount < km2_max → dùng hết số dư khả dụng
    • available_amount = 0 → ẩn option KM2
    • Bản ghi mua đã hết hạn (expired_at <= NOW()) → KHÔNG tính vào available_amount dù scheduler chưa kịp đổi status (DEC-029 enforce realtime)

FORMULA-002: Gợi ý số tiền hoàn ví KM2 (auto-fill, approver sửa được)

  • Mô tả: Tính số tiền gợi ý hoàn cho khách trong yêu cầu refund_km2_wallet. Chỉ là số tiền tự điền ban đầu — người duyệt có quyền sửa tuỳ tình huống nếu có quyền refund_request_management_submenu:update; bước duyệt dùng approve, bước chi tiền dùng payment.
  • Công thức: suggested_refund = MAX(km2_deduct × refund_ratio - refund_fee, 0)
  • Biến số:
    • km2_deduct: số tiền KM2 trừ (Manager nhập, auto-fill = lot.balance) — nguồn: form input
    • refund_ratio: giá mua gói / giá trị ví — nguồn: wallet_km2_lot.package_price / wallet_km2_lot.wallet_value (DEC-016 snapshot)
    • refund_fee: phí xử lý hoàn ví = giá mua gói × phí xử lý hoàn ví % — nguồn: wallet_km2_lot.package_price × wallet_km2_config.refund_fee_percent / 100
  • Đơn vị: VND (số nguyên)
  • Ví dụ: KM2 trừ = 2tr, ratio = 500k/5tr = 10%, phí = 500k × 20% = 100k → MAX(2tr × 10% - 100k, 0) = 100,000đ (gợi ý)
  • Người duyệt sửa số tiền: người duyệt nhập số khác (VD: 150k cho khách VIP). Kiểm tra hợp lệ: 0 ≤ value ≤ tổng đã trả
  • Trường hợp cá biệt:
    • lot.balance = 0 → ẩn nút "Hoàn ví KM2"
    • suggested_refund = 0 (phí > hoàn) → tự điền 0đ, Manager vẫn sửa được
    • refund_fee_percent = 0 → không trừ phí
    • Partial refund: KM2 trừ < balance → lot vẫn active, formula tính trên phần trừ
    • Đơn mua Gói Ví KM2 còn nợ → form hiển thị "Đã trả: X/Y", Manager cân nhắc khi nhập

FORMULA-003: Tỉ lệ sử dụng Ví KM2

  • Mô tả: Tỉ lệ tiền đã sử dụng so với tổng nạp
  • Công thức: rate = total_used / total_deposited × 100
  • Biến số:
    • total_used: tổng đã dùng — nguồn: SUM(wallet_km2_lot.used_amount) WHERE status IN ('active', 'exhausted', 'expired')
    • total_deposited: tổng đã nạp — nguồn: SUM(wallet_km2_lot.initial_amount) WHERE status IN ('active', 'exhausted', 'expired')
  • Đơn vị: % (2 decimal)
  • Ví dụ: Tổng nạp 10tr, đã dùng 3.5tr → 3.5/10 × 100 = 35.00%
  • Trường hợp cá biệt:
    • total_deposited = 0 → hiển thị "—"
    • Exclude status = 'refunded' (Gói Ví KM2 đã hoàn, không tính)

FORMULA-004: Tiền vào Ví KM 2 per lần thanh toán (multi-payment)

  • Mô tả: Khi khách trả nhiều lần cho đơn mua Gói Ví KM2, mỗi lần trả → ví nhận theo tỉ lệ (giống thẻ trả trước hiện tại)
  • Công thức: km2_credit = (customer_paid_amount / total_order_amount) × total_value_into_wallet
  • Biến số:
    • customer_paid_amount: số tiền khách trả lần này — nguồn: invoice.customer_paid_amount
    • total_order_amount: tổng tiền đơn (giá mua gói × qty) — nguồn: order.amount
    • total_value_into_wallet: tổng giá trị ví (value_into_wallet × qty) — nguồn: SUM(order_item.prepaid_value_into_wallet)
  • Đơn vị: VND (số nguyên)
  • Ví dụ: Gói Gold 500k→5tr, qty=2, tổng đơn 1,000k. Trả lần 1: 600k → ví nhận: (600k / 1,000k) × 10tr = 6.000.000đ
  • Trường hợp cá biệt:
    • Trả đủ 100% 1 lần → credit = total_value_into_wallet (case phổ biến nhất)
    • Trả lần cuối → credit = total_value_into_wallet - SUM(credit các lần trước) (tránh sai lệch do làm tròn)
    • Lot balance tăng dần: lần 1 tạo lot (balance = proportional), lần 2+ cộng thêm vào lot