# Task Breakdown: EnakPoint & EnakCoin **Sumber:** [PRD EnakPoint & EnakCoin](prd-point-coin.md) **Tanggal:** 2026-09-29 > **Catatan 2026-10-07:** fase 3 (Pembayaran EnakPoint) dihapus sesuai > [`enakgame-prd.md`](./enakgame-prd.md) §3.2: EnakPoint hanya bisa ditukar ke voucher, > bukan alat bayar. Payment method EnakPoint, kode bayar, bayar di kasir & app, refund > EnakPoint, dan EnakPoint di laporan (PC-303 – PC-308) sudah dihapus dari backend > (migrasi `000102`). PIN customer (PC-301) dan pengaturan loyalitas organisasi (PC-302) > tetap dipakai. Sisa dokumen ini dibiarkan sebagai riwayat. Setiap task menyebut bagian PRD 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 `000089`), `entities` → `repository` → `processor` → `service` → `handler` / `validator` → `router`, dan wiring di `internal/app/app.go`. --- ## Ringkasan | Fase | Task | Terblokir oleh catatan PRD | |---|---|---| | 1. Fondasi | PC-101 – PC-109 | – | | 2. Earning | PC-201 – PC-205 | – | | 3. Pembayaran EnakPoint | PC-301 – PC-308 | N2 (keuangan), N3 (legal) sebelum **rilis** | | 4. Pergerakan saldo | PC-401 – PC-404 | N3 (legal) sebelum **rilis** | | 5. Kedaluwarsa | PC-501 – PC-504 | N4 (model kedaluwarsa) sebelum **dikerjakan** | | 6. Bersih-bersih | PC-601 – PC-602 | – | Catatan N2 dan N3 hanya memblokir **rilis** fase 3–4, bukan pengerjaannya. N4 memblokir pengerjaan fase 5, karena rumus kedaluwarsanya belum ditentukan. ``` PC-101 ─┬─ PC-103 ── PC-104 ─┬─ PC-105 ── PC-106 │ ├─ PC-107 │ ├─ PC-108 │ ├─ PC-203 ── PC-204 ── PC-205 │ ├─ PC-305 ── PC-306 / PC-307 │ ├─ PC-401 / PC-402 / PC-403 / PC-404 │ └─ PC-502 ── PC-503 PC-102 ── PC-109 ─┬─ PC-201 ── PC-202 ── PC-203 └─ PC-302 PC-301 ── PC-304 ── PC-305 PC-303 ── PC-305 ``` --- ## Fase 1 — Fondasi Semua fase lain bergantung pada fase ini. **PC-104 (wallet engine) adalah inti.** Tidak ada kode lain yang boleh mengubah saldo tanpa lewat engine ini. ### PC-101 · Migrasi tabel wallet, ledger, dan lot · M - **PRD:** §8 (`customer_wallets`, `wallet_transactions`, `wallet_lots`, `wallet_lot_allocations`), K5, K9 - **Kerjakan:** migrasi `000090_create_wallet_tables` (up & down) dengan semua `CHECK` constraint dan index persis seperti di §8. - **Selesai jika:** - Up dan down berjalan bersih di database kosong dan di salinan staging. - Uji constraint langsung di SQL, masing-masing harus ditolak: `PAYMENT` bercurrency `COIN`; `TRANSFER_IN` tanpa `counterparty_customer_id`; `EARN_REVERSAL` tanpa `reverses_transaction_id`; `ADJUSTMENT` tanpa `reason`; `EXPIRE` dengan `reference_type` selain `LOT`; saldo negatif; lot dengan `remaining_amount > original_amount`. - **Bergantung pada:** – ### PC-102 · Migrasi pengaturan organisasi dan riwayat perubahan setting · S - **PRD:** F2, `loyalty_setting_changes` di §8 - **Kerjakan:** tabel `organization_settings` (key–value, pola sama dengan `outlet_settings`, `UNIQUE(organization_id, key)`) dan `loyalty_setting_changes`. Saat ini belum ada tempat menyimpan setting per organisasi. - **Selesai jika:** up/down bersih. - **Bergantung pada:** – ### PC-103 · Entities dan repository wallet · M - **PRD:** §7.1–§7.4 - **Kerjakan:** - Entities untuk empat tabel PC-101. - Repository yang **selalu** memakai `DBFromContext` (berbeda dari repository gamification yang sekarang memakai `r.db` langsung). - `LockWallet(ctx, customerID)` dengan `SELECT … FOR UPDATE`. Membuat baris wallet jika belum ada (`INSERT … ON CONFLICT DO NOTHING`, lalu lock). - `LockWallets(ctx, a, b)` yang selalu mengunci berurutan berdasarkan `customer_id`. - Update saldo bersyarat yang **mengembalikan error jika 0 baris ter-update**. - Query lot aktif sesuai urutan K9: `expires_at NULLS LAST, created_at`. - **Selesai jika:** test repository menunjukkan update bersyarat gagal saat saldo kurang, dan dua goroutine yang mengunci wallet yang sama berjalan bergantian. - **Bergantung pada:** PC-101 ### PC-104 · Wallet engine (credit / debit / lot / idempotensi) · L - **PRD:** K5, K6, K9, §7, §8.1 - **Kerjakan:** processor `WalletProcessor` sebagai **satu-satunya pintu** perubahan saldo: - `Credit(ctx, CreditInput)`: menulis ledger, membuat lot (dengan `expires_at` dan `origin_lot_id` dari input), dan menambah saldo. - `Debit(ctx, DebitInput)`: mengambil dari lot sesuai urutan K9 (atau dari lot tertentu lebih dulu, untuk reversal di F10), menulis alokasi dan ledger, lalu mengurangi saldo. Mengembalikan daftar alokasi, supaya transfer/exchange bisa membuat lot penerima dengan `expires_at` yang sama. - `DebitUpTo`: mengambil sebanyak yang tersedia, untuk reversal dengan shortfall (F10, Q3). - Idempotensi: jika `idempotency_key` sudah ada, kembalikan hasil pertama tanpa mutasi baru. - Validasi di level kode untuk aturan §8.1 (tipe ↔ currency ↔ referensi wajib), sebagai lapisan kedua di atas constraint database. - Semua method mewajibkan caller sudah berada di dalam transaksi dan wallet sudah dikunci. - **Selesai jika:** unit test mencakup debit yang melewati beberapa lot, urutan lot dengan dan tanpa `expires_at`, debit melebihi saldo (ditolak), `DebitUpTo` dengan shortfall, idempotency key ganda, dan saldo = `SUM(ledger)` = `SUM(lot.remaining)` setelah setiap skenario. - **Bergantung pada:** PC-103 ### PC-105 · Migrasi data Point & Token lama · M - **PRD:** §10, Q6 - **Kerjakan:** migrasi data (atau command satu kali) yang: - Membuat wallet dari `customer_points` dan penjumlahan semua jenis `customer_tokens`. - Menulis ledger `MIGRATION` dan lot tanpa `expires_at` per customer per currency, dengan rincian per jenis token di `metadata`. - Mengganti `TOKENS` menjadi `COINS` di `campaigns.type` dan `campaign_rules.reward_type`. - **Selesai jika:** jumlah total Point dan Token sebelum = jumlah total saldo wallet sesudah (diuji pada salinan data staging), dan migrasi aman dijalankan dua kali (tidak menggandakan). - **Bergantung pada:** PC-104 ### PC-106 · Endpoint saldo & riwayat customer · M - **PRD:** F6, §9 (customer app) - **Kerjakan:** `GET /customer/wallet` dan `GET /customer/wallet/transactions` (pagination, filter currency/tipe/tanggal). Ganti isi `/customer/points` dan `/customer/tokens` menjadi alias yang membaca `customer_wallets`. Hapus pemakaian `customer_points_repository` untuk saldo. - **Selesai jika:** response menampilkan asal/tujuan setiap mutasi sesuai §8.1, dan aplikasi lama yang memanggil `/points` / `/tokens` tetap mendapat angka yang benar. - **Bergantung pada:** PC-105 ### PC-107 · Wallet customer dan adjustment di dashboard · M - **PRD:** F7 - **Kerjakan:** `GET /marketing/customers/:id/wallet` (saldo, lot aktif, mutasi) dan `POST /marketing/customers/:id/wallet/adjust` (alasan wajib, tidak boleh membuat saldo negatif, tercatat `created_by_user`). - **Selesai jika:** adjustment muncul di riwayat customer dengan nama admin dan alasan. Adjustment yang melebihi saldo ditolak. - **Bergantung pada:** PC-104 ### PC-108 · Job rekonsiliasi · S - **PRD:** §7.5 - **Kerjakan:** query dan job terjadwal yang memeriksa semua invarian §7.5 dan melaporkan selisih ke log dan notifikasi admin. - **Selesai jika:** job menemukan selisih yang sengaja dibuat di data test, dan diam saat data konsisten. - **Bergantung pada:** PC-104 ### PC-109 · Pembaca setting loyalitas dan riwayat perubahan · M - **PRD:** F1, F2 (bagian mekanisme, bukan UI), §8 `loyalty_setting_changes` - **Kerjakan:** service yang membaca setting outlet dan organisasi dengan nilai default PRD bila key belum diisi, mengembalikan struct bertipe (bukan string mentah), dan menulis `loyalty_setting_changes` setiap kali setting loyalitas diubah. - **Selesai jika:** outlet tanpa setting mendapat semua default PRD, dan setiap perubahan tercatat nilai lama, nilai baru, dan pelakunya. - **Bergantung pada:** PC-102 --- ## Fase 2 — Earning ### PC-201 · API pengaturan loyalitas outlet · M - **PRD:** F1 - **Kerjakan:** `GET/PUT /outlets/:id/loyalty-settings` dengan validasi F1. Response menyertakan **persentase cashback efektif** (`earn_value × nilai EnakPoint / earn_per_amount`). - **Selesai jika:** validasi menolak nilai di luar batas, dan hanya Admin/Manager yang bisa mengubah. - **Bergantung pada:** PC-109 ### PC-202 · Kalkulator earning · S - **PRD:** F1 (rumus), Q1, Q10 - **Kerjakan:** fungsi murni `CalculateEarning(order, pointPaidAmount, settings)` yang mengembalikan Point, Coin, basis, dan snapshot setting. - **Selesai jika:** unit test mencakup contoh di PRD (Rp 87.500 → 875 Point, 3 Coin; dengan Rp 20.000 dibayar EnakPoint → 675 Point, 2 Coin), basis di bawah minimum, `max_per_order`, setting nonaktif, dan pajak tidak ikut dihitung. - **Bergantung pada:** PC-201 ### PC-203 · Earning saat order lunas · L - **PRD:** F3 - **Kerjakan:** - **Satukan titik "order menjadi lunas".** Saat ini `payment_status = completed` di-set di empat tempat: `OrderProcessorImpl.UpdateOrder`, `OrderProcessorImpl.updateOrderStatus`, dan dua tempat di `split_bill_processor.go`. Buat satu hook `onOrderPaid(orderID)` yang dipanggil dari keempatnya. - Hook menjalankan earning **setelah** transaksi pembayaran commit, sehingga kegagalan earning tidak menggagalkan pembayaran. - Idempotency key `earn:{order_id}:{currency}`. - Lewati order tanpa customer, customer default, atau customer nonaktif. - **Jaring pengaman:** job yang mencari order lunas beberapa hari terakhir yang belum punya `EARN` (dan seharusnya punya), lalu menjalankan ulang earning. - **Selesai jika:** test untuk pembayaran penuh, split bill (lunas di pembayaran terakhir), self-order, dan pemanggilan ganda (hasilnya tetap satu earning). Earning yang sengaja digagalkan tidak menggagalkan pembayaran dan terambil oleh job. - **Bergantung pada:** PC-104, PC-202 ### PC-204 · Reversal earning saat void / refund · M - **PRD:** F10, Q3 - **Kerjakan:** panggil reversal dari `VoidOrder`, `RefundOrder`, dan `RefundPayment`. Ambil pertama dari lot `EARN` order tersebut, lalu lot lain. Gunakan `DebitUpTo` dan catat `shortfall`. Refund tidak pernah diblokir. - **Selesai jika:** test untuk void penuh, refund sebagian (proporsional, akumulasi tidak melebihi earning), dan saldo yang sudah terpakai (shortfall tercatat, saldo 0). - **Bergantung pada:** PC-203 ### PC-205 · Tampilkan earning di order dan struk · S - **PRD:** F3 - **Kerjakan:** tambah `points_earned` dan `coins_earned` ke response order dan data struk. - **Selesai jika:** nilai sama dengan baris `EARN` di ledger, dan bernilai 0 untuk order tanpa earning. - **Bergantung pada:** PC-203 --- ## Fase 3 — Pembayaran EnakPoint > Boleh dikerjakan sekarang. **Tidak boleh dirilis** sebelum catatan N2 (keuangan) dan > N3 (legal) ditutup. ### PC-301 · PIN customer · L - **PRD:** K8, F11, Q16, Q17 - **Kerjakan:** - Migrasi kolom PIN di `customers` dan tabel `customer_security_events`. - Purpose OTP baru `pin_setup` dan `pin_reset` di `OtpProcessor`. - Endpoint `/customer/pin/*` (status, OTP, buat, ganti, reset). - Hash bcrypt, tolak PIN lemah (digit sama, berurutan, tanggal lahir). - Kunci 30 menit setelah 5 kali salah, dengan penghitung di database. - Tahan transfer keluar 24 jam setelah reset. - `VerifyPin(ctx, customerID, pin)` untuk dipakai task lain, dengan error `PIN_NOT_SET`, `PIN_INVALID`, `PIN_LOCKED`, `TRANSFER_BLOCKED`. - Hapus PIN oleh admin: `DELETE /marketing/customers/:id/pin`, dan `GET /marketing/customers/:id/security-events`. - PIN tidak pernah muncul di log (termasuk log request body). - **Selesai jika:** test untuk semua aturan di atas, termasuk 5 kali salah lalu PIN benar tetap ditolak selama terkunci, dan reset OTP membuka kunci. - **Bergantung pada:** – ### PC-302 · API pengaturan loyalitas organisasi · S - **PRD:** F2 - **Kerjakan:** `GET/PUT /marketing/loyalty-settings` untuk nilai EnakPoint, kurs exchange, dan batas transfer, serta `GET /marketing/loyalty-settings/history`. Sebelum menyimpan perubahan nilai EnakPoint atau kurs, response preview menampilkan total saldo beredar dan nilai rupiahnya sebelum/sesudah. - **Selesai jika:** perubahan tercatat di riwayat, dan transaksi lama tetap memakai nilai yang dibekukan. - **Bergantung pada:** PC-109 ### PC-303 · Payment method EnakPoint · M - **PRD:** F9 (payment method), §8 (perubahan `payments`), §10.5 - **Kerjakan:** - Tambah tipe `point` ke `PaymentMethodType` dan validator (`oneof=cash card digital_wallet point`). - Migrasi kolom `points_used` dan `point_value` di `payments`. - Buat payment method sistem "EnakPoint" untuk setiap organisasi yang sudah ada, dan otomatis untuk organisasi baru. - Tolak hapus / ubah tipe method sistem. - Sembunyikan method ini di kasir jika outlet tidak mengaktifkan `accept_payment`. - **Selesai jika:** setiap organisasi punya tepat satu method EnakPoint yang tidak bisa dihapus. - **Bergantung pada:** – ### PC-304 · Kode bayar sekali pakai · M - **PRD:** F9 (persetujuan customer), K8 - **Kerjakan:** `POST /customer/wallet/payment-code` (butuh PIN) menghasilkan kode 6 digit dan QR yang berlaku 2 menit, disimpan di Redis dengan TTL dan diambil sekali-pakai (`GETDEL`). Kode terikat ke `customer_id`. - **Selesai jika:** kode kedaluwarsa, kode yang sudah dipakai, dan kode milik customer lain semuanya ditolak. - **Bergantung pada:** PC-301 ### PC-305 · Bayar EnakPoint di kasir · L - **PRD:** F9 (perhitungan, pencatatan), K7 - **Kerjakan:** - `GET /orders/:id/point-payment/preview`. - Cabang method `point` di `CreatePayment`: validasi kode bayar, cocokkan customer order, hitung `maks_point`, lalu lakukan langkah 1–6 F9 dalam **satu transaksi** bersama pembuatan baris `payments`. - Idempotency `payment:{payment_id}`. - Tolak jika `nominal_rupiah > remaining_amount` (tidak ada kembalian). - **Selesai jika:** test untuk pembayaran penuh, sebagian + tunai (split), batas `max_payment_percent`, order walk-in (ditolak), kode salah (ditolak), dan dua pembayaran bersamaan untuk customer yang sama (saldo tidak terpakai dua kali). Earning (PC-203) menghitung basis tanpa bagian EnakPoint. - **Bergantung pada:** PC-104, PC-303, PC-304 ### PC-306 · Bayar EnakPoint dari app / self-order · M - **PRD:** F9 - **Kerjakan:** `POST /customer/orders/:id/pay-with-points` (butuh PIN, hanya untuk order milik customer itu sendiri), memakai logika yang sama dengan PC-305. - **Selesai jika:** customer tidak bisa membayar order milik customer lain. - **Bergantung pada:** PC-305 ### PC-307 · Refund pembayaran EnakPoint · M - **PRD:** F9 (void/refund), K7, Q13 - **Kerjakan:** `PAYMENT_REFUND` di jalur void dan refund memakai `point_value` yang dibekukan, dengan pembulatan ke bawah. Kembalikan ke lot dengan `expires_at` asal (aturan perpanjangan 7 hari mengikuti N4, jadi untuk sekarang cukup pakai tanggal asal). **Tolak** permintaan refund bagian EnakPoint lewat method lain. - **Selesai jika:** test untuk void penuh, refund sebagian, perubahan nilai EnakPoint di antara bayar dan refund (jumlah EnakPoint yang kembali tetap sama), dan percobaan refund tunai atas pembayaran EnakPoint (ditolak). - **Bergantung pada:** PC-305 ### PC-308 · EnakPoint di laporan · S - **PRD:** F9 (laporan) - **Kerjakan:** laporan per payment method menampilkan EnakPoint terpisah dan tidak menghitungnya sebagai kas masuk. Cek laporan analytics yang menjumlahkan pembayaran. - **Selesai jika:** total kas masuk di laporan tidak berubah saat sebagian order dibayar EnakPoint. Perlakuan akuntansi lanjutan menunggu N2. - **Bergantung pada:** PC-305 --- ## Fase 4 — Pergerakan Saldo > Boleh dikerjakan sekarang. Transfer (PC-402) **tidak boleh dirilis** sebelum N3 > (legal) ditutup. ### PC-401 · Exchange EnakCoin → EnakPoint · M - **PRD:** F4, K3 - **Kerjakan:** `GET /customer/wallet/exchange/preview` dan `POST /customer/wallet/exchange` (butuh PIN, `Idempotency-Key`). Jumlah harus kelipatan `coin_amount`. Kurs dibekukan di metadata. Lot EnakPoint kedaluwarsa pada `min(lot EnakCoin asal, sekarang + masa berlaku EnakPoint)`. - **Selesai jika:** test untuk kurs default 1:1, kurs 10:3, jumlah bukan kelipatan (ditolak), dan `expires_at` lot hasil exchange tidak pernah lebih lama dari lot asal. - **Bergantung pada:** PC-104, PC-301, PC-302 ### PC-402 · Transfer antar customer · L - **PRD:** F5, Q4, Q16 - **Kerjakan:** `GET /customer/wallet/transfer/recipient` (nama disamarkan) dan `POST /customer/wallet/transfer` (butuh PIN, `Idempotency-Key`). Kunci dua wallet berurutan, cek batas organisasi (per transaksi dan harian), cek `transfer_blocked_until`. Lot penerima mewarisi `expires_at` dan `origin_lot_id` dari lot pengirim. Kirim notifikasi push ke penerima. - **Selesai jika:** test untuk transfer ke diri sendiri / customer lain organisasi / customer default (semua ditolak), batas harian, transfer dua arah bersamaan (tidak deadlock), dan `expires_at` di penerima sama persis dengan pengirim. - **Bergantung pada:** PC-104, PC-301, PC-302 ### PC-403 · Semua game memakai EnakCoin · M - **PRD:** F8, K1 - **Kerjakan:** - `GamePlayProcessor.PlayGame` memotong EnakCoin lewat wallet engine sesuai `games.metadata.coin_cost`. - Seluruh permainan dalam satu transaksi: ubah repository game, game play, dan game prize ke `DBFromContext`. Gagal mengurangi stok hadiah membatalkan permainan (saat ini hanya di-`Printf`), dan rollback manual `AddTokens` dihapus. - Rename `game_plays.token_used` menjadi `coins_used`. - **Selesai jika:** test untuk saldo kurang (ditolak, tidak ada `game_plays` yang tercatat), stok hadiah gagal (semua dibatalkan), dan `GAME_SPEND` menunjuk `game_plays.id`. - **Bergantung pada:** PC-104 ### PC-404 · Telusuri per butir di dashboard · S - **PRD:** F7, §8.1 (per butir) - **Kerjakan:** `GET /marketing/wallet-transactions/:id/trace` yang mengembalikan alokasi lot dan rantai `origin_lot_id` sampai ke lot pertama. - **Selesai jika:** contoh di §8 (A transfer 120 ke B, B bayar 30) menelusuri ke order #ORD-1 milik A. - **Bergantung pada:** PC-402 --- ## Fase 5 — Kedaluwarsa > **Jangan dikerjakan sebelum catatan N4 (model kedaluwarsa) diputuskan.** Struktur lot > sudah ada sejak PC-101, jadi yang tersisa hanya rumus, job, dan tampilan. ### PC-501 · Setting kedaluwarsa · M - **PRD:** F12 (pengaturan), N4 - **Kerjakan:** key setting sesuai model yang dipilih di N4, dengan validasi dan preview "saldo yang didapat hari ini kedaluwarsa pada …". - **Bergantung pada:** PC-302, **N4** ### PC-502 · Hitung `expires_at` saat lot dibuat · M - **PRD:** F12 (tabel kedaluwarsa per lot), N4 - **Kerjakan:** satu fungsi `ComputeExpiry(currency, receivedAt, settings)` yang dipakai oleh `EARN` dan `ADJUSTMENT`, serta aturan aktivasi pertama untuk lot lama (termasuk lot `MIGRATION`). - **Bergantung pada:** PC-104, PC-501 ### PC-503 · Job kedaluwarsa · M - **PRD:** F12 (proses kedaluwarsa) - **Kerjakan:** job per jam yang memproses lot lewat tanggal dengan `EXPIRE` (idempotency `expire:{lot_id}`) di bawah lock wallet. - **Jangan menyalin pola `OmsetMilestoneScheduler`.** Scheduler itu menyimpan state di memori, dan menurut komentarnya sendiri bisa mengirim ulang notifikasi setelah restart. Job ini harus aman dijalankan di banyak instance sekaligus: pilih lot dengan `FOR UPDATE SKIP LOCKED`, dan andalkan idempotency key. - **Selesai jika:** dua instance yang berjalan bersamaan tidak menghanguskan lot yang sama dua kali, dan tidak ada lot yang lewat tanggal lebih dari 1 jam tanpa diproses. - **Bergantung pada:** PC-502 ### PC-504 · Pengingat dan tampilan saldo yang akan kedaluwarsa · M - **PRD:** F6 (`/wallet/expiring`), F12 (pengingat) - **Kerjakan:** endpoint `GET /customer/wallet/expiring`, field saldo kedaluwarsa terdekat di `/wallet`, dan notifikasi pengingat yang dikelompokkan per tanggal. Jadwal pengingat mengikuti N4. - **Bergantung pada:** PC-503 --- ## Fase 6 — Bersih-bersih ### PC-601 · Hapus tabel dan kode lama · S - **PRD:** §10.7 - **Kerjakan:** satu rilis setelah PC-105 berjalan di production, drop `customer_points` dan `customer_tokens`. Hapus `CustomerPointsProcessor` dan `CustomerTokensProcessor` beserta stub `not implemented`, rute yang di-comment di `router.go`, dan alias `/customer/points` / `/customer/tokens` setelah aplikasi diperbarui. - **Bergantung pada:** PC-106, PC-403, dan konfirmasi bahwa aplikasi sudah tidak memanggil endpoint lama. ### PC-602 · Dokumentasi integrasi · S - **Kerjakan:** panduan integrasi untuk tim aplikasi dan POS (seperti `integration-weight-based-products.md`): endpoint, kode error PIN, alur kode bayar, dan contoh request/response. - **Bergantung pada:** PC-305, PC-402 --- ## Yang Bisa Dimulai Sekarang Bisa dikerjakan paralel tanpa menunggu apa pun: - **PC-101** (migrasi wallet) → langsung lanjut **PC-103**, lalu **PC-104** - **PC-102** (setting organisasi) → **PC-109** - **PC-301** (PIN customer) - **PC-303** (payment method EnakPoint) PC-104 adalah jalur kritis: hampir semua task lain menunggunya.