Appearance
v1.8 — 15/05/2026
| Thay đổi | Section | Ảnh hưởng |
|---|---|---|
Update smoke test Step 8 (E4.1 T+30): enforce DEC-027 — đơn prepaid_order.status='pending' chưa thanh toán → KHÔNG có lot trong DB; sau khi thanh toán đủ → 1 lot active với credited_amount = initial_amount | E4.1 | QA, Ops |
Checklist phát hành (Go-Live) - Ví KM 2 (Promotion Wallet 2)
Tham chiếu: PRD v1.8 + SOURCE_OF_TRUTH v1.6 | Ngày: 15/05/2026
File này dùng để làm gì: chốt gate trước deploy, pre-check, rollback và ký duyệt cho rollout
Ví KM 2. Nên đọc trước:decision-brief.md->E1) Cổng phát hành->E2) Kiểm tra trước triển khai code->E4) Các bước deploy.Quy tắc ưu tiên: nếu có conflict với
SOURCE_OF_TRUTH.md, ưu tiênSOURCE_OF_TRUTH.md. File này là owner của readiness, thứ tự deploy, rollback và ký duyệt vận hành.
Đầu vào chuẩn (Canonical Inputs)
| File | Vai trò | Nếu xung đột |
|---|---|---|
| SOURCE_OF_TRUTH.md | Nguồn sự thật chuẩn + khóa giải pháp | Ưu tiên cao nhất |
| decision-brief.md | Tóm tắt phạm vi/rủi ro/handoff | Chỉ định hướng ưu tiên readiness |
| prd.md | Scope, lifecycle, ràng buộc nghiệp vụ | Theo truth đã khóa |
| dev-spec.md | Data/API/migration/task/security | Theo truth đã khóa |
| qa-test-plan.md | Điều kiện vào/ra và regression oracle | QA owner |
| handoff.md | Kế hoạch, RACI, điểm còn mở | handoff.md là file chịu trách nhiệm |
E1) Cổng phát hành
| # | Cổng kiểm tra | Phụ trách | Điều kiện pass |
|---|---|---|---|
| 1 | PO ký duyệt PRD và Decision Brief | PO | Không còn critical gap ở FR/DEC/formula |
| 2 | TL ký duyệt Đặc tả kỹ thuật | Tech Lead | Idempotency, FIFO lock, expiry guard, rollback được chốt |
| 3 | QA pass P0/P1 | QA Lead | D5 exit criteria pass, không còn bug critical/major |
| 4 | Migration/metadata verify trên staging | BE Dev | Table/column/index/action/cron query verify thành công |
| 5 | Smoke test payment/wallet/report | QA + PO | Bán Gói Ví KM2, thanh toán, refund, expiry dry-run, print/fund/report pass |
| 6 | Monitoring sẵn sàng | TL + Ops | Log/metric/alert cho payment, scheduler, expired balance đã cấu hình |
E2) Kiểm tra trước triển khai code
BE Dev
- [ ] Migration timestamp mới nhất được chốt cho
walletvàecommerce. - [ ]
wallet_type,payment_gateway,payment_gateway_wallet_typeseed không trùng. - [ ]
wallet_km2_configseed đúng 1 global row (branch_id IS NULL,disabled=true), cóuq_wallet_km2_config_global_rowvàuq_wallet_km2_config_global_active. - [ ] Metadata không cấp direct select role
userchowallet_km2_lot/wallet_km2_lot_deduction; customer profile đọc qua actionget_customer_km2_lots. - [ ] Dynamic Permission v2 seed/verify xong:
module_permission_action,role_module.actions, portal,branch_modecho KM2 đúng SOURCE_OF_TRUTH. - [ ]
ModuleOperationMapping/auth mapping đã map các action:internal_configuration:update,customer_management:access/view_all,prepaid_order:create/payment,service_order:payment,product_order:payment,refund_request_management_submenu:access/create/update/approve/payment. - [ ]
deduct_km2_paymentcó idempotency key theoorder_id+ payment attempt/request id. - [ ]
refund_km2_walletchạy trong luồng Yêu cầu hoàn tiền hiện có; label UI là "Hoàn ví KM2"; không còn metadata/action legacy cũ. - [ ] Payment action bỏ qua lot hết hạn bằng guard
expired_at > NOW()trước khi lock/deduct. - [ ] Impact boundary đã được verify: KM1/report/fund/print/notification tách KM2, và các module không liên quan không bị mở scope hoặc đổi dữ liệu.
- [ ]
down.sqlrollback được test trên staging clone.
FE Dev
- [ ] Route settings dùng shell hiện có
/s/internal-settings/promotion-wallet. - [ ] Report dashboard để sau PD-001; Phase 1/2 không hardcode route report chưa chốt.
- [ ] Form state có dirty-state, confirm khi rời màn hình nếu chưa lưu.
- [ ] Payment UI ẩn/hiện KM1/KM2 đúng toggle
allow_combine_km1.
QA
- [ ] Seed DS-001 tạo đủ: config, 2 Gói Ví KM2, wallet type, eligible/non-eligible item.
- [ ] Test account có đủ tổ hợp permission: có/không có
internal_configuration:update,service_order:payment,product_order:payment,customer_management:access/view_all,refund_request_management_submenu:access/create/update/approve/payment; branch scope rõ. - [ ] Grant/revoke permission cần refresh/relogin/refetch theo cơ chế Dynamic Permission hiện có và đã có test API no-leak.
- [ ] Test race condition có 2 request thanh toán song song.
- [ ] Chạy đủ
TC-IMPACT-*trước UAT để xác nhận feature ảnh hưởng/không ảnh hưởng đúng biên đã khóa.
Gate kỹ thuật trước merge / UAT
| Gate | Khi nào bắt buộc pass | Phụ trách | Điều kiện pass tối thiểu |
|---|---|---|---|
| TG-001 Hoàn ví KM2 | Trước merge Phase 2 refund | TL + FE + BE + QA | Yêu cầu hoàn tiền có option "Hoàn ví KM2", behavior refund_km2_wallet, không hard-code VND_PROMOTION, cập nhật đúng lot/deduction/audit |
| TG-002 Hard-code KM1/KM2 | Trước merge Phase 1 payment/invoice/report/fund | TL + BE + FE | Grep wallet_promotion / VND_PROMOTION / wallet_type_id đã phân loại; không còn occurrence chưa xử lý trong affected features |
| TG-003 FIFO/idempotency/expiry | Trước merge deduct_km2_payment | TL + BE | Có transaction lock, idempotency key, guard expired_at > NOW(), retry/race test pass |
| TG-004 DB/Hasura/codegen | Trước FE integration và UAT | BE + FE | Migration/metadata/codegen pass; action/field KM2 tồn tại đúng contract dev-spec |
| TG-005 Dynamic Permission v2 | Trước UAT permission | TL + BE + FE + QA | Seed/action mapping đúng; FE ẩn/hiện đúng; API direct call thiếu quyền bị chặn |
| TG-006 Scheduler/rollback/monitoring | Trước go-live | TL + Ops + BE | Cron dry-run pass, rollback disable config/cron thử được, metric/log/alert có owner |
| TG-007 Impact boundary regression | Trước UAT sign-off | QA + TL | TC-IMPACT-* pass; affected/unaffected đúng biên đã khóa trong SOURCE_OF_TRUTH |
handoff.mdlà owner chi tiết của TG-001..TG-007. Checklist này chỉ dùng để chặn merge/UAT/go-live.
E3) Kiểm tra trước deploy
Database / Hasura
- [ ] Apply migration tạo
wallet_km2_lot,wallet_km2_lot_deduction,wallet_km2_config. - [ ] Apply migration alter
prepaid_card,product,service,invoice/report field nếu cần. - [ ] Apply Hasura metadata table permissions, action
get_customer_km2_lots, actiondeduct_km2_payment, actionrefund_km2_walletnếu dùng action riêng, cronwallet_km2_expire_lots. - [ ] Verify query:
SELECT id FROM wallet_type WHERE id = 'VND_PROMOTION_2';SELECT COUNT(*) FROM wallet_km2_config WHERE branch_id IS NULL AND disabled = true AND deleted_at IS NULL;→ trước enable phải = 1SELECT COUNT(*) FROM wallet_km2_config WHERE branch_id IS NULL AND disabled = false AND deleted_at IS NULL;→ trước enable phải = 0SELECT COUNT(*) FROM payment_gateway_wallet_type WHERE payment_gateway_id = 'wallet_promotion_2';- Metadata check: không có
select_permissionsroleusertrongpublic_wallet_km2_lot.yamlvàpublic_wallet_km2_lot_deduction.yaml.
Backend
- [ ]
wallet-apideploy có action handler + scheduler handler. - [ ]
ecommerce-apideploy có payment max% validation, order confirm balance check, refund logic. - [ ]
pkg/storemapping/balance skip list đã thêmwallet_promotion_2/VND_PROMOTION_2. - [ ] Log structured events cho lot created/deducted/expired/refunded.
Frontend
- [ ] Admin settings KM2 load/save được.
- [ ] PrepaidCard form có
wallet_target,expiry_months. - [ ] Prepaid order summary hiện đúng dòng/cột KM2 conditional.
- [ ] Payment order ẩn/hiện KM2 theo config, balance, eligible item.
- [ ] Customer profile hiện KM2 card/tab/lot state.
E4) Các bước deploy
E4.0) Tóm tắt — bảng cũ giữ làm overview
| # | Bước | Phụ trách | Cách kiểm chứng | Rollback |
|---|---|---|---|---|
| 1 | Backup DB trước migration | Ops | Backup job completed | Restore backup nếu migration lỗi nghiêm trọng |
| 2 | Apply DB migrations wallet + ecommerce | BE Dev | Verify SQL E3 pass | Chạy down.sql nếu chưa có data production |
| 3 | Apply Hasura metadata | BE Dev | Metadata reload không lỗi | Revert metadata commit trước đó |
| 4 | Deploy wallet-api | BE Dev | /healthz, action dry-run, scheduler dry-run | Rollback image trước |
| 5 | Deploy ecommerce-api | BE Dev | Payment smoke test staging/prod canary | Rollback image trước |
| 6 | Deploy FE admin | FE Dev | Route settings/payment/customer load đúng | Revert build trước |
| 7 | Bật config KM2 | PO + Admin | Settings save, payment method visible, disabled=false đúng 1 global row | Tắt disabled=true cho config/payment method |
E4.1) Migration Runbook minute-by-minute (BẮT BUỘC chạy đúng kịch bản)
Mục tiêu: Day-0 deploy không cần Ops hỏi TL từng bước. Mọi command + rollback action phải in trước, có người ký duyệt từng giai đoạn.
Maintenance window: 60 phút sau giờ thấp điểm (gợi ý 23:00-00:00 Asia/Ho_Chi_Minh, sau khi POS đóng ca tối). Tổng deploy kế hoạch ~45 phút, dự phòng 15 phút buffer.
Tham gia bắt buộc: Ops (lead), TL (approve gate), BE Dev (execute), FE Dev (deploy FE), QA (smoke), PO (bật config cuối).
| Mốc | Action | Owner | Verify | Rollback action nếu fail |
|---|---|---|---|---|
| T-30 | Pre-flight check: confirm staff chi nhánh đã đóng ca; kiểm tra backup job nightly chạy thành công 24h qua | Ops | Backup status completed; POS dashboard không có đơn pending | Hoãn deploy; reschedule |
| T-15 | Snapshot DB ad-hoc (cả wallet + ecommerce DB) | Ops | Snapshot ID record được; size > 0 | — (chưa làm gì) |
| T-10 | Đóng cờ feature flag globally wallet_km2_config.disabled=true (đảm bảo seed) + announce maintenance window qua Slack #all-staff | TL | SELECT disabled FROM wallet_km2_config = true | Hoãn |
| T-5 | TL ký bắt đầu deploy; Ops post message vào Slack #deploy-log | TL | Slack message timestamp | — |
| T+0 | Step 1 — Apply migrations (đảm bảo thứ tự): wallet DB trước (lot, payment_attempt, deduction, config + seed VND_PROMOTION_2), sau đó ecommerce DB (ALTER prepaid_card, ALTER product, ALTER service, seed payment_method row wallet_promotion_2) | BE Dev | \dt wallet_km2_* show 4 tables; \d prepaid_card có cột wallet_target + expiry_months | psql -f wallet/down.sql && psql -f ecommerce/down.sql; restore snapshot T-15 nếu data corrupt |
| T+5 | Step 2 — Reload Hasura metadata từ commit ref đã chuẩn bị | BE Dev | hasura metadata apply exit 0; Hasura console /v1/metadata không lỗi; check action deduct_km2_payment + get_customer_km2_lots + refund_km2_wallet đăng ký đúng | git checkout {prev_metadata_sha} && hasura metadata apply |
| T+8 | Step 3 — Verify GraphQL schema | BE Dev | query { wallet_km2_lot { id } wallet_km2_config { disabled } } trả empty result không lỗi schema | Revert metadata + rerun apply |
| T+10 | Step 4 — Deploy wallet-api image new tag | BE Dev + Ops | /healthz 200; log không có panic; curl -X POST .../actions/dry-run-scheduler trả 200 | kubectl rollout undo deployment/wallet-api; verify image trở về tag cũ |
| T+15 | Step 5 — Deploy ecommerce-api image new tag | BE Dev + Ops | /healthz 200; smoke test endpoint /api/payment/health trả OK | kubectl rollout undo deployment/ecommerce-api |
| T+20 | Step 6 — Cron metadata trigger wallet_km2_expire_lots đăng ký với schedule 5 0 * * * (Asia/Ho_Chi_Minh) nhưng KHÔNG enable (one_off run only cho test) | BE Dev | Hasura cron list show trigger paused | Xoá cron trigger qua Hasura UI |
| T+25 | Step 7 — Deploy FE admin (release tag mới có route settings + chip Ví KM 2 + popup customer KM2 + dialog refund KM2) | FE Dev | Trang /s/internal-settings/promotion-wallet load không 500; toggle KM 2 hiện; chưa bật vẫn hiển thị banner readiness | Revert FE build trước (CDN purge cache) |
| T+30 | Step 8 — Smoke test bắt buộc (QA driven) trên production canary với khách test_km2_user_1: (a) Admin tạo Gói Test 100k → 1tr KM 2 ( expiry_months=1) (b) Bật allow_promo_wallet_2=true cho 1 dịch vụ test (c) POS bán 1 đơn qty=1 nhưng KHÔNG thanh toán → expect prepaid_order.status='pending' và SELECT COUNT(*) FROM wallet_km2_lot WHERE user_id='test_km2_user_1' = 0 (DEC-027: lot chỉ tạo khi khách trả ≥ 1 đồng); sau đó thanh toán đầy đủ 100k → expect 1 lot active với credited_amount = initial_amount (d) Verify migration data integrity: không có lot orphan; wallet_km2_config đúng 1 row với disabled=true | QA + BE | 4 case pass; lot count đúng theo từng sub-step (c) | Nếu fail bất kỳ case → escalate TL → rollback toàn bộ E5.1 |
| T+40 | Step 9 — Enable cron trigger wallet_km2_expire_lots (đổi từ paused → active) | BE Dev | hasura cron-trigger list show active | Pause lại |
| T+42 | Gate quyết định bật config: TL + PO confirm tất cả smoke test pass + monitoring dashboard không alert critical | TL + PO | Slack message ký duyệt | Skip Step 10, kết thúc maintenance window với config vẫn disabled=true; KM 2 sẽ bật trong release nhỏ kế tiếp |
| T+45 | Step 10 — Bật wallet_km2_config.disabled=false qua SCR-01 Settings UI (PO/Admin click toggle "Bật Ví KM 2 cho POS" sau khi readiness 4 bước pass) | PO + Admin | SELECT disabled FROM wallet_km2_config WHERE branch_id IS NULL = false; POS thấy chip Ví KM 2 | UPDATE wallet_km2_config SET disabled=true (rollback nhanh nhất) |
| T+50 | Step 11 — End maintenance window, post #all-staff rằng KM 2 đã live | Ops | Slack message | — |
| T+55 | Theo dõi monitoring dashboard 1 giờ tiếp theo; QA chạy 5 đơn KM 2 thật ở chi nhánh pilot | Ops + QA | Dashboard 0 critical alert; 5 đơn pass | Tắt config nếu phát hiện bug |
E4.2) Decision tree khi gặp lỗi
Migration step nào fail?
├─ T+0 (DB migrate) → restore snapshot T-15 + reschedule
├─ T+5 (Hasura metadata) → git revert + rerun
├─ T+10 / T+15 (BE deploy) → kubectl rollout undo
├─ T+25 (FE deploy) → revert build + CDN purge
├─ T+30 (smoke test) →
│ ├─ Migration data corrupt → restore snapshot
│ ├─ Action không trả đúng → revert BE deploy
│ └─ FE render lỗi → revert FE deploy
├─ T+40 (cron) → pause + investigate, không block deploy
└─ T+45 (bật config) → UPDATE disabled=true ngay; lock incidentE4.3) Tiêu chí "đã deploy thành công" (ký bởi TL + PO)
- [ ] Tất cả 11 step Status = ✅
- [ ]
wallet_km2_configđúng 1 row global,disabled=false(sau Step 10) - [ ] 4 table mới + 5 ALTER + payment_method seed verified
- [ ] Hasura action
deduct_km2_payment+get_customer_km2_lots+refund_km2_walletreachable - [ ] Cron
wallet_km2_expire_lotsactive - [ ] Grafana dashboard
KM2 Operationsxanh (không critical alert) - [ ] 5 đơn pilot pass trong 1 giờ đầu
- [ ] TL + PO ký Slack
#deploy-log
Kiểm tra ngay sau deploy
- [ ] Admin bật KM2 và lưu config thành công.
- [ ] Permission smoke: user bị revoke không thấy menu/button KM2 và gọi API trực tiếp bị chặn; user được grant thấy đúng sau refresh/relogin.
- [ ] Sau khi bật:
SELECT COUNT(*) FROM wallet_km2_config WHERE branch_id IS NULL AND disabled = false AND deleted_at IS NULL;= 1. - [ ] Tạo 1 Gói Ví KM2 test, bán cho customer test, tạo lot thành công.
- [ ] Staff chỉ xem được KM2 lot của khách được phân quyền; gọi action với khách ngoài phạm vi bị chặn.
- [ ] Thanh toán đơn DV bằng KM2 với eligible item, wallet amount và lot balance giảm đúng.
- [ ] Refund đơn DV trả lại lot đúng deduction records.
- [ ] Tạo Yêu cầu hoàn tiền loại "Hoàn ví KM2", duyệt qua màn hiện có, lot/wallet/refund method cập nhật đúng.
- [ ] Chạy scheduler dry-run/limited batch, log không có error.
- [ ] Fund/print/report không mất data và không gộp nhầm KM1/KM2.
Theo dõi Day-1
- [ ]
km2_deduction_error_total= 0 trong 2 giờ đầu. - [ ]
km2_fifo_duration_secondsp99 < 500ms. - [ ]
km2_scheduler_error_total= 0 sau lần cron đầu. - [ ]
km2_expired_balance_totalkhông tăng bất thường trong ngày đầu. - [ ] Log không có trùng deduction cho cùng
order_id+ request id.
E5) Khôi phục (Rollback)
| Tình huống | Cách rollback | Ghi chú |
|---|---|---|
| FE lỗi UI nhưng BE/DB ổn | Revert FE build | Giữ config KM2 disabled nếu cần |
| Payment action lỗi trừ tiền | Disable KM2 config + rollback ecommerce-api/wallet-api | Đối chiếu wallet_km2_lot_deduction trước khi sửa data |
| Scheduler expire sai | Disable cron trigger wallet_km2_expire_lots | Không xóa lot; TL/BE review log và chạy correction script riêng |
| Migration lỗi trước khi có data | Chạy down.sql | Chỉ dùng khi chưa có giao dịch KM2 production |
| Data đã có giao dịch KM2 | Không drop table trực tiếp | Dùng feature disable + hotfix/compensation, có TL approve |
E6) Ký duyệt
| Nhóm ký duyệt | Người | Ngày | Trạng thái |
|---|---|---|---|
| Nghiệp vụ | PO | Chờ duyệt | |
| Kỹ thuật | Tech Lead | Chờ duyệt | |
| QA | QA Lead | Chờ duyệt | |
| Vận hành | Ops/TL | Chờ duyệt |