Skip to main content

Task-kiosk-260 — Chuẩn hóa wording Adjustment / Reversal / Refund

FieldValue
Task IDTask-kiosk-260
Target release / Fix versionV1.2.0
ScopeAdmin 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 docsAdmin financial correction debit spec · Admin USD wallet transaction detail view · Backend code review (full)

1. Background

Các từ Adjustment, ReversalRefund 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ợpUI wordingMô tả ngắn
Admin cộng balance để sửa saiBalance Correction — CreditSử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 saiBalance Correction — DebitClawback / debit correction; có ticket/reason/admin
Đảo giao dịch đã cóTransaction ReversalLedger đố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 userRefundUser 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 Adjustment cho 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

FieldMô tả
Original Transaction IDID giao dịch bị đảo (business transactionId hoặc ledger row id — ưu tiên business id)
Reversal Transaction IDID dòng reversal hiện tại
ReasonLý do đảo (admin note, rejection reason, system message…)
Initiated ByAdmin actor hoặc System nếu auto (payout fail webhook)

2.3 Balance Correction / Refund — trường bắt buộc trên Detail

FieldMô tả
ReasonBắt buộc — từ metadata.reason, metadata.note, mô tả refund…
Initiated ByAdmin (metadata.adminId, adminRejection.rejectedByAdminId…) hoặc System

3. Required changes (product)

3.1 Wording table (Admin UI — English)

Internal signalUI label
Admin credit correctionBalance Correction — Credit
Admin debit correctionBalance Correction — Debit
Transaction reversalTransaction Reversal
User refundRefund

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ị ReasonInitiated 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 typeBadge hiện tạireasonLabel hiện tạiVấn đề
reversalRefundBalance correctionTrái spec: reversal ≠ refund
adjustmentAdjustmentBalance adjustmentNhã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

FlowDB typeServiceGần wording mới
POST /api/admin/finance-corrections (debit)adjustmentusdWalletService.recordAdjustmentBalance Correction — Debit
Reject pending USD withdrawalreversalusdWalletService.recordReversalTransaction Reversal
Shop CDN credit-adjust direction=credit|debitwallet ledgeradminAgentManagementBizService.applyShopWalletAdjustBalance Correction — Credit/Debit
Payout fail → credit walletthường credit + payoutRefund metadatapaymentUserController, webhooksRefund (rule product)
Admin game wallet refundmetadata.source=admin_refundpaymentAdminController._refundSingleTransactionRefund

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”

FileVị trí
WalletTransactionTable.tsxFilter + badge adjustment
GameWalletTransactionTable.tsxDirection filter adjustment → “Adjustment”
AgentWalletTransferPanel.tsxOption adjustment
agentCdnTxUtils.tsLabel map
CreditWalletActivityView.tsxadmin_shop_adjustment → “Admin shop adjustment”
referenceTypeDescriptions.tsadmin_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-1Payout gateway fail credit wallet vs admin reject withdrawal — cùng recordReversal shapeRefund khi provider thuộc payout-fail allowlist; Transaction Reversal khi admin reject (admin_withdrawal_reject_reversal). Chi tiết §12.2
D-2Balance Correction — Credit cho USD walletHiệ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-3Export CSV / notificationsCùng wording mới với UI list

6. Implementation scope (tóm tắt)

Chi tiết task reviewable: §13 Implementation plan.

PhaseMục tiêuTask IDs
0Gate & constantsK260-001 → K260-003
1Backend classifier + APIK260-010 → K260-018
2Admin USD UIK260-020 → K260-028
3CDN / Game / Player UIK260-030 → K260-036
4Payment detail & exportK260-040 → K260-043
5QC & releaseK260-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

LabelBadge tone (gợi ý)
Balance Correction — CreditSlate / blue, + amount
Balance Correction — DebitSlate / amber, - amount
Transaction ReversalIndigo
RefundEmerald / 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

KeyText
Empty reasonNo reason recorded.
System actorSystem (automatic)
Unknown adminAdmin (ID: …) with copy

8. Test plan (QC)

8.1 Balance Correction — Debit

  1. Super admin apply finance correction debit shop USD (POST /api/admin/finance-corrections).
  2. Mở USD wallet ledger → row type adjustment.
  3. Expect: badge Balance Correction — Debit; Detail có ticket, reason, admin name/id.

8.2 Balance Correction — Credit

  1. Shop CDN credit-adjust direction=credit (legacy API hoặc future credit correction).
  2. Expect: Balance Correction — Credit; không hiện “Adjustment”.

8.3 Transaction Reversal

  1. Reject pending USD withdrawal (manual, non-auto payout).
  2. Expect: reversal row label Transaction Reversal (không phải Refund).
  3. Detail: Original Transaction ID = withdrawal transactionId; Reversal ID = reversal row; Reason = rejection note; Initiated By = rejecting admin.

8.4 Refund

  1. Admin refund failed game wallet transaction (admin_refund).
  2. Expect: label Refund; không dùng Reversal.
  3. 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=adjustment nội bộ; UI label khác).
  • usdWalletLedgerUtils.test.ts cập nhật assertions.

9. Mapping nhanh — scenario → label

ScenarioLabel UI
Finance correction debit (USD/CDN)Balance Correction — Debit
Shop credit-adjust creditBalance Correction — Credit
Reject USD withdrawal → reversal rowTransaction 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 walletRefund
Legacy row adjustment không metadataBalance 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 (grep kioskgaming_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-implementChư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ượngPhase 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 docKết luận
§5.1 pseudocodeMọi type=reversal + sourceWithdrawalTransactionIdTransaction Reversal
§9 mapping“BTCPay / gateway payout fail → wallet credited” → Refund
AC-4Refund 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.recordReversal
  • referenceType: usd_wallet_transaction
  • metadata.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):

ScenarioLabel UISignal phân loại (đề xuất)
Admin reject pending withdrawalTransaction Reversalprovider = admin_withdrawal_reject_reversal hoặc metadata.adminRejection trên source withdrawal row
Auto payout fail / webhook fail → hoàn USD vào walletRefundprovider IN (portal_withdrawal_payout_fail_reversal, …) hoặc metadata.payoutPhase = failed trên source withdrawal
Chained USD movement (generic reversal)Transaction ReversalreferenceType = usd_wallet_transaction 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 UIThường nằm ở đâu
Rejection reasonmetadata.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/ticketmetadata.reason, metadata.ticketId, metadata.adminId trên adjustment row
Payout refund reasonPaymentTransaction.syncMetadata.payoutRefundkhông trên UsdWalletTransaction

Khuyến nghị kiến trúc (Phase 1, không optional):

  1. 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": "..." }]
}
  1. Admin UI chỉ render displayCategory / audit — không nhân bộ heuristic ở 6+ component.

  2. Classifier logic một module backend: usdLedgerDisplayCategoryService.js (hoặc mở rộng usdLedgerEnrichment.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

FactImpact
recordAdjustment chỉ debit (trừ balance)Mọi finance-correction USD hiện tại → Debit only
Không có recordAdjustment credit symmetricAC-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

SurfaceGhi chú
usdLedgerEnrichment.js / CSV export backendExport có thể vẫn raw type=adjustment
transactionBellNotifyServiceNotification copy có thể vẫn “adjustment”
AdjustBalanceModal (flagged withdrawal)Legacy adjust-balance vs finance-corrections — wording chưa thống nhất
adminTransactionDetailBizServiceTimeline leg verb: Reversal — chưa map display taxonomy
Payment transaction detailpayoutRefund banner đã có; cần link tới wallet Refund row
Filter UXAC-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):

  1. Refundmetadata.source = admin_refund · syncMetadata.payoutRefund.refundedAt · provider trong REFUND_PROVIDER_ALLOWLIST · game deposit refund flow
  2. Transaction Reversaltype = reversal (provider = admin_withdrawal_reject_reversal hoặc chained reversal không thuộc refund allowlist)
  3. Balance Correction — Debittype = adjustment hoặc referenceType admin correction netDelta < 0 / ledgerType = debit
  4. Balance Correction — Credit — admin correction reference netDelta > 0 / ledgerType = credit
  5. Legacy fallback — infer Credit/Debit từ delta; never emit label “Adjustment”; log unknown_ledger_display_pattern metric

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)

#CaseExpected label
T-1Admin reject manual withdrawalTransaction Reversal
T-2Portal auto payout fail reversalRefund
T-3Finance correction USD debitBalance Correction — Debit
T-4Shop CDN credit-adjust creditBalance Correction — Credit
T-5Reversal row without resolvable source rowFallback + telemetry; không crash Detail
T-6Initiated By = admin id onlyResolve displayName qua API enrich
T-7CSV export row T-2Same 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 type alone)
  • 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ạo Task-kiosk-260.md khi implement)

12.10 Changelog (review)

DateAuthorNotes
2026-06-20Solution Architect review§12 added — precedence, backend enrich, D-1 fix, scope gaps
2026-06-20Solution 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

Reposite-documents (comment on ticket)
SizeXS
Depends
DeliverableComment/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

Reposite-documents
SizeXS
DependsK260-001
DeliverableAppendix 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

Repokioskgaming_backend
SizeXS
DependsK260-001
Filessrc/constants/usdLedgerDisplayCategories.js (new)
DeliverableDISPLAY_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)

Repokioskgaming_backend
SizeS
DependsK260-003
Filestests/unit/usdLedger/usdLedgerDisplayCategoryService.test.js, tests/fixtures/usdLedgerDisplay/*.json
DeliverableTests 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?)

Repokioskgaming_backend
SizeS
DependsK260-010
Filessrc/services/display/usdLedgerDisplayCategoryService.js
DeliverableFunction: 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

Repokioskgaming_backend
SizeS
DependsK260-011
FilesusdLedgerDisplayCategoryService.js (+ helper loadLinkedUsdLedgerContext.js)
DeliverableBatch-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

Repokioskgaming_backend
SizeS
DependsK260-012
FilesusdLedgerDisplayCategoryService.js
DeliverablebuildDisplayAudit(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)

Repokioskgaming_backend
SizeS
DependsK260-013
FilesusdLedgerDisplayCategoryService.js or adminDisplayNameResolver.js
DeliverableResolve 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

Repokioskgaming_backend
SizeS
DependsK260-014
FilesusdLedgerEnrichment.js or presentUsdLedgerRows path, usdWalletManagementController.js
DeliverableEach 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

Repokioskgaming_backend
SizeXS
DependsK260-015
FilesgetUsdWalletLedgerById in usdWalletManagementController.js
DeliverableGET /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


Repokioskgaming_backend
SizeXS
DependsK260-016
FilesusdLedgerDisplayCategoryService.js
Deliverablelinks[] 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

Repokioskgaming_backend
SizeS
DependsK260-015
Filesexport path in usdWalletManagementController.js
DeliverableExport 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

Repokioskgaming_admin
SizeXS
DependsK260-002
Filessrc/types/usdLedgerDisplay.ts (new)
DeliverableTypes 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

Repokioskgaming_admin
SizeS
DependsK260-015, K260-020
FilesusdWalletLedgerUtils.ts (badge path only), USD wallets list component
DeliverableBadge 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

Repokioskgaming_admin
SizeS
DependsK260-016, K260-020
FilesUsdWalletActivityView.tsx
DeliverableConditional 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

Repokioskgaming_admin
SizeS
DependsK260-016, K260-017
FilesUsdWalletActivityView.tsx
DeliverableBlock: 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

Repokioskgaming_admin
SizeXS
DependsK260-016
FilesUsdWalletActivityView.tsx
DeliverableBlock 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

Repokioskgaming_admin
SizeXS
DependsK260-021
FilesUSD wallets filter UI (if any type filter exposed)
DeliverableUI 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

Repokioskgaming_admin
SizeXS
DependsK260-021
FilesusdWalletLedgerUtils.ts
DeliverableDelete 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

Repokioskgaming_admin
SizeXS
DependsK260-026
FilesusdWalletLedgerUtils.test.ts
DeliverableTests 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

Repokioskgaming_admin
SizeXS
DependsK260-022–024
FilesusdWalletLedgerUtils.ts (resolveBusinessLabels adjustment/reversal branches)
DeliverablereasonLabel 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

Repokioskgaming_admin
SizeXS
DependsK260-003 (TS copy of reference types)
FilesWalletTransactionTable.tsx
DeliverableMap 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

Repokioskgaming_admin
SizeXS
Depends
FilesGameWalletTransactionTable.tsx
DeliverableKeep 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

Repokioskgaming_admin
SizeXS
Depends
FilesCreditWalletActivityView.tsx, referenceTypeDescriptions.ts
Deliverableadmin_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

Repokioskgaming_admin
SizeXS
Depends
FilesagentCdnTxUtils.ts, AgentWalletTransferPanel.tsx
DeliverableReplace “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

Repokioskgaming_admin
SizeXS
Depends
FilesFinanceCorrectionDebitModal.tsx
DeliverableTitle/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

Repokioskgaming_admin
SizeXS
Depends
FilesAdjustBalanceModal.tsx
DeliverablePlaceholder/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”

Repokioskgaming_admin
SizeXS
DependsK260-021…035
DeliverableScript 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


Repokioskgaming_admin + optional backend
SizeS
DependsK260-016
FilesTransactionDetails.tsx, maybe API to find ledger by payment tx
DeliverableWhen 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

Repokioskgaming_backend + kioskgaming_admin
SizeS
DependsK260-015
FilesadminTransactionDetailBizService.js, timeline consumer
DeliverableUSD 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

Repokioskgaming_admin
SizeXS
DependsK260-041
FilestransactionDisplayUtils.ts export helpers
DeliverableIf 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)

Repokioskgaming_backend
SizeXS
DependsK260-015
FilestransactionBellNotifyService.js
DeliverableBell 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

Reposite-documents
SizeXS
DependsPhase 1–2 done
Filesdocs/qc/finance/task-kiosk-260.md (new)
DeliverableExecutable 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

Repoticket / PR comment
SizeXS
DependsK260-050, all dev tasks
DeliverableQA comment: AC-1…AC-5 checked
Review checklist[ ] §10 DoD complete

PR: N/A — QA ticket


K260-052 · Release note snippet V1.2.0

Reposite-documents or release doc
SizeXS
DependsK260-051
DeliverableOperator-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

SprintTasksGoal
S1K260-001…003, K260-010…014Classifier + tests green locally
S2K260-015…018, K260-020…021API enrich + list badges
S3K260-022…028USD detail complete — MVP shippable
S4K260-030…036, K260-040…043Remaining surfaces + polish
S5K260-050…052QC + 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:

  1. Single concern — PR title matches one K260-xxx id
  2. No new classifier logic in admin (Phase 2+) — UI consumes API
  3. Tests — backend unit hoặc frontend test / manual QC step cited
  4. Strings — không introduce user-facing “Adjustment”
  5. Backward compatible — API additive; filters unchanged

13.3 Task index (quick lookup)

IDTitlePhase
K260-001Product sign-off D-10
K260-002API contract doc0
K260-003Backend constants0
K260-010Classifier tests (red)1
K260-011Pure classifier1
K260-012Source row resolver1
K260-013Audit DTO builder1
K260-014Admin displayName batch1
K260-015List API enrich1
K260-016Detail API enrich1
K260-017Links builder1
K260-018CSV display column1
K260-020TS types2
K260-021List badges2
K260-022Detail correction section2
K260-023Detail reversal trace2
K260-024Detail refund section2
K260-025Filter labels2
K260-026Remove legacy mislabels2
K260-027Utils unit tests2
K260-028reasonLabel cleanup2
K260-030Player wallet labels3
K260-031Game wallet labels3
K260-032Credit wallet labels3
K260-033Agent CDN labels3
K260-034Finance modal copy3
K260-035Flagged withdrawal copy3
K260-036Grep gate3
K260-040Payment → USD link4
K260-041Timeline USD labels4
K260-042Payment export wording4
K260-043Bell notifications4
K260-050QC doc5
K260-051Staging sign-off5
K260-052Release note5

Total: 32 tasks · ~22 dev-days sequential · ~8–10 dev-days với 2 dev song song Phase 1/2