Task-kiosk-260 — Chuẩn hóa wording Adjustment / Reversal / Refund
| Field | Value |
|---|---|
| Task ID | Task-kiosk-260 |
| Target release / Fix version | V1.2.0 |
| Scope | Admin Portal (kioskgaming_admin) — list, badge, filter, export, Audit/Transaction Detail |
| Out of scope (v1) | Đổi DB enum UsdWalletTransaction.type; Shop/Agent/Super Agent portal |
| Related docs | Admin financial correction debit spec · Admin USD wallet transaction detail view · Backend code review (full) |
1. Background
Các từ Adjustment, Reversal và Refund có ý nghĩa nghiệp vụ khác nhau nhưng hiện được dùng không nhất quán trên Admin Portal. Operator khó biết đây là:
- sửa balance thủ công (correction),
- đảo một giao dịch đã post (reversal),
- hay trả lại tiền/credit cho user (refund).
Mục tiêu task: chuẩn hóa taxonomy hiển thị (presentation layer) — map từ ledger/API sang label UI rõ ràng, kèm metadata audit trên trang Detail.
2. Định nghĩa nghiệp vụ (source of truth)
| Trường hợp | UI wording | Mô tả ngắn |
|---|---|---|
| Admin cộng balance để sửa sai | Balance Correction — Credit | Sửa sổ thủ công; có ticket/reason/admin; không bắt buộc gắn 1 payment tx gốc |
| Admin trừ balance để sửa sai | Balance Correction — Debit | Clawback / debit correction; có ticket/reason/admin |
| Đảo giao dịch đã có | Transaction Reversal | Ledger đối ứng 1-1 với giao dịch gốc (reject withdrawal, chained USD movement…) |
| Trả lại tiền hoặc credit cho user | Refund | User nhận lại balance vì flow thất bại hoặc admin refund có chủ đích |
2.1 Không dùng “Adjustment” làm nhãn chung
- Cấm hiển thị badge/filter/export label
Adjustmentcho mọi correction/reversal/refund. - DB/API có thể vẫn giữ
type: adjustment— chỉ UI đổi label.
2.2 Transaction Reversal — trường bắt buộc trên Detail
| Field | Mô tả |
|---|---|
| Original Transaction ID | ID giao dịch bị đảo (business transactionId hoặc ledger row id — ưu tiên business id) |
| Reversal Transaction ID | ID dòng reversal hiện tại |
| Reason | Lý do đảo (admin note, rejection reason, system message…) |
| Initiated By | Admin actor hoặc System nếu auto (payout fail webhook) |
2.3 Balance Correction / Refund — trường bắt buộc trên Detail
| Field | Mô tả |
|---|---|
| Reason | Bắt buộc — từ metadata.reason, metadata.note, mô tả refund… |
| Initiated By | Admin (metadata.adminId, adminRejection.rejectedByAdminId…) hoặc System |
3. Required changes (product)
3.1 Wording table (Admin UI — English)
| Internal signal | UI label |
|---|---|
| Admin credit correction | Balance Correction — Credit |
| Admin debit correction | Balance Correction — Debit |
| Transaction reversal | Transaction Reversal |
| User refund | Refund |
3.2 Acceptance criteria
- AC-1: Không còn hiển thị “Adjustment” như tên chung trên list/badge/filter/export (Admin).
- AC-2: Credit correction và Debit correction phân biệt rõ (label + hướng tiền +/-).
- AC-3: Reversal có link hoặc reference về transaction gốc (clickable khi route tồn tại).
- AC-4: Refund chỉ dùng khi thực sự trả balance cho user — không gắn nhãn Refund cho mọi
type=reversal. - AC-5: Trang Detail hiển thị Reason và Initiated By (không chỉ generic reason label như “Balance adjustment”).
4. Trạng thái hiện tại (gap analysis)
4.1 USD Wallet Ledger — lệch nghiêm trọng
File: kioskgaming_admin/src/components/pages/usdWallets/usdWalletLedgerUtils.ts
DB type | Badge hiện tại | reasonLabel hiện tại | Vấn đề |
|---|---|---|---|
reversal | Refund | Balance correction | Trái spec: reversal ≠ refund |
adjustment | Adjustment | Balance adjustment | Nhãn chung; không tách Credit/Debit |
Detail: UsdWalletActivityView.tsx — section “Source & reason” dùng ledgerDisplay.reasonLabel generic, không surface metadata.reason, metadata.adminId, sourceWithdrawalTransactionId.
4.2 Backend baseline (không đổi enum trong v1)
Model: UsdWalletTransaction.type = deposit | withdrawal | adjustment | reversal
| Flow | DB type | Service | Gần wording mới |
|---|---|---|---|
POST /api/admin/finance-corrections (debit) | adjustment | usdWalletService.recordAdjustment | Balance Correction — Debit |
| Reject pending USD withdrawal | reversal | usdWalletService.recordReversal | Transaction Reversal |
Shop CDN credit-adjust direction=credit|debit | wallet ledger | adminAgentManagementBizService.applyShopWalletAdjust | Balance Correction — Credit/Debit |
| Payout fail → credit wallet | thường credit + payoutRefund metadata | paymentUserController, webhooks | Refund (rule product) |
| Admin game wallet refund | metadata.source=admin_refund | paymentAdminController._refundSingleTransaction | Refund |
Reversal metadata (reject withdrawal) — đã có:
{
"sourceWithdrawalId": "<usd_wallet_transaction.uuid>",
"sourceWithdrawalTransactionId": "<business transactionId>",
"note": "<admin rejection note>"
}
Finance correction metadata — đã có:
{
"ticketId": "INC-12345",
"reason": "...",
"adminId": "...",
"idempotencyKey": "..."
}
4.3 Các màn Admin khác còn “Adjustment”
| File | Vị trí |
|---|---|
WalletTransactionTable.tsx | Filter + badge adjustment |
GameWalletTransactionTable.tsx | Direction filter adjustment → “Adjustment” |
AgentWalletTransferPanel.tsx | Option adjustment |
agentCdnTxUtils.ts | Label map |
CreditWalletActivityView.tsx | admin_shop_adjustment → “Admin shop adjustment” |
referenceTypeDescriptions.ts | admin_shop_adjustment blurb |
Đã đúng hướng: FinanceCorrectionDebitModal.tsx — “Balance correction (debit)”.
5. Classification rules (implementation)
Tạo một helper dùng chung (đề xuất: kioskgaming_admin/src/utils/ledgerCorrectionDisplay.ts hoặc mở rộng usdWalletLedgerUtils.ts).
5.1 Pseudocode
function resolveCorrectionDisplay(input):
meta = parse metadata
type = lower(input.type)
refType = lower(input.referenceType)
netDelta = balanceNetAfter - balanceNetBefore
// --- Refund (user receives balance back) ---
IF meta.source == 'admin_refund'
OR meta.payoutRefund?.refundedAt
OR refType matches payment refund flow
OR provider indicates automatic payout refund (document per case)
→ label: "Refund"
show: reason, initiatedBy (admin or System)
// --- Transaction Reversal ---
IF type == 'reversal'
AND (
refType == 'usd_wallet_transaction'
OR meta.sourceWithdrawalTransactionId
OR meta.originalTransactionId with explicit reversal pairing
)
→ label: "Transaction Reversal"
show: originalTxId, reversalTxId, reason, initiatedBy
link: original ledger row / withdrawal detail when route exists
// --- Balance Correction ---
IF type == 'adjustment'
OR refType IN (
admin_finance_correction,
admin_shop_adjustment,
admin_agent_adjustment,
admin_super_agent_adjustment
)
OR AdminActionLog action == finance_balance_correction
→ IF netDelta >= 0 OR direction == credit
label: "Balance Correction — Credit"
ELSE
label: "Balance Correction — Debit"
show: reason, ticketId, initiatedBy
// --- Legacy fallback (no "Adjustment") ---
IF type == 'adjustment' OR type == 'reversal' (unclassified)
→ infer Credit/Debit from netDelta; prefix "Balance Correction —" or "Transaction Reversal"
per heuristics; log unknown pattern for follow-up
5.2 Product decision cần chốt trước ship
| # | Câu hỏi | Đề xuất mặc định |
|---|---|---|
| D-1 | Payout gateway fail credit wallet vs admin reject withdrawal — cùng recordReversal shape | Refund khi provider thuộc payout-fail allowlist; Transaction Reversal khi admin reject (admin_withdrawal_reject_reversal). Chi tiết §12.2 |
| D-2 | Balance Correction — Credit cho USD wallet | Hiện recordAdjustment chỉ debit; credit USD có thể qua deposit path — document riêng nếu chưa có API thống nhất |
| D-3 | Export CSV / notifications | Cùng wording mới với UI list |
6. Implementation scope (tóm tắt)
Chi tiết task reviewable: §13 Implementation plan.
| Phase | Mục tiêu | Task IDs |
|---|---|---|
| 0 | Gate & constants | K260-001 → K260-003 |
| 1 | Backend classifier + API | K260-010 → K260-018 |
| 2 | Admin USD UI | K260-020 → K260-028 |
| 3 | CDN / Game / Player UI | K260-030 → K260-036 |
| 4 | Payment detail & export | K260-040 → K260-043 |
| 5 | QC & release | K260-050 → K260-052 |
Nguyên tắc chia task: 1 PR ≈ 1 task · diff target < 300 LOC · single concern · có test hoặc snapshot QC riêng.
7. UI copy reference
7.1 Badge / list column
| Label | Badge tone (gợi ý) |
|---|---|
| Balance Correction — Credit | Slate / blue, + amount |
| Balance Correction — Debit | Slate / amber, - amount |
| Transaction Reversal | Indigo |
| Refund | Emerald / orange (giữ phân biệt với deposit) |
7.2 Detail section titles
- Correction details — Reason, Ticket ID, Initiated By, Amount, Balance before/after
- Reversal trace — Original Transaction ID (link), Reversal Transaction ID, Reason, Initiated By
- Refund details — Reason, Initiated By, Related payment transaction (link)
7.3 Microcopy
| Key | Text |
|---|---|
| Empty reason | No reason recorded. |
| System actor | System (automatic) |
| Unknown admin | Admin (ID: …) with copy |
8. Test plan (QC)
8.1 Balance Correction — Debit
- Super admin apply finance correction debit shop USD (
POST /api/admin/finance-corrections). - Mở USD wallet ledger → row type
adjustment. - Expect: badge
Balance Correction — Debit; Detail có ticket, reason, admin name/id.
8.2 Balance Correction — Credit
- Shop CDN credit-adjust
direction=credit(legacy API hoặc future credit correction). - Expect:
Balance Correction — Credit; không hiện “Adjustment”.
8.3 Transaction Reversal
- Reject pending USD withdrawal (manual, non-auto payout).
- Expect: reversal row label
Transaction Reversal(không phải Refund). - Detail: Original Transaction ID = withdrawal
transactionId; Reversal ID = reversal row; Reason = rejection note; Initiated By = rejecting admin.
8.4 Refund
- Admin refund failed game wallet transaction (
admin_refund). - Expect: label
Refund; không dùng Reversal. - Payout fail auto-refund (BTCPay cancel): label
Refund; reason từpayoutRefund.refundReason.
8.5 Regression
- Export CSV ledger dùng label mới.
- Không breaking filter values gửi API (filter vẫn có thể dùng
type=adjustmentnội bộ; UI label khác). -
usdWalletLedgerUtils.test.tscập nhật assertions.
9. Mapping nhanh — scenario → label
| Scenario | Label UI |
|---|---|
| Finance correction debit (USD/CDN) | Balance Correction — Debit |
| Shop credit-adjust credit | Balance Correction — Credit |
| Reject USD withdrawal → reversal row | Transaction Reversal |
BTCPay / gateway payout fail → wallet credited (recordReversal, provider payout-fail) | Refund (see §12.2 — same row shape as reject reversal; classify by provider) |
| Admin refund game wallet | Refund |
Legacy row adjustment không metadata | Balance Correction — Credit/Debit (theo delta) |
10. Definition of done
- Tất cả AC (§3.2) pass trên staging.
- Không còn string user-facing
"Adjustment"trên các màn in-scope (grepkioskgaming_admin). - Unit tests + QC checklist §8 signed off.
- Product sign-off trên decision D-1, D-2 (§5.2).
12. Solution Architect Review (2026-06-20)
Review góc kiến trúc — đánh giá tính khả thi, mâu thuẫn nội bộ spec, và khuyến nghị trước khi implement V1.2.0.
12.1 Verdict tổng quan
| Tiêu chí | Đánh giá |
|---|---|
| Vấn đề cần giải | ✅ Đúng — confusion operator là pain thật, gap USD ledger đã được mô tả chính xác |
| Hướng “presentation layer trước” | ⚠️ Chấp nhận được cho V1.2.0 nếu có rule precedence rõ và test matrix đủ |
| Spec hiện tại ready-to-implement | ❌ Chưa — còn mâu thuẫn Refund vs Reversal (§12.2), thiếu cross-row metadata lookup (§12.3), AC-2 vs backend USD credit gap (§12.4) |
| Effort ước lượng | Phase 1 (USD): 3–5 dev-days · Full 3 phase + backend enrich: 8–12 dev-days |
Khuyến nghị: Ship Phase 1 + backend enrich tối thiểu trong V1.2.0; Phase 2–3 có thể cùng release nếu dùng một shared classifier (§12.5), không copy logic sang từng file.
12.2 Mâu thuẫn nghiêm trọng: Refund vs Transaction Reversal
Spec hiện tự mâu thuẫn:
| Nguồn trong doc | Kết luận |
|---|---|
| §5.1 pseudocode | Mọi type=reversal + sourceWithdrawalTransactionId → Transaction Reversal |
| §9 mapping | “BTCPay / gateway payout fail → wallet credited” → Refund |
| AC-4 | Refund không gắn cho mọi type=reversal |
Thực tế codebase: payout fail (portalUsdWithdrawalPayoutService) và admin reject withdrawal (usdWalletManagementController) cùng pattern:
usdWalletService.recordReversalreferenceType: usd_wallet_transactionmetadata.sourceWithdrawalId,metadata.sourceWithdrawalTransactionId
→ Cùng mechanical shape; không thể phân loại chỉ bằng metadata trên reversal row.
Quyết định kiến trúc bắt buộc (chốt D-1 revised):
| Scenario | Label UI | Signal phân loại (đề xuất) |
|---|---|---|
| Admin reject pending withdrawal | Transaction Reversal | provider = admin_withdrawal_reject_reversal hoặc metadata.adminRejection trên source withdrawal row |
| Auto payout fail / webhook fail → hoàn USD vào wallet | Refund | provider IN (portal_withdrawal_payout_fail_reversal, …) hoặc metadata.payoutPhase = failed trên source withdrawal |
| Chained USD movement (generic reversal) | Transaction Reversal | referenceType = usd_wallet_transaction và không thuộc refund provider list |
Action: Cập nhật §5.1, §9 và test §8.3/8.4 theo bảng trên. Không dùng type=reversal alone làm discriminator.
12.3 Metadata không nằm trên cùng một row — cần enrichment
Detail yêu cầu Reason + Initiated By, nhưng dữ liệu phân tán:
| Field UI | Thường nằm ở đâu |
|---|---|
| Rejection reason | metadata.note trên reversal row; hoặc metadata.adminRejection.note trên source withdrawal row |
| Initiated By (reject) | metadata.adminRejection.rejectedByAdminId trên source withdrawal, không phải reversal row |
| Finance correction reason/ticket | metadata.reason, metadata.ticketId, metadata.adminId trên adjustment row ✅ |
| Payout refund reason | PaymentTransaction.syncMetadata.payoutRefund — không trên UsdWalletTransaction |
Khuyến nghị kiến trúc (Phase 1, không optional):
GET /admin/usd-wallets/ledger/:id(hoặc enrich list payload) trả thêm block:
{
"displayCategory": "transaction_reversal | balance_correction_debit | balance_correction_credit | refund",
"displayLabel": "Transaction Reversal",
"audit": {
"reason": "...",
"initiatedBy": { "kind": "admin|system", "adminId": "...", "displayName": "..." },
"originalTransactionId": "...",
"reversalTransactionId": "..."
},
"links": [{ "kind": "usd_ledger|payment_transaction", "id": "...", "label": "..." }]
}
-
Admin UI chỉ render
displayCategory/audit— không nhân bộ heuristic ở 6+ component. -
Classifier logic một module backend:
usdLedgerDisplayCategoryService.js(hoặc mở rộngusdLedgerEnrichment.js) + mirror unit test; frontend có thin wrapper cho CDN/game wallets until backend parity.
Lý do: Tránh drift giữa list, detail, CSV export, Telegram notify, và QC automation.
12.4 AC-2 vs gap backend USD Credit Correction
| Fact | Impact |
|---|---|
recordAdjustment chỉ debit (trừ balance) | Mọi finance-correction USD hiện tại → Debit only |
Không có recordAdjustment credit symmetric | AC-2 “Credit correction” không test được trên USD wallet trong V1.2.0 trừ khi map qua recordDeposit ad-hoc |
WalletTransaction.type = credit | debit only (không có adjustment) | WalletTransactionTable filter adjustment có thể là legacy/API alias — cần verify trước khi đổi label |
Khuyến nghị:
- Ghi rõ trong AC: “Balance Correction — Credit (USD)” out of scope V1.2.0 hoặc mở ticket backend
recordAdjustmentCredit/ dùng deposit córeferenceType: admin_finance_correction. - CDN credit-adjust (
direction=credit) in scope Phase 2.
12.5 Kiến trúc module đề xuất
┌─────────────────────────────────────────────────────────┐
│ Backend (source of truth for display taxonomy) │
│ usdLedgerDisplayCategoryService │
│ ← UsdWalletTransaction + linked source row │
│ ← PaymentTransaction.syncMetadata (refund) │
│ ← Admin lookup (initiatedBy displayName) │
└───────────────────────┬─────────────────────────────────┘
│ displayCategory + audit DTO
┌───────────────┼───────────────┐
▼ ▼ ▼
Admin list Admin detail CSV export
badges Reversal trace (same labels)
Shared TS (optional v1.1): @kioskgaming/ledger-display package dùng chung admin + future super-agent read-only views.
Anti-pattern cần tránh: Mở rộng chỉ usdWalletLedgerUtils.ts mà không backend enrich → Phase 3 lại viết lại logic cho TransactionDetails.tsx.
12.6 Phạm vi còn thiếu trong doc
| Surface | Ghi chú |
|---|---|
usdLedgerEnrichment.js / CSV export backend | Export có thể vẫn raw type=adjustment |
transactionBellNotifyService | Notification copy có thể vẫn “adjustment” |
AdjustBalanceModal (flagged withdrawal) | Legacy adjust-balance vs finance-corrections — wording chưa thống nhất |
adminTransactionDetailBizService | Timeline leg verb: Reversal — chưa map display taxonomy |
| Payment transaction detail | payoutRefund banner đã có; cần link tới wallet Refund row |
| Filter UX | AC-1 cấm label “Adjustment” nhưng API query param vẫn có thể type=adjustment — document “internal type vs display label” |
12.7 Classification precedence (canonical — thay §5.1)
Thứ tự bắt buộc (first match wins):
- Refund —
metadata.source = admin_refund·syncMetadata.payoutRefund.refundedAt· provider trongREFUND_PROVIDER_ALLOWLIST· game deposit refund flow - Transaction Reversal —
type = reversalvà (provider = admin_withdrawal_reject_reversalhoặc chained reversal không thuộc refund allowlist) - Balance Correction — Debit —
type = adjustmenthoặcreferenceTypeadmin correction vànetDelta < 0/ledgerType = debit - Balance Correction — Credit — admin correction reference và
netDelta > 0/ledgerType = credit - Legacy fallback — infer Credit/Debit từ delta; never emit label “Adjustment”; log
unknown_ledger_display_patternmetric
REFUND_PROVIDER_ALLOWLIST (draft): portal_withdrawal_payout_fail_reversal, … (maintain in code constant + doc appendix).
12.8 Test matrix bổ sung (bắt buộc trước ship)
| # | Case | Expected label |
|---|---|---|
| T-1 | Admin reject manual withdrawal | Transaction Reversal |
| T-2 | Portal auto payout fail reversal | Refund |
| T-3 | Finance correction USD debit | Balance Correction — Debit |
| T-4 | Shop CDN credit-adjust credit | Balance Correction — Credit |
| T-5 | Reversal row without resolvable source row | Fallback + telemetry; không crash Detail |
| T-6 | Initiated By = admin id only | Resolve displayName qua API enrich |
| T-7 | CSV export row T-2 | Same label as UI (not raw reversal) |
12.9 Definition of Done — bổ sung
- D-1 revised signed off (Refund vs Reversal by provider / source context, not
typealone) - Backend
displayCategory(+ audit DTO) on USD ledger detail or documented exception with SA approval - Không còn mâu thuẫn giữa §5.1, §9 và AC-4
- Metric/ log cho unclassified legacy rows
- Cross-reference QC doc
site-documents/docs/qc/finance/(tạoTask-kiosk-260.mdkhi implement)
12.10 Changelog (review)
| Date | Author | Notes |
|---|---|---|
| 2026-06-20 | Solution Architect review | §12 added — precedence, backend enrich, D-1 fix, scope gaps |
| 2026-06-20 | Solution Architect | §13 added — reviewable task breakdown plan |
13. Implementation plan — reviewable tasks
Mỗi task = 1 PR, scope hẹp, reviewer chỉ cần đọc 1 concern.
Size: XS ≈ 2–4h · S ≈ 0.5 ngày · M ≈ 1 ngày (tránh M nếu có thể tách).
13.0 Dependency graph (high level)
flowchart LR
K001[K260-001 Gate] --> K010[K260-010 Tests]
K003[K260-003 Constants] --> K010
K010 --> K011[K260-011 Classifier]
K011 --> K012[K260-012 Source resolver]
K012 --> K013[K260-013 Admin resolver]
K013 --> K014[K260-014 List API]
K013 --> K015[K260-015 Detail API]
K014 --> K020[K260-020 TS types]
K015 --> K022[K260-022 Detail sections]
K020 --> K021[K260-021 List badges]
K021 --> K028[K260-028 Cleanup utils]
Phase 0 — Gate & shared contracts
K260-001 · Product sign-off D-1 + scope AC-2 USD Credit
| Repo | site-documents (comment on ticket) |
| Size | XS |
| Depends | — |
| Deliverable | Comment/ticket: D-1 revised approved; AC-2 ghi rõ USD Credit correction out of scope V1.2.0 (CDN credit in scope Phase 3) |
| Review checklist | [ ] Bảng provider Reversal vs Refund đã chốt · [ ] Không block dev Phase 1 |
PR: docs(K260-001): sign off D-1 and USD credit scope
K260-002 · API contract — displayCategory + audit DTO
| Repo | site-documents |
| Size | XS |
| Depends | K260-001 |
| Deliverable | Appendix trong doc này hoặc file Task-kiosk-260-api-contract.md: JSON schema, enum values, links[] kinds |
| Review checklist | [ ] Enum khớp §12.7 · [ ] Field nullable documented · [ ] Backward compatible (fields additive) |
PR: docs(K260-002): displayCategory API contract
K260-003 · Backend constants — provider allowlists
| Repo | kioskgaming_backend |
| Size | XS |
| Depends | K260-001 |
| Files | src/constants/usdLedgerDisplayCategories.js (new) |
| Deliverable | DISPLAY_CATEGORY_*, REFUND_PROVIDER_ALLOWLIST, REVERSAL_PROVIDER_ALLOWLIST, ADMIN_CORRECTION_REFERENCE_TYPES |
| Review checklist | [ ] Constants match §12.2 table · [ ] JSDoc cites Task-kiosk-260 · [ ] No runtime logic yet |
PR: feat(K260-003): USD ledger display category constants
Phase 1 — Backend classifier (TDD first)
K260-010 · Classifier unit tests (fixtures only)
| Repo | kioskgaming_backend |
| Size | S |
| Depends | K260-003 |
| Files | tests/unit/usdLedger/usdLedgerDisplayCategoryService.test.js, tests/fixtures/usdLedgerDisplay/*.json |
| Deliverable | Tests T-1…T-5 (§12.8) red — cover reject reversal, payout-fail reversal, finance debit, legacy adjustment, missing source row |
| Review checklist | [ ] Fixtures mirror real metadata shapes · [ ] Precedence cases explicit · [ ] Tests fail until K260-011 |
PR: test(K260-010): USD display category fixtures and cases
K260-011 · Pure classifier — resolveDisplayCategory(row, context?)
| Repo | kioskgaming_backend |
| Size | S |
| Depends | K260-010 |
| Files | src/services/display/usdLedgerDisplayCategoryService.js |
| Deliverable | Function: input plain ledger row (+ optional preloaded source row) → { displayCategory, displayLabel }; no DB |
| Review checklist | [ ] K260-010 green · [ ] Precedence §12.7 · [ ] Never returns label “Adjustment” |
PR: feat(K260-011): USD ledger display category classifier
K260-012 · Source row resolver
| Repo | kioskgaming_backend |
| Size | S |
| Depends | K260-011 |
| Files | usdLedgerDisplayCategoryService.js (+ helper loadLinkedUsdLedgerContext.js) |
| Deliverable | Batch-load source UsdWalletTransaction when referenceType=usd_wallet_transaction; attach to classifier context |
| Review checklist | [ ] N+1 avoided (batch map) · [ ] Missing source → graceful fallback · [ ] Unit test with mocked repo |
PR: feat(K260-012): load linked USD ledger row for display
K260-013 · Audit builder — reason + initiatedBy + tx ids
| Repo | kioskgaming_backend |
| Size | S |
| Depends | K260-012 |
| Files | usdLedgerDisplayCategoryService.js |
| Deliverable | buildDisplayAudit(row, context) → { reason, initiatedBy, originalTransactionId, reversalTransactionId }; system vs admin |
| Review checklist | [ ] Reject: reads adminRejection from source row · [ ] Finance correction: metadata.reason/ticketId/adminId · [ ] Empty reason → null not fake string |
PR: feat(K260-013): USD ledger display audit DTO builder
K260-014 · Admin displayName lookup (batch)
| Repo | kioskgaming_backend |
| Size | S |
| Depends | K260-013 |
| Files | usdLedgerDisplayCategoryService.js or adminDisplayNameResolver.js |
| Deliverable | Resolve adminId → { displayName, email? } batched for list enrich |
| Review checklist | [ ] Unknown admin → { kind:'admin', adminId, displayName:null } · [ ] No extra query per row in loop |
PR: feat(K260-014): batch admin display name for ledger audit
K260-015 · Wire classifier into ledger list API
| Repo | kioskgaming_backend |
| Size | S |
| Depends | K260-014 |
| Files | usdLedgerEnrichment.js or presentUsdLedgerRows path, usdWalletManagementController.js |
| Deliverable | Each row in GET /admin/usd-wallets/ledger includes displayCategory, displayLabel, audit (summary), links (optional minimal) |
| Review checklist | [ ] Additive JSON only · [ ] List perf smoke (50 rows) · [ ] Integration test 1 case |
PR: feat(K260-015): enrich USD ledger list with displayCategory
K260-016 · Wire classifier into ledger detail API
| Repo | kioskgaming_backend |
| Size | XS |
| Depends | K260-015 |
| Files | getUsdWalletLedgerById in usdWalletManagementController.js |
| Deliverable | GET /admin/usd-wallets/ledger/:id returns full audit + links (source ledger, payment tx if derivable) |
| Review checklist | [ ] Same classifier as list · [ ] 404 unchanged · [ ] OpenAPI/doc comment updated |
PR: feat(K260-016): enrich USD ledger detail with full audit DTO
K260-017 · Links builder — deep link targets
| Repo | kioskgaming_backend |
| Size | XS |
| Depends | K260-016 |
| Files | usdLedgerDisplayCategoryService.js |
| Deliverable | links[] entries with kind (usd_ledger or payment_transaction), id, label for reversal/refund rows |
| Review checklist | [ ] Reversal → link source withdrawal uuid · [ ] Payment tx link only when resolvable · [ ] No broken ids |
PR: feat(K260-017): USD ledger display deep link targets
K260-018 · CSV export — displayLabel column
| Repo | kioskgaming_backend |
| Size | S |
| Depends | K260-015 |
| Files | export path in usdWalletManagementController.js |
| Deliverable | Export adds column Display type (= displayLabel); keep raw type column for ops |
| Review checklist | [ ] T-7 §12.8 · [ ] Column order documented · [ ] No regression on existing columns |
PR: feat(K260-018): USD ledger CSV display label column
Phase 2 — Admin USD UI (consume API only)
K260-020 · TypeScript types — UsdLedgerDisplay
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | K260-002 |
| Files | src/types/usdLedgerDisplay.ts (new) |
| Deliverable | Types matching API contract; map displayCategory → badge class helper types only |
| Review checklist | [ ] Matches backend enum · [ ] Optional fields correct |
PR: types(K260-020): USD ledger display DTO types
K260-021 · List badges — use displayLabel from API
| Repo | kioskgaming_admin |
| Size | S |
| Depends | K260-015, K260-020 |
| Files | usdWalletLedgerUtils.ts (badge path only), USD wallets list component |
| Deliverable | Badge text = API displayLabel; fallback to legacy only if field missing (feature flag or version check) |
| Review checklist | [ ] No user-facing “Adjustment”/mislabeled “Refund” on list · [ ] Badge colors §7.1 · [ ] Fallback logged in dev |
PR: feat(K260-021): USD wallet list badges from displayLabel
K260-022 · Detail section — Balance Correction
| Repo | kioskgaming_admin |
| Size | S |
| Depends | K260-016, K260-020 |
| Files | UsdWalletActivityView.tsx |
| Deliverable | Conditional block when displayCategory is correction_*: Reason, Ticket ID, Initiated By |
| Review checklist | [ ] Renders only for correction categories · [ ] Empty states §7.3 · [ ] No duplicate generic reasonLabel |
PR: feat(K260-022): USD detail correction audit section
K260-023 · Detail section — Transaction Reversal trace
| Repo | kioskgaming_admin |
| Size | S |
| Depends | K260-016, K260-017 |
| Files | UsdWalletActivityView.tsx |
| Deliverable | Block: Original Transaction ID, Reversal Transaction ID, Reason, Initiated By + link from links[] |
| Review checklist | [ ] AC-3 · [ ] Copy buttons on ids · [ ] T-1 manual QC steps pass |
PR: feat(K260-023): USD detail reversal trace section
K260-024 · Detail section — Refund
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | K260-016 |
| Files | UsdWalletActivityView.tsx |
| Deliverable | Block when displayCategory=refund: Reason, Initiated By, related payment link |
| Review checklist | [ ] AC-4 · [ ] T-2 scenario · [ ] Distinct from Reversal UI |
PR: feat(K260-024): USD detail refund section
K260-025 · Filter labels — rename “Adjustment” filter copy
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | K260-021 |
| Files | USD wallets filter UI (if any type filter exposed) |
| Deliverable | UI label “Balance corrections” / group; query param vẫn type=adjustment |
| Review checklist | [ ] AC-1 filter copy · [ ] API param unchanged · [ ] Tooltip explains internal type |
PR: fix(K260-025): USD ledger filter display labels
K260-026 · Remove incorrect legacy labels in usdWalletLedgerUtils
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | K260-021 |
| Files | usdWalletLedgerUtils.ts |
| Deliverable | Delete getLedgerUxStatus paths that map reversal→Refund, adjustment→Adjustment; keep fallback for rows without API enrich |
| Review checklist | [ ] grep no 'Refund' for reversal type in utils · [ ] Tests updated |
PR: refactor(K260-026): remove legacy USD ledger mislabels
K260-027 · Update usdWalletLedgerUtils.test.ts
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | K260-026 |
| Files | usdWalletLedgerUtils.test.ts |
| Deliverable | Tests aligned with API-driven labels or explicit fallbacks |
| Review checklist | [ ] CI green · [ ] No assertions on old wrong labels |
PR: test(K260-027): update USD ledger utils tests
K260-028 · Spike cleanup — reasonLabel generic swap
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | K260-022–024 |
| Files | usdWalletLedgerUtils.ts (resolveBusinessLabels adjustment/reversal branches) |
| Deliverable | reasonLabel for correction/reversal/refund defers to audit.reason when present; generic “Balance adjustment” removed |
| Review checklist | [ ] AC-5 · [ ] Deposit/withdrawal labels unchanged |
PR: fix(K260-028): use audit reason instead of generic reasonLabel
Phase 3 — CDN / Game / Player (frontend-only v1)
Phase 3 chưa có backend classifier CDN — dùng thin mapper trong admin, reuse constants pattern từ K260-003 (copy enum to TS or shared doc). Ticket follow-up: backend parity.
K260-030 · Player wallet — badge labels only
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | K260-003 (TS copy of reference types) |
| Files | WalletTransactionTable.tsx |
| Deliverable | Map referenceType + credit/debit → correction/refund labels; verify if adjustment filter still valid |
| Review checklist | [ ] No “Adjustment” badge · [ ] Filter still works · [ ] Scope: labels only |
PR: feat(K260-030): player wallet transaction display labels
K260-031 · Game wallet — Refund vs Adjustment direction
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | — |
| Files | GameWalletTransactionTable.tsx |
| Deliverable | Keep admin_refund → Refund; adjustment direction → Balance Correction — Credit/Debit by amount sign or metadata |
| Review checklist | [ ] Refund modal flows unchanged · [ ] Filter option labels updated |
PR: feat(K260-031): game wallet direction display labels
K260-032 · Credit wallet activity — reference type labels
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | — |
| Files | CreditWalletActivityView.tsx, referenceTypeDescriptions.ts |
| Deliverable | admin_shop_adjustment → “Balance correction (shop)”; finance correction refs aligned |
| Review checklist | [ ] Copy consistent with §7 · [ ] Descriptions not misleading |
PR: docs(K260-032): credit wallet reference label copy
K260-033 · Agent CDN utils + transfer panel labels
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | — |
| Files | agentCdnTxUtils.ts, AgentWalletTransferPanel.tsx |
| Deliverable | Replace “Adjustment” option label with “Balance correction” |
| Review checklist | [ ] API value unchanged · [ ] UI only |
PR: fix(K260-033): agent CDN adjustment label copy
K260-034 · Finance correction modal — align Credit/Debit copy
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | — |
| Files | FinanceCorrectionDebitModal.tsx |
| Deliverable | Title/toast use exact strings §3.1 (“Balance Correction — Debit”) |
| Review checklist | [ ] Consistent with list badges · [ ] No functional change |
PR: fix(K260-034): finance correction modal wording
K260-035 · Flagged withdrawal AdjustBalanceModal copy
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | — |
| Files | AdjustBalanceModal.tsx |
| Deliverable | Placeholder/help text references “Balance correction” not generic “adjustment” |
| Review checklist | [ ] Legacy API unchanged · [ ] Copy only |
PR: fix(K260-035): flagged withdrawal modal wording
K260-036 · Grep gate — no user-facing “Adjustment”
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | K260-021…035 |
| Deliverable | Script or CI note: rg 'Adjustment' src/ allowlist for internal enums/API keys only |
| Review checklist | [ ] AC-1 · [ ] Allowlist documented in PR |
PR: chore(K260-036): document Adjustment string allowlist
Phase 4 — Payment detail & cross-links
K260-040 · Payment detail — link to USD Refund ledger row
| Repo | kioskgaming_admin + optional backend |
| Size | S |
| Depends | K260-016 |
| Files | TransactionDetails.tsx, maybe API to find ledger by payment tx |
| Deliverable | When syncMetadata.payoutRefund.walletTransactionId or USD reversal exists → link “View wallet refund” |
| Review checklist | [ ] Link only when id exists · [ ] No new mislabel |
PR: feat(K260-040): payment detail link to USD refund ledger
K260-041 · Cash flow timeline — displayLabel for USD legs
| Repo | kioskgaming_backend + kioskgaming_admin |
| Size | S |
| Depends | K260-015 |
| Files | adminTransactionDetailBizService.js, timeline consumer |
| Deliverable | USD legs in payment detail use displayLabel not raw Reversal/Deposit where applicable |
| Review checklist | [ ] Non-USD legs unchanged · [ ] One payment tx QC case |
PR: feat(K260-041): payment timeline USD leg display labels
K260-042 · Export payment transactions — optional display column
| Repo | kioskgaming_admin |
| Size | XS |
| Depends | K260-041 |
| Files | transactionDisplayUtils.ts export helpers |
| Deliverable | If export mentions adjustment/refund, use new wording |
| Review checklist | [ ] D-3 · [ ] Scope limited to export strings |
PR: fix(K260-042): payment export display wording
K260-043 · Notification copy audit (optional V1.2.0)
| Repo | kioskgaming_backend |
| Size | XS |
| Depends | K260-015 |
| Files | transactionBellNotifyService.js |
| Deliverable | Bell messages for finance correction use “Balance correction” not “adjustment” |
| Review checklist | [ ] Message-only · [ ] i18n N/A |
PR: fix(K260-043): bell notification wording for corrections
Phase 5 — QC & release
K260-050 · QC checklist doc
| Repo | site-documents |
| Size | XS |
| Depends | Phase 1–2 done |
| Files | docs/qc/finance/task-kiosk-260.md (new) |
| Deliverable | Executable steps T-1…T-7 (§12.8) + screenshots placeholders |
| Review checklist | [ ] Maps to AC · [ ] Staging data prerequisites listed |
PR: docs(K260-050): QC checklist Task-kiosk-260
K260-051 · Staging verification sign-off
| Repo | ticket / PR comment |
| Size | XS |
| Depends | K260-050, all dev tasks |
| Deliverable | QA comment: AC-1…AC-5 checked |
| Review checklist | [ ] §10 DoD complete |
PR: N/A — QA ticket
K260-052 · Release note snippet V1.2.0
| Repo | site-documents or release doc |
| Size | XS |
| Depends | K260-051 |
| Deliverable | Operator-facing note: terminology change table §3.1 |
| Review checklist | [ ] CS/Ops informed · [ ] No promise of USD credit correction if out of scope |
PR: docs(K260-052): V1.2.0 admin ledger wording release note
13.1 Suggested sprint allocation
| Sprint | Tasks | Goal |
|---|---|---|
| S1 | K260-001…003, K260-010…014 | Classifier + tests green locally |
| S2 | K260-015…018, K260-020…021 | API enrich + list badges |
| S3 | K260-022…028 | USD detail complete — MVP shippable |
| S4 | K260-030…036, K260-040…043 | Remaining surfaces + polish |
| S5 | K260-050…052 | QC + release |
MVP cutoff (recommended): end of S3 — USD wallet list + detail + CSV đúng wording; Phase 3 có thể V1.2.1 nếu cần rút scope.
13.2 PR review rubric (mọi task)
Reviewer xác nhận:
- Single concern — PR title matches one K260-xxx id
- No new classifier logic in admin (Phase 2+) — UI consumes API
- Tests — backend unit hoặc frontend test / manual QC step cited
- Strings — không introduce user-facing “Adjustment”
- Backward compatible — API additive; filters unchanged
13.3 Task index (quick lookup)
| ID | Title | Phase |
|---|---|---|
| K260-001 | Product sign-off D-1 | 0 |
| K260-002 | API contract doc | 0 |
| K260-003 | Backend constants | 0 |
| K260-010 | Classifier tests (red) | 1 |
| K260-011 | Pure classifier | 1 |
| K260-012 | Source row resolver | 1 |
| K260-013 | Audit DTO builder | 1 |
| K260-014 | Admin displayName batch | 1 |
| K260-015 | List API enrich | 1 |
| K260-016 | Detail API enrich | 1 |
| K260-017 | Links builder | 1 |
| K260-018 | CSV display column | 1 |
| K260-020 | TS types | 2 |
| K260-021 | List badges | 2 |
| K260-022 | Detail correction section | 2 |
| K260-023 | Detail reversal trace | 2 |
| K260-024 | Detail refund section | 2 |
| K260-025 | Filter labels | 2 |
| K260-026 | Remove legacy mislabels | 2 |
| K260-027 | Utils unit tests | 2 |
| K260-028 | reasonLabel cleanup | 2 |
| K260-030 | Player wallet labels | 3 |
| K260-031 | Game wallet labels | 3 |
| K260-032 | Credit wallet labels | 3 |
| K260-033 | Agent CDN labels | 3 |
| K260-034 | Finance modal copy | 3 |
| K260-035 | Flagged withdrawal copy | 3 |
| K260-036 | Grep gate | 3 |
| K260-040 | Payment → USD link | 4 |
| K260-041 | Timeline USD labels | 4 |
| K260-042 | Payment export wording | 4 |
| K260-043 | Bell notifications | 4 |
| K260-050 | QC doc | 5 |
| K260-051 | Staging sign-off | 5 |
| K260-052 | Release note | 5 |
Total: 32 tasks · ~22 dev-days sequential · ~8–10 dev-days với 2 dev song song Phase 1/2