- ✅
- ✅ - ✅ - ✅
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
# Setrip — Admin Audit & Investigation Roadmap
|
||||
|
||||
Admin perlu **mencari** lintas entity (booking/payment/refund/user/trip) dan **export** untuk compliance + investigasi dispute.
|
||||
|
||||
> **Skenario nyata:** auditor bertanya "tunjukkan semua refund yang di-approve admin X di bulan Juni 2026 dengan total lebih dari Rp 5 juta". Saat ini admin harus query DB manual atau ambil screenshot satu-satu. Tidak ada cara cari berdasarkan kombinasi reviewer + tanggal + nominal.
|
||||
|
||||
---
|
||||
|
||||
## Baseline
|
||||
|
||||
- ✅ Data audit sudah ada di schema: `Refund.reviewedBy/reviewedAt/adminNote`, `Payout.processedBy/processedAt/adminNote`, `OrganizerVerification.reviewedBy/reviewedAt/rejectionReason`.
|
||||
- ✅ Existing list pages (`/admin/refunds`, `/admin/payouts`, `/admin/verifications`) sudah grouping by status tab.
|
||||
- ❌ Tidak ada filter date range / reviewer / amount / reason.
|
||||
- ❌ Tidak ada kolom "reviewer email" di list — harus klik detail.
|
||||
- ❌ Tidak ada global search (cari berdasarkan email user, order id, trip id).
|
||||
- ❌ Tidak ada CSV export.
|
||||
- ❌ Tidak ada audit log untuk action admin di entity lain (User suspension, Trip force-cancel, Verification reopen).
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Filter & Search Enhancements ⏳
|
||||
|
||||
Sebelum bikin audit log baru, perbaiki dulu kemampuan cari & filter di list yang sudah ada.
|
||||
|
||||
**Keputusan asumsi:**
|
||||
- Pakai `searchParams` di Next.js — tidak perlu state client (server-render fast + shareable URL).
|
||||
- Default date range: 30 hari terakhir, supaya page tidak load semua history.
|
||||
- Reviewer dropdown sumber dari `ADMIN_EMAILS` env (sudah ada).
|
||||
|
||||
| # | Item | Status | File |
|
||||
|---|---|---|---|
|
||||
| 1.1 | Filter date range (`from`, `to`) di `/admin/refunds` | ⏳ | [app/admin/refunds/page.tsx](app/admin/refunds/page.tsx) |
|
||||
| 1.2 | Filter `reviewedBy` (admin email dropdown) di `/admin/refunds` | ⏳ | [app/admin/refunds/page.tsx](app/admin/refunds/page.tsx) |
|
||||
| 1.3 | Filter `reason` di `/admin/refunds` (lihat juga [ADMIN_PAYMENT_OPS_ROADMAP.md](ADMIN_PAYMENT_OPS_ROADMAP.md)) | ⏳ | [app/admin/refunds/page.tsx](app/admin/refunds/page.tsx) |
|
||||
| 1.4 | Filter date range + `processedBy` di `/admin/payouts` | ⏳ | [app/admin/payouts/page.tsx](app/admin/payouts/page.tsx) |
|
||||
| 1.5 | Filter date range + `reviewedBy` di `/admin/verifications` | ⏳ | [app/admin/verifications/page.tsx](app/admin/verifications/page.tsx) |
|
||||
| 1.6 | Tampilkan kolom "reviewer email" + "reviewed at" di tabel/list (semua admin pages) | ⏳ | semua `app/admin/*/page.tsx` |
|
||||
| 1.7 | Repo helper: tambah optional filter params di `refundRepo.listByStatus`, `payoutRepo.listByStatus`, `organizerRepo.listByStatus` | ⏳ | `server/repositories/*.ts` |
|
||||
|
||||
**Tindakan manual:** tidak ada.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Global Search ⏳
|
||||
|
||||
Satu search box yang resolve ke entity detail page paling relevan.
|
||||
|
||||
**Keputusan asumsi:**
|
||||
- Input string user, prefix-based dispatch:
|
||||
- Email format (`@`) → user search → redirect ke `/admin/users/[id]`
|
||||
- Mulai `midtrans-` / `manual-` → payment lookup by `externalOrderId` → `/admin/bookings/[bookingId]`
|
||||
- Mulai `cm` (cuid pattern) + length 25 → coba lookup berurutan: trip → booking → user
|
||||
- Else: full-text search di trip title/destination
|
||||
- Pakai server action atau route handler `/api/admin/search` — return list hasil + jenis entity.
|
||||
- UI: searchbar di admin layout (top-right) yang dropdown hasil.
|
||||
|
||||
| # | Item | Status | File |
|
||||
|---|---|---|---|
|
||||
| 2.1 | `adminSearchService.resolve(query)` — dispatch ke repo lookup yang tepat | ⏳ | `server/services/admin-search.service.ts` |
|
||||
| 2.2 | Route handler `/api/admin/search?q=...` (GET, guard isAdmin) | ⏳ | `app/api/admin/search/route.ts` |
|
||||
| 2.3 | Component `AdminSearchBar` di admin layout — debounced, dropdown hasil | ⏳ | `features/admin/components/admin-search-bar.tsx` |
|
||||
| 2.4 | Page `/admin/search?q=...` untuk full results kalau dropdown limit terlampaui | ⏳ | `app/admin/search/page.tsx` |
|
||||
|
||||
**Tindakan manual:** tidak ada.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — CSV Export ⏳
|
||||
|
||||
Export untuk laporan keuangan & compliance.
|
||||
|
||||
**Keputusan asumsi:**
|
||||
- Stream CSV via route handler — jangan load semua ke memory.
|
||||
- Pakai filter yang sama dengan list page — admin pakai URL filter lalu klik "Export".
|
||||
- Header CSV: human-readable bahasa Indonesia (mis. "Tanggal Approve", "Email Peserta", "Nominal Refund").
|
||||
- Tidak ada Excel/xlsx — CSV cukup, mudah dibuka di Sheets/Excel.
|
||||
|
||||
| # | Item | Status | File |
|
||||
|---|---|---|---|
|
||||
| 3.1 | Helper `lib/csv.ts` — `streamCsv(headers, rows)` return Response | ⏳ | `lib/csv.ts` |
|
||||
| 3.2 | Route `/api/admin/export/refunds` — pakai filter dari query string | ⏳ | `app/api/admin/export/refunds/route.ts` |
|
||||
| 3.3 | Route `/api/admin/export/payouts` | ⏳ | `app/api/admin/export/payouts/route.ts` |
|
||||
| 3.4 | Route `/api/admin/export/verifications` (tanpa NIK / KTP — hanya metadata) | ⏳ | `app/api/admin/export/verifications/route.ts` |
|
||||
| 3.5 | Tombol "Export CSV" di tiap admin list page | ⏳ | semua `app/admin/*/page.tsx` |
|
||||
|
||||
**Tindakan manual:**
|
||||
1. Test export di staging — pastikan tidak leak data sensitif (NIK harus tetap encrypted/excluded).
|
||||
2. Update kebijakan privasi: data export hanya untuk internal compliance.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Generic Admin Audit Log ⏳
|
||||
|
||||
Tabel `AdminActionLog` untuk action di entity yang belum punya audit field (User suspend, Trip force-cancel, Verification reopen, dst).
|
||||
|
||||
**Keputusan asumsi:**
|
||||
- Single tabel polymorphic: `AdminActionLog { adminId, action, entityType, entityId, payload Json?, createdAt }`.
|
||||
- Append-only, never update/delete.
|
||||
- Service helper `auditLog.record(...)` dipanggil eksplisit di setiap action admin (tidak via Prisma middleware — terlalu magic).
|
||||
- View page `/admin/audit-log` dengan filter `adminId`, `entityType`, `action`, date range.
|
||||
|
||||
| # | Item | Status | File |
|
||||
|---|---|---|---|
|
||||
| 4.1 | Model `AdminActionLog` + migration | ⏳ | [prisma/schema.prisma](prisma/schema.prisma) |
|
||||
| 4.2 | Helper `auditLog.record({ adminId, action, entityType, entityId, payload? })` | ⏳ | `server/services/audit-log.service.ts` |
|
||||
| 4.3 | Wire `auditLog.record` di semua admin server action existing (refund approve/reject/mark, payout markPaid, verification approve/reject) | ⏳ | `features/*/actions.ts` |
|
||||
| 4.4 | Page `/admin/audit-log` dengan filter + pagination | ⏳ | `app/admin/audit-log/page.tsx` |
|
||||
|
||||
**Tindakan manual:** tidak ada.
|
||||
Reference in New Issue
Block a user