# SUPERAPP ACADEMY V20 — NOTIFIKASI ADMIN, TIKET, AKSES MANUAL, TRYOUT PRIVAT, DAN KONTAK

Paket ini **SOURCE LENGKAP CodeIgniter 4.7.6** dengan fitur V19.1 tetap dipertahankan. Ada pula ZIP patch khusus untuk V19.1 agar dapat di-*overwrite* tanpa menghapus file yang tidak berubah.

**Kompatibilitas:** PHP 8.2+ dengan ekstensi `intl`, `mbstring`, `mysqli`, `curl`, `openssl`, MySQL/MariaDB, hosting cPanel (tanpa terminal). **Jangan timpa `.env`**, jangan hapus `writable/uploads/academy`, `public/uploads/catalog`, `public/uploads/branding`, dan jangan import ulang database awal yang sudah berisi data.

## 1. Upgrade dari V19.1 yang telah berjalan

1. Backup seluruh file website, database dari phpMyAdmin, `.env`, folder gambar dan bukti transfer.
2. **Sebelum menimpa source**, pilih database aplikasi yang benar di phpMyAdmin dan jalankan `database/UPGRADE_V19_1_KE_V20.sql` **sekali** pada database tersebut. Berkas ini hanya **menambah tabel**, tidak menghapus transaksi lama.
3. Buka cPanel File Manager, arahkan ke root CodeIgniter (`app/`, `public/`, `system/`, `writable/`), unggah `SuperApp_Academy_V20_PATCH_TIMPA_V19_1.zip`, pilih **Extract → Overwrite**. Jangan taruh ZIP di dalam `public/` bila dokumen root menunjuk folder `public`.
4. Pastikan folder `public/uploads/branding` bisa ditulis PHP. Biasanya permission folder `755` dengan pemilik yang benar; sesuaikan hosting jika perlu.
5. Buka `Panel Admin → Pengaturan → Bersihkan Cache Publik`, lalu tekan `Ctrl+F5` untuk CSS/JS baru.
6. Tes halaman beranda, katalog, login Google, transaksi, notifikasi pembayaran, tiket, pemberian akses, dan pengaturan kontak.

**Penting:** Jika sebelumnya Anda **belum** menjalankan upgrade V19 atau V19.1 (misalnya log masih `catalog_profiles doesn't exist`), lengkapi migrasi V19 terlebih dahulu sesuai `MULAI_DISINI_V19_1.md`, baru jalankan `UPGRADE_V19_1_KE_V20.sql`. Jangan menjalankan `IMPORT_SEKALI.sql` pada database aktif.

## 2. Instalasi baru dari database kosong

Pakai ZIP FULL SOURCE. Upload kode dan isi `.env` sesuai hosting; arahkan document root ke `public/`. Pada database **kosong** saja, impor `database/IMPORT_SEKALI.sql` satu kali. Itu sudah termasuk semua tabel V20. Jangan menjalankan SQL upgrade setelah impor awal yang lengkap.

## 3. Modul baru di V20

### Notifikasi pembayaran menunggu verifikasi

- Buka `/panel-admin/ringkasan`, lihat badge **🔔 Pembayaran** dan **🎫 Tiket** di kanan header. Badge membaca data database melalui endpoint `GET /panel-admin/notifikasi/status` setiap 25 detik saat tab aktif. Tidak tergantung keberhasilan WhatsApp.
- Pembayaran dihitung sebagai **menunggu verifikasi** hanya ketika `orders.status='waiting_verification'`, `payment_method='manual_transfer'`, dan ada `proof_path`.
- Klik badge → `/panel-admin/pesanan?status=waiting_verification`, periksa **mutasi rekening** dan bukti, lalu konfirmasi manual. Jangan mengkonfirmasi hanya berdasarkan foto.
- Status `paid` dan callback gateway sah **tidak boleh dikonfirmasi ulang**. Notifikasi peserta dibuat setelah admin memverifikasi pembayaran manual.

### Tiket bantuan peserta

- Peserta: `/akun/tiket` → buat tiket (kategori pembayaran/akses/teknis/lainnya), lihat percakapan, balas, dan tandai selesai.
- Admin: `/panel-admin/tiket` → lihat antrean, buka tiket, balas, tutup. Balasan admin memunculkan notifikasi peserta di `/akun/notifikasi`.
- Semua query peserta dibatasi menggunakan `session platform_user_id`; seluruh perubahan melalui POST+CSRF. Peserta dibatasi maksimum 5 tiket baru per jam per akun.
- Untuk bukti transfer, gunakan fitur upload bukti pembayaran lama pada pesanan. **Tiket V20 menggunakan pesan teks; tidak mengunggah lampiran gambar tersendiri.**

### Perpanjangan akses dan membership

- Admin: `/panel-admin/peserta` → **Kelola Akses** pada peserta → pilih Tryout/Bank/Materi/Kelas, masukkan tanggal akhir atau kosong untuk tidak terbatas, isi alasan, simpan.
- Akses baru masuk ke `admin_access_overrides`, perubahan dicatat dalam `admin_access_audit`, dan notifikasi tampil pada akun peserta. Transaksi pembelian tetap utuh.
- Halaman yang sama dapat memperpanjang `user_memberships.expires_at` menggunakan tanggal baru yang **lebih lama** dari tanggal sebelumnya. Perpanjangan membership tidak otomatis memperpanjang konten dengan batas kalender terpisah; bila diperlukan, atur akses konten satu per satu.
- Ujian `tryout_akbar` atau ujian berkuota tetap wajib mempunyai **registrasi terkonfirmasi**; admin tidak bisa memberikan kursi yang sudah penuh. Jika calon peserta mempunyai reservasi pembayaran aktif yang belum terverifikasi, selesaikan pembayaran itu dahulu.
- Pencabutan hanya menghentikan **override manual**, bukan menghapus hak dari pembelian sah. Wajib mencantumkan alasan.

### Tryout privat untuk peserta tertentu atau pembeli paket

- Admin: `/panel-admin/program-khusus` → pilih Tryout → **Privat** → opsional pilih paket pembelian yang relevan → Simpan.
- Tryout privat tidak tampil pada daftar ujian publik dan tidak dijual langsung sebagai produk satuan. Akses server di halaman detail/CAT tetap dicek, bukan hanya menu disembunyikan.
- Admin dapat mengundang pengguna langsung melalui **ID peserta**, atau mencentang pembeli dari paket yang sudah lunas. Admin menekan **Tambahkan Peserta & Kirim Notifikasi**. Maksimum 50 peserta per batch.
- Pembeli paket yang dipilih boleh membuka rincian tryout, tetapi **tidak otomatis mendapatkan kursi atau akses CAT hanya karena ia membayar**; admin memilih siapa saja yang boleh ikut. Ini menghindari pendaftaran yang melampaui kuota.
- Jika kursi penuh, peserta tersebut gagal ditambahkan dan alasannya ditampilkan kepada admin; yang berhasil tidak dibatalkan.

### Identitas aplikasi, kontak, logo dan WA melayang

- Admin: `/panel-admin/identitas` → ubah **nama aplikasi, slogan, tentang, email, telepon, alamat, jam layanan, WhatsApp bantuan**.
- Upload logo **JPG/PNG/WebP maksimal 2 MB**, minimum 100×100 piksel, saran desain **400×400 px**.
- Logo disimpan dengan nama acak di `public/uploads/branding/`, bukan di database biner. URL gambar berasal dari nama file yang terverifikasi.
- Tombol WA melayang muncul di halaman publik jika nomor bantuan yang valid diisi; mengarah ke `wa.me`, bukan API notifikasi Fonnte. Kontak juga tersedia di `/kontak`.
- Token WA/payment **tetap disimpan di `.env`**, bukan tabel `app_settings`.

## 4. Daftar file baru yang penting

```
app/Services/AdminInboxService.php
app/Services/AdminAccessService.php
app/Services/RestrictedExamService.php
app/Services/SupportTicketService.php
app/Services/SiteIdentityService.php
app/Services/UserNotificationService.php
app/Controllers/Admin/{InboxController,AccessController,SupportController,RestrictedProgramController,IdentityController}.php
app/Controllers/Web/{SupportController,NotificationController,ContactController}.php
app/Views/admin/{support_tickets,support_ticket_detail,member_access,restricted_programs,identity}.php
app/Views/web/{tickets,ticket_detail,notifications,contact}.php
public/assets/v20/{site.css,inbox.js}
database/UPGRADE_V19_1_KE_V20.sql
```

Kode ditulis non-minify dan diberi komentar Bahasa Indonesia pada bagian logika baru. Server produksi tidak membutuhkan Composer atau SSH apabila source `system/` dari ZIP tersedia dan ekstensi PHP aktif.

## 5. Uji coba wajib sebelum bertransaksi produksi

1. Login sebagai admin dan peserta uji di dua browser terpisah.
2. Peserta checkout transfer manual, upload bukti. Admin dapat melihat badge notifikasi; konfirmasi transaksi hanya setelah cek mutasi rekening. Pastikan badge turun setelah status paid.
3. Peserta buat tiket, admin balas, peserta mendapatkan notifikasi dan bisa membukanya. Akun pengguna lain tidak bisa membaca tiket tersebut.
4. Admin berikan akses program, lihat muncul di `/akun/belajar`; coba masa aktif dan pencabutan override. Tes kasus tanggal kedaluwarsa.
5. Admin atur tryout privat, periksa tidak tampak di katalog umum; pengguna undangan bisa melihat dan mengikuti jika telah terdaftar, pengguna lain mendapat 404.
6. Tes kuota WAR penuh dan reservasi lama supaya tidak memberikan kursi ganda.
7. Admin ganti nama aplikasi, upload logo dan isi WA. Cek tautan WA, halaman kontak, serta tampilan di HP.
8. Jalankan pemeriksaan kode `php tests/smoke_v20.php` jika memiliki CLI PHP dengan ekstensi yang sesuai. Jika tidak, cek error log dan uji browser di subdomain staging.

**Batas pengujian paket:** Syntax PHP dan JavaScript serta smoke check statis dapat diperiksa secara offline. Pengujian nyata MySQL, OAuth, Fonnte, gateway callback, upload pada filesystem hosting, dan concurrent registration harus diuji terpisah sebelum mengganti sistem produksi. Jangan membuat klaim instalasi sudah aman di hosting hanya karena lint statis lolos.
