Files
apskel-pos-backend/docs/tasks-enakgame.md
T

504 lines
23 KiB
Markdown
Raw Normal View History

# Task Breakdown: EnakGame
**Sumber:** [RFC EnakGame](rfc-enakgame.md), [PRD EnakGame](enakgame-prd.md)
**Tanggal:** 2026-10-07
Setiap task menyebut bagian RFC yang dikerjakan, lapisan kode yang disentuh, task yang
harus selesai lebih dulu, dan kriteria selesai. Ukuran: **S** ≤ 1 hari, **M** 2–3 hari,
**L** 4–5 hari.
Konvensi kode mengikuti yang sudah ada: `migrations/` (lanjut dari `000101`),
`entities` → `repository` → `processor` → `service` → `handler` / `validator` →
`router`, dan wiring di `internal/app/app.go`. Repository **selalu** memakai
`DBFromContext`. Semua mutasi saldo **hanya** lewat `WalletProcessor`.
---
## Ringkasan
| Fase | Task | Terblokir oleh |
|---|---|---|
| 0. Matikan bayar dengan EnakPoint | EG-001 – EG-002 | – |
| 1. Fondasi | EG-101 – EG-106 | – |
| 2. Admin game & session | EG-201 – EG-206 | – |
| 3. Reward Engine & complete | EG-301 – EG-303 | Pembulatan (RFC §19.2 #1) sebelum **rilis** |
| 4. Economy Guard | EG-401 – EG-402 | – |
| 5. Voucher & redemption | EG-501 – EG-507 | – |
| 6. Budget metrics | EG-601 – EG-602 | Exhaustion policy & threshold (#3, #4) sebelum **rilis** |
| 7. Event / campaign | EG-701 – EG-704 | Event stacking (#2) sebelum **rilis** |
| 8. Voucher eksternal | EG-801 – EG-803 | Provider pertama yang konkret sebelum **dikerjakan** |
| 9. Budget Controller & analytics | EG-901 – EG-903 | Threshold (#4) sebelum **dikerjakan** |
| 10. Spin & bersih-bersih | EG-1001 – EG-1003 | – |
Fase 0 independen dan bisa jalan paralel dengan semua fase lain, tetapi **harus selesai
sebelum EnakGame rilis** (PRD §3.2).
Fase 1–3 membuat game bisa dimainkan end-to-end dengan Coin. Fase 5 membuat Point bisa
ditukar. Fase 6 membuat Finance bisa melihat biaya.
```
Fase 0 EG-001 ── EG-002
Fondasi EG-101 ── EG-102 ── EG-105
EG-103, EG-104, EG-106, EG-301 (independen)
Game EG-104 + EG-105 ─────────────────── EG-201, EG-203
EG-104 + EG-105 + EG-301 ────────── EG-202
EG-103 + EG-201 + EG-202 + EG-203 ─ EG-204 ── EG-205
EG-204 + EG-205 + EG-301 + EG-302 ─ EG-303
EG-106 ── EG-401
EG-303 + EG-401 ─────────────────── EG-402
Voucher EG-501 ── EG-502
EG-103 + EG-502 + EG-503 ────────── EG-504
EG-303 + EG-504 ─────────────────── EG-505 ── EG-601 ─┬─ EG-602
└─ EG-901 ── EG-902
EG-504 ── EG-801
EG-505 + EG-801 ─────────────────── EG-802 ── EG-803
Event EG-102 ── EG-701
EG-203 + EG-701 ─────────────────── EG-702
EG-402 + EG-702 ─────────────────── EG-703
Akhir EG-402 ── EG-1001 ── EG-1002 ── EG-1003
```
Task kecil yang tidak digambar: EG-206, EG-302 (setelah EG-105), EG-506 (EG-504), EG-507
(EG-501), EG-704 (EG-206 + EG-702), EG-903 (EG-303 + EG-505).
---
## Fase 0 — Matikan Bayar dengan EnakPoint
PRD §3.2: Point hanya bisa ditukar ke voucher. Belum ada order yang dibayar dengan Point,
jadi tidak ada data yang dimigrasi.
### EG-001 · Tutup semua jalur pembayaran EnakPoint · M
- **RFC:** §14
- **Kerjakan:**
- Sudah dikonfirmasi belum ada pembayaran dengan EnakPoint di production (2026-10-07).
- Migrasi: drop trigger yang membuat payment method `point` untuk organisasi baru
(`000094`), lalu nonaktifkan / hapus baris `payment_methods` tipe `point`.
- Tolak pembayaran bertipe `point` di jalur POS (`order_handler`, `order_processor`,
`payment_method_processor`) dan customer app (`customer_order_payment_service`).
- Hapus route `GET /orders/:id/point-payment/preview` dan
`POST /customer/wallet/payment-code`.
- Hapus `PAYMENT` dan `PAYMENT_REFUND` dari `walletTypeRules`, supaya engine menolak
keduanya. CHECK database dibiarkan.
- Hapus setting `loyalty.point.accept_payment` dari descriptor
`LoyaltySettingsProcessor` dan dari response outlet customer.
- **Selesai jika:** pembayaran order dengan method `point` ditolak di POS dan customer app,
outlet baru tidak lagi mendapat payment method EnakPoint, dan test yang ada diperbarui.
- **Bergantung pada:** –
### EG-002 · Hapus kode point payment · S
- **Kerjakan:** hapus `PointPaymentProcessor`, `point_payment_refund.go`,
`PointPaymentRepository`, `PointPaymentService`, `PointPaymentHandler`, wiring di
`app.go` / `router.go`, dan test-nya (`point_payment_db_test.go`,
`point_payment_method_db_test.go`). Pindahkan dulu helper yang ternyata dipakai di
tempat lain (mis. pola pembuatan lot refund, yang dipakai ulang di EG-205).
- **Selesai jika:** `go build ./...` dan seluruh test lulus, dan
`grep -rn "PointPayment" internal/` kosong.
- **Bergantung pada:** EG-001
---
## Fase 1 — Fondasi
### EG-101 · Migrasi extend `games` + arsipkan game lama · S
- **RFC:** §5.1, §14
- **Kerjakan:** migrasi up & down persis seperti §5.1. Semua baris lama menjadi
`ARCHIVED`. **Tidak ada `DELETE`** (FK `game_plays` CASCADE).
- **Selesai jika:**
- Up/down bersih di database kosong dan di salinan staging.
- Jumlah baris `game_plays` sebelum = sesudah.
- Ditolak database: game `ACTIVE` tanpa `organization_id` atau `slug`;
`entry_cost = 0`; dua game dengan slug sama di satu org.
- **Bergantung pada:** –
### EG-102 · Migrasi budget, reward config, session · M
- **RFC:** §5.2, §5.3, §5.4, §5.6
- **Kerjakan:** `game_budgets`, `game_reward_configs`, `game_sessions`,
`game_session_rewards`, dalam urutan foreign key.
- **Selesai jika:** up/down bersih. Ditolak database: dua config `ACTIVE` untuk satu game;
session `REFUNDED` tanpa `refund_transaction_id`; dua budget `GLOBAL` dengan
`period_start` sama di satu org; `game_session_rewards` dengan
`wallet_transaction_id` yang sama dua kali.
- **Bergantung pada:** EG-101
### EG-103 · Tipe ledger baru · M
- **RFC:** §6
- **Kerjakan:**
- Konstanta `GAME_SPEND_REFUND`, `GAME_REWARD`, `REWARD_REDEEM_REFUND`, ref
`GAME_SESSION` di `constants/wallet.go`.
- `walletTypeRules`: tambah tiga tipe, dan izinkan ref `GAME_SESSION` pada
`GAME_SPEND`.
- Migrasi: drop + create ulang tiga CHECK di `wallet_transactions` (§6.2).
- Periksa invariant di `wallet_reconciliation_repository.go` terhadap tipe baru.
- **Selesai jika:** test validasi di `wallet_processor_test.go` per tipe baru (currency
salah, ref salah, reversal tanpa `reverses_transaction_id` → ditolak), dan test DB yang
benar-benar insert ke `wallet_transactions` untuk setiap tipe (lolos engine **dan**
CHECK).
- **Bergantung pada:** –
### EG-104 · `audit_logs` + helper · S
- **RFC:** §5.9, §13
- **Kerjakan:** migrasi, entity, dan `AuditLogger.Record(ctx, entry)` yang menulis lewat
`DBFromContext`, sehingga audit ikut commit/rollback bersama perubahannya.
- **Selesai jika:** perubahan yang di-rollback tidak meninggalkan baris audit.
- **Bergantung pada:** –
### EG-105 · Entities & repository EnakGame · M
- **RFC:** §5.1–§5.6
- **Kerjakan:** entities dan repository untuk kolom baru `games`, `game_reward_configs`,
`game_sessions`, `game_session_rewards`, `game_budgets`. Transisi status session
sebagai `UPDATE ... WHERE status = 'STARTED'` yang mengembalikan jumlah baris
ter-update (D4). Semua query game/budget memfilter `organization_id`.
- **Selesai jika:** test repository: dua transisi bersamaan pada session yang sama, hanya
satu yang mendapat 1 baris.
- **Bergantung pada:** EG-101, EG-102
### EG-106 · Setting limit EnakGame · S
- **RFC:** §5.10
- **Kerjakan:** key `enakgame.limit.user_daily` dan `enakgame.limit.global_daily` sebagai
field descriptor di `LoyaltySettingsProcessor` (default 0 = tanpa batas, min 0).
- **Selesai jika:** setting terbaca dengan default, bisa diubah lewat endpoint loyalty
settings yang ada, dan perubahan tercatat di `loyalty_setting_changes`.
- **Bergantung pada:** –
---
## Fase 2 — Admin Game & Session
### EG-201 · Admin game CRUD · M
- **RFC:** §11 (admin)
- **Kerjakan:** `/marketing/enakgame/games` CRUD + `PUT /:id/status`, org dari user admin.
Validasi `slug`, `entry_cost ≥ 1`, `result_rules`. Game `ARCHIVED` tidak bisa diubah.
Perubahan status masuk `audit_logs`.
- **Selesai jika:** admin org A tidak bisa melihat atau mengubah game org B; game lama
(arsip) tidak muncul.
- **Bergantung pada:** EG-104, EG-105
### EG-202 · Admin reward config versioning · M
- **RFC:** §5.2, D7
- **Kerjakan:** `POST /games/:id/reward-configs` (versi baru, `rules` divalidasi
`RewardCalculator.Validate`), `POST /reward-configs/:id/activate` (dalam satu transaksi:
config lama → `RETIRED`, yang baru → `ACTIVE`), `GET` daftar versi. Tidak ada endpoint
update atau delete. Wajib `RequireLoyaltyManager`. Semua aksi masuk `audit_logs`.
- **Selesai jika:** config yang sudah dipakai session tidak bisa diubah lewat jalur apa
pun; aktivasi bersamaan dua versi berakhir dengan tepat satu `ACTIVE`.
- **Bergantung pada:** EG-104, EG-105, EG-301
### EG-203 · Admin budget + `GameBudgetPeriodJob` · M
- **RFC:** §5.6, §12
- **Kerjakan:** CRUD `/marketing/enakgame/budgets` (scope `GLOBAL` / `EVENT`), wajib
`RequireLoyaltyManager`, audit. Job harian yang membuat budget global bulan berikutnya
dari bulan berjalan bila belum ada.
- **Selesai jika:** job aman dijalankan berulang (unique index mencegah duplikat), dan
perubahan `amount` tercatat sebelum/sesudah di audit.
- **Bergantung pada:** EG-104, EG-105
### EG-204 · Start Game · M
- **RFC:** §7.1
- **Kerjakan:** `POST /customer/enakgame/sessions` dengan `Idempotency-Key` wajib (≤ 50
karakter, pola `customer_wallet_handler.go`). Langkah persis §7.1.
- **Selesai jika:** test untuk:
- Key yang sama dua kali → satu debit, session yang sama dikembalikan.
- Coin kurang → ditolak, tidak ada ledger maupun session.
- Game org lain, game tidak `ACTIVE`, tanpa config aktif, tanpa budget global → ditolak.
- `entry_cost` dan `reward_config_id` tersimpan sebagai snapshot: mengubah game setelah
start tidak mengubah session.
- **Bergantung pada:** EG-103, EG-105, EG-201, EG-202, EG-203
### EG-205 · Refund otomatis + `GameSessionJob` · M
- **RFC:** §7.3, §6.3
- **Kerjakan:**
- `RefundSession(ctx, sessionID, reason)`: lock wallet → transisi bersyarat ke
`REFUNDED` → `Credit` `GAME_SPEND_REFUND` dengan lot per alokasi asal
(`RefundExpiry`, `origin_lot_id`) → audit `SYSTEM`.
- Job tiap 1 menit, per batch, satu transaksi per session, sesuai tabel §7.3.
- Wiring start/stop di `app.go` seperti `WalletExpiryJob`.
- **Selesai jika:** test untuk:
- Game dinonaktifkan → session `STARTED` di-refund tanpa menunggu kedaluwarsa.
- Expired dengan `completion_failed_at` → refund. Expired tanpa → `EXPIRED`, saldo tetap.
- Lot refund punya `origin_lot_id` ke lot asal dan `expires_at` minimal 7 hari.
- Complete dan refund bersamaan pada session yang sama → tepat satu yang berhasil.
- Job dijalankan dua kali → tidak ada refund ganda.
- **Bergantung pada:** EG-204
### EG-206 · Customer: daftar game & riwayat session · S
- **RFC:** §11 (customer)
- **Kerjakan:** `GET /customer/enakgame/games`, `GET /sessions`, `GET /sessions/:id`. Hanya
game `ACTIVE` milik org customer. Rincian internal (`reward_breakdown` lengkap, angka
RNG) tidak ditampilkan.
- **Bergantung pada:** EG-105
---
## Fase 3 — Reward Engine & Complete
### EG-301 · `RewardCalculator` + empat tipe · M
- **RFC:** §8
- **Kerjakan:** interface `Validate` / `Calculate`, implementasi `FIXED`, `SCORE_BASED`,
`OUTCOME_BASED`, `PROBABILITY`. RNG lewat interface yang diisi `crypto/rand` di
production dan RNG tetap di test. Pembulatan ke bawah.
- **Selesai jika:** unit test: contoh tabel PRD §12 untuk setiap tipe; band skor
tumpang tindih / berlubang ditolak `Validate`; bobot `PROBABILITY` nol atau negatif
ditolak; distribusi `PROBABILITY` pada 100.000 undian berada dalam toleransi bobotnya.
- **Bergantung pada:** –
### EG-302 · Result Validator · S
- **RFC:** §7.2 langkah 5
- **Kerjakan:** validasi `result` terhadap `games.result_rules`: durasi minimal, skor
maksimum, skor per detik, outcome yang dikenal. Mengembalikan alasan, bukan error.
- **Selesai jika:** unit test per aturan, termasuk game tanpa `result_rules` (semua lolos).
- **Bergantung pada:** EG-105
### EG-303 · Complete Game · L
- **RFC:** §7.2, §7.3 (bagian `completion_failed_at`)
- **Kerjakan:**
- `POST /customer/enakgame/sessions/:id/complete`, langkah §7.2 dengan base reward dari
budget global saja. Event menyusul di EG-703, guard di EG-402.
- Satu baris `GAME_REWARD` + `game_session_rewards` per budget, key
`game-reward:{session}:{budget}`. Lot dengan `ComputeExpiry(CoinExpiry)`.
- Game tidak `ACTIVE` saat complete → `RefundSession(GAME_DEACTIVATED)`.
- Error non-bisnis → tulis `completion_failed_at` di transaksi terpisah
(`DetachTransaction`), lalu kembalikan 5xx.
- **Selesai jika:** test untuk:
- Complete dua kali → satu reward, response kedua sama dengan yang pertama.
- Hasil tidak valid → reward 0, `flagged`, session `COMPLETED`, tanpa refund.
- Session milik customer lain → 404. Session expired → ditolak.
- Error yang disuntikkan setelah kredit → rollback total, `completion_failed_at` terisi.
- Request yang membawa field `reward` diabaikan (P1).
- **Bergantung pada:** EG-204, EG-205, EG-301, EG-302
---
## Fase 4 — Economy Guard
### EG-401 · Counter reward + increment bersyarat · M
- **RFC:** §5.8, §9
- **Kerjakan:** migrasi `game_reward_counters`; repository `Consume(scope, id, day, x,
limit)` yang mengembalikan jumlah yang **benar-benar** diterima (dipotong ke sisa
limit, 0 bila habis). Hari dihitung di Asia/Jakarta.
- **Selesai jika:** test DB: 20 goroutine menambah counter yang sama dengan limit 100 →
total tepat 100, tidak pernah lewat.
- **Bergantung pada:** EG-106
### EG-402 · Guard di complete · S
- **RFC:** §9
- **Kerjakan:** panggil `Consume` untuk `USER` dan `GLOBAL` harian (dari setting) dan
`GAME` harian (dari `result_rules`) sebelum kredit. Batas yang memotong dicatat di
`reward_breakdown` dan dikembalikan di response.
- **Selesai jika:** reward 30 dengan sisa limit user 10 → kredit 10, breakdown menyebut
limit user. Sisa 0 → session `COMPLETED` dengan reward 0.
- **Bergantung pada:** EG-303, EG-401
---
## Fase 5 — Voucher & Redemption
### EG-501 · Migrasi voucher · M
- **RFC:** §5.7
- **Kerjakan:** `vouchers`, `voucher_codes`, `voucher_redemptions`,
`voucher_redemption_costs`.
- **Selesai jika:** up/down bersih. Ditolak database: voucher `STATIC` tanpa `stock`;
`EXTERNAL` tanpa `provider`; kode `REDEEMED` tanpa `redemption_id`; dua redemption dengan
`(customer_id, idempotency_key)` yang sama.
- **Bergantung pada:** –
### EG-502 · Admin voucher + impor kode · M
- **RFC:** §11 (admin)
- **Kerjakan:** CRUD `/marketing/enakgame/vouchers`, `POST /:id/codes` (CSV, kode duplikat
dilewati dan dilaporkan), `GET /:id/codes` dengan jumlah per status. `RequireLoyaltyManager`
dan audit.
- **Selesai jika:** impor CSV yang sama dua kali tidak menggandakan kode.
- **Bergantung pada:** EG-104, EG-501
### EG-503 · Aksi PIN `REDEEM` · S
- **RFC:** §7.4 langkah 2
- **Kerjakan:** `PinActionRedeem` di `customer_pin_processor.go`, ikut aturan kunci yang
ada (5 salah, 30 menit).
- **Bergantung pada:** –
### EG-504 · Redemption internal · L
- **RFC:** §7.4
- **Kerjakan:** `POST /customer/enakgame/vouchers/:id/redeem` untuk `STATIC` dan
`CODE_POOL`, satu transaksi, langkah persis §7.4. Debit `REWARD_REDEEM` dengan key
`redeem:{redemption}`.
- **Selesai jika:** test untuk:
- Stok tersisa 1, dua customer menukar bersamaan → tepat satu berhasil.
- `CODE_POOL`: dua redemption bersamaan mendapat kode berbeda (`SKIP LOCKED`).
- Key yang sama dua kali → satu debit, satu kode.
- Point kurang, PIN salah, `max_per_customer` tercapai, voucher di luar masa berlaku →
ditolak tanpa apa pun tercatat.
- **Bergantung pada:** EG-103, EG-501, EG-502, EG-503
### EG-505 · Atribusi realized cost · M
- **RFC:** §7.6, D5
- **Kerjakan:** query rekursif §7.6 di dalam transaksi redemption, pembagian
`face_value` proporsional dengan sisa pembulatan ke bagian terbesar, insert
`voucher_redemption_costs`.
- **Selesai jika:** test DB untuk rantai: `GAME_REWARD` → exchange → redeem;
`GAME_REWARD` → transfer → exchange → redeem; campuran `GAME_REWARD` + `EARN` (contoh
§7.6: Rp7.500 ke budget, Rp2.500 tanpa budget); `Σ cost = face_value` untuk kombinasi
angka yang tidak habis dibagi.
- **Bergantung pada:** EG-303, EG-504
### EG-506 · Customer: katalog & voucher saya · S
- **RFC:** §11 (customer)
- **Kerjakan:** `GET /customer/enakgame/vouchers` (stok tersedia, tanpa membocorkan
jumlah kode per status) dan `GET /redemptions` (dengan kode voucher).
- **Bergantung pada:** EG-504
### EG-507 · `VoucherCodeExpiryJob` · S
- **RFC:** §12
- **Kerjakan:** `AVAILABLE → EXPIRED` untuk kode lewat `expires_at`, per batch.
- **Bergantung pada:** EG-501
---
## Fase 6 — Budget Metrics
### EG-601 · Perhitungan metrik & status budget · M
- **RFC:** §10
- **Kerjakan:** realized cost, Coin issued, remaining, utilization, forecast, exposure,
dan status per budget. Global dibatasi periode; event tanpa batas waktu. Threshold dari
`game_budgets.thresholds`.
- **Selesai jika:** test dengan data tetap menghasilkan angka contoh PRD §30 (budget
Rp100M, realized Rp60M, sisa 10 hari); Point dari `EARN` tidak ikut dihitung.
- **Bergantung pada:** EG-505
### EG-602 · Endpoint metrik budget · S
- **Kerjakan:** `GET /marketing/enakgame/budgets/:id/metrics`.
- **Bergantung pada:** EG-601
---
## Fase 7 — Event / Campaign
### EG-701 · Migrasi event · S
- **RFC:** §5.5
- **Kerjakan:** `game_events`, `game_event_games`.
- **Selesai jika:** up/down bersih; event tanpa `budget_id` atau dengan
`end_at ≤ start_at` ditolak.
- **Bergantung pada:** EG-102
### EG-702 · Admin event · M
- **RFC:** §5.5, §11
- **Kerjakan:** CRUD + status. Budget yang dipasang harus milik org yang sama dan ber-scope
`EVENT`. Audit.
- **Bergantung pada:** EG-203, EG-701
### EG-703 · Modifier event di complete · M
- **RFC:** §7.2 langkah 7, §8 (modifier), D6
- **Kerjakan:** cari event aktif untuk game, hitung tambahan dari multiplier dan bonus,
pisahkan menjadi komponen per budget, terapkan `reward_limit` dan `user_daily_limit`
event lewat counter `EVENT`, cap `max_reward`. Aturan tumpuk mengikuti default PRD §16.
- **Selesai jika:** contoh PRD §7 (10 Coin + Ramadan 2x) menghasilkan dua baris
`GAME_REWARD`: 10 ke budget global, 10 ke budget event; event di luar jam aktif tidak
berpengaruh.
- **Bergantung pada:** EG-402, EG-702
### EG-704 · Event di daftar game customer · S
- **Kerjakan:** `GET /customer/enakgame/games` menyertakan event aktif per game (nama,
banner, multiplier/bonus, berakhir kapan).
- **Bergantung pada:** EG-206, EG-702
---
## Fase 8 — Voucher Eksternal
Terblokir sampai provider pertama ditentukan: API-nya menentukan bentuk adapter dan
apakah provider menerima idempotency key (RFC §18).
### EG-801 · Interface provider + adapter pertama · M
- **Kerjakan:** `VoucherProvider` (`Issue(ctx, redemptionID, voucher)`,
`Lookup(ctx, redemptionID)`), adapter provider pertama, dan klasifikasi hasil: sukses,
gagal pasti, tidak jelas.
- **Bergantung pada:** EG-504
### EG-802 · Redemption dua tahap · M
- **RFC:** §7.5
- **Kerjakan:** jalur `EXTERNAL` di endpoint redeem: transaksi 1 (`PENDING` + debit),
panggil provider di luar transaksi, transaksi 2 (`COMPLETED` + atribusi, atau `FAILED` +
`REWARD_REDEEM_REFUND`).
- **Selesai jika:** test dengan provider palsu untuk ketiga hasil; timeout meninggalkan
`PENDING` dengan Point terpotong dan response "sedang diproses".
- **Bergantung pada:** EG-505, EG-801
### EG-803 · `VoucherRedemptionRecoveryJob` · M
- **RFC:** §7.5 langkah 4, §12
- **Kerjakan:** ambil `PENDING` lewat N menit, `Lookup` ke provider, selesaikan transaksi 2.
Setelah batas percobaan → refund + `FAILED`.
- **Selesai jika:** tidak ada redemption yang tertinggal `PENDING` melewati batas
percobaan; job berjalan dua kali tidak merefund dua kali.
- **Bergantung pada:** EG-802
---
## Fase 9 — Budget Controller & Analytics
### EG-901 · Rekomendasi multiplier · M
- **RFC:** §10 (rekomendasi), PRD §30–§31
- **Kerjakan:** hitung multiplier yang membuat forecast = budget, dibatasi step maksimum dan
min/max multiplier. `GET /budgets/:id/recommendation`.
- **Bergantung pada:** EG-601
### EG-902 · Terima rekomendasi · S
- **Kerjakan:** admin menerima rekomendasi → reward config versi baru dibuat dari config
aktif dengan angka yang disesuaikan, tercatat di audit dengan `source = budget_controller`.
- **Bergantung pada:** EG-202, EG-901
### EG-903 · Analytics · M
- **RFC:** PRD §36
- **Kerjakan:** endpoint dashboard game (plays, completed, rata-rata skor & reward, Coin
issued, entry cost dibayar, Coin di-refund) dan economy (Coin generated / spent /
expired / outstanding, Point redeemed), per org dan rentang tanggal.
- **Bergantung pada:** EG-303, EG-505
---
## Fase 10 — Spin & Bersih-bersih
### EG-1001 · Spin sebagai game EnakGame · S
- **RFC:** §14
- **Kerjakan:** seeder / langkah admin untuk game spin baru dengan config `PROBABILITY` dan
reward Coin. Client Phaser di luar repo ini.
- **Selesai jika:** spin bisa dimainkan lewat `/customer/enakgame/sessions` end-to-end.
- **Bergantung pada:** EG-402
### EG-1002 · Hapus alur game lama · M
- **RFC:** §14, §15
- **Kerjakan:** hapus route `POST /customer/spin`, `GET /customer/games`,
`GET /customer/ferris-wheel`, admin `/marketing/games`, `/marketing/game-prizes`, dan
`/marketing/rewards`, beserta handler, service, processor, dan test-nya
(`GamePlayProcessor`, `SpinGameService`, dan seterusnya). **Tabel `games`,
`game_prizes`, `game_plays` tetap ada** untuk riwayat ledger.
- **Selesai jika:** build dan test lulus; aplikasi customer sudah tidak memanggil endpoint
lama (konfirmasi tim aplikasi).
- **Bergantung pada:** EG-1001. Tabel `rewards` tidak punya data produksi (dikonfirmasi
2026-10-07), jadi tidak ada yang dipindah ke `vouchers`.
- **Catatan:** endpoint customer lama boleh dimatikan **lebih awal**, kapan pun, untuk
menutup temuan RFC §15 nomor 1 dan 2.
### EG-1003 · Bersihkan kolom lama `games` · S
- **Kerjakan:** drop `games.is_active` dan berhenti membaca `metadata.coin_cost`.
- **Bergantung pada:** EG-1002
---
## Yang Bisa Dimulai Sekarang
Bisa dikerjakan paralel tanpa menunggu apa pun:
- **EG-001** (matikan bayar dengan EnakPoint) → **EG-002**
- **EG-101** → **EG-102** → **EG-105** (skema & repository)
- **EG-103** (tipe ledger)
- **EG-104** (audit), **EG-106** (setting limit)
- **EG-301** (Reward Engine, kode murni tanpa database)
- **EG-501**, **EG-503** (fondasi voucher)
Jalur kritis: **EG-105 → EG-204 → EG-303 → EG-402**. Hampir semua fase setelahnya
menunggu complete game selesai.