# Cadangan Rebuild Architecture: Sistem myLesen **Versi:** 1.0 — 18 Mac 2026 **Berdasarkan:** Audit `docs/audit-mylesen.md` **Stack:** Laravel 11 + Bootstrap 5 + jQuery + MySQL > **Nota:** Dokumen ini adalah blueprint cadangan. Ia tidak memerlukan implementasi sepenuhnya sekaligus. Setiap bahagian boleh dilaksanakan secara berperingkat. --- ## Bahagian 1 — Ringkasan Hala Tuju Rebuild ### 1.1 Masalah Utama Codebase Lama **Masalah 1: Tiada satu sumber kebenaran untuk status** Nilai `status_progress` seperti `'menunggu bayaran proses'` ditulis sebagai string literals dalam 4 controllers berbeza. Typo dalam satu tempat boleh menyebabkan permohonan tidak kelihatan dalam mana-mana queue. **Masalah 2: Business logic tersebar dalam fat controllers** `Admin\PenjajaController::store()` melakukan banyak perkara sekaligus tanpa DB transaction. Jika upload dokumen fail selepas lesen dicipta, data menjadi tidak konsisten. **Masalah 3: EPBT integration logic diduplikasi** Kod untuk query `EpbtBpBil` dan `EpbtEcasResit` muncul dalam 4 tempat berbeza. Satu perubahan pada EPBT memerlukan update di 4 lokasi. **Masalah 4: Fail berbahaya dan kod tidak siap ditinggalkan** `public/test_curl.php` adalah risiko keselamatan kritikal. Kod PBTPay, Laporan Prestasi, dan `LesenPenjajaHistory` adalah stubs yang tidak siap tetapi masih dalam codebase. ### 1.2 Prinsip Rebuild 1. **Bukan rewrite penuh** — aliran bisnes sedia ada sudah betul. Yang perlu diperbaiki adalah cara kod disusun. 2. **Laravel convention** — gunakan apa yang sudah ada dalam Laravel (FormRequest, Policy, Enum, Queue) sebelum bina sendiri. 3. **Tidak over-engineer** — tiada Repository pattern, tiada CQRS, tiada microservices. Service class yang mudah sudah cukup. 4. **Satu sumber kebenaran** — status, roles, dan constants dalam Enum dan Config, bukan string literals. 5. **Team kecil** — keputusan seni bina perlu boleh difahami oleh developer Laravel biasa dalam masa 30 minit membaca kod. ### 1.3 Pendekatan Keseluruhan Rebuild dilakukan dalam **4 fasa berurutan**, bukan sekaligus: | Fasa | Nama | Tempoh | Fokus | |---|---|---|---| | 0 | Cleanup | 1 minggu | Padam fail berbahaya dan backup lama | | 1 | Refactor Core | 3 minggu | Enum, Services, FormRequest, fix N+1 | | 2 | Lengkapkan Integrasi | 3 minggu | Aktifkan integration, putuskan PBTPay, bina laporan | | 3 | API + UI | 4 minggu | Chrome extension API, UI improvements, UAT | ### 1.4 Keputusan Penting Yang Perlu Diambil Awal 1. Pakai PBTPay atau bayaran kekal melalui EPBT eCAS sahaja? 2. Aktifkan `janaBil()` auto-generate atau kekal manual? 3. Apa status terminal yang jelas untuk permohonan yang selesai? 4. Clarify peranan `no_akaun_lesen` vs `no_fail_lesen` ### 1.5 Trade-Off: Reuse vs Rewrite | | Reuse | Rewrite | |---|---|---| | **Kelebihan** | Cepat, business rules terpelihara | Lebih bersih, technical debt hilang | | **Kelemahan** | Masalah lama mungkin kekal | Berisiko hilang business rules | | **Cadangan** | Reuse models dan blade views aktif. Rewrite controllers kepada pattern baru. Padam semua kod stub/backup. | | --- ## Bahagian 2 — Cadangan Seni Bina Baru ### 2.1 Struktur Folder Baru ``` app/ │ ├── Enums/ │ ├── StatusPermohonan.php ← gantikan string hardcoded │ ├── StatusMesyuarat.php │ └── RolePengguna.php │ ├── Services/ │ ├── ApplicationService.php ← create, submit, status transitions │ ├── EpbtService.php ← semua EPBT API + DB lookups │ ├── BilService.php ← bil proses + bil lesen │ └── MesyuaratService.php ← meeting + Lampiran B │ ├── Http/ │ ├── Controllers/ │ │ ├── Pemohon/ ← portal awam │ │ │ ├── DashboardController.php │ │ │ └── PermohonanController.php │ │ ├── Admin/ │ │ │ ├── DashboardController.php │ │ │ ├── PT/ │ │ │ │ ├── PermohonanController.php (senarai + view) │ │ │ │ ├── BilController.php (wang proses) │ │ │ │ └── MesyuaratController.php │ │ │ ├── IK/ │ │ │ │ └── PemeriksaanController.php │ │ │ ├── Pegawai/ │ │ │ │ ├── CadanganController.php (PP) │ │ │ │ └── UlasanController.php (Pengarah) │ │ │ ├── Pengurusan/ │ │ │ │ ├── PenggunaController.php │ │ │ │ ├── PengumumanController.php │ │ │ │ └── CarouselController.php │ │ │ └── Laporan/ │ │ │ └── LaporanController.php │ │ └── Api/ │ │ ├── AuthController.php │ │ └── PermohonanController.php │ ├── Requests/ │ │ ├── Permohonan/ │ │ │ ├── StorePermohonanRequest.php │ │ │ └── UpdatePermohonanRequest.php │ │ ├── Bil/ │ │ │ └── StoreBilRequest.php │ │ └── Mesyuarat/ │ │ └── StoreMesyuaratRequest.php │ └── Middleware/ │ ├── AdminMiddleware.php │ └── KakitanganMiddleware.php │ ├── Models/ ← kekal, dengan penambahan ├── Policies/ ← implement sepenuhnya └── Providers/ └── AppServiceProvider.php ← buang Gate definitions, guna Policy routes/ ├── web.php ← dikemas, dikelompok mengikut role ├── api.php ← BARU — untuk Chrome extension └── auth.php resources/views/ ├── layouts/ │ └── app.blade.php ← satu layout sahaja ├── components/ ├── pemohon/ ← portal awam ├── admin/ │ ├── dashboard.blade.php │ ├── pt/ │ ├── ik/ │ ├── pegawai/ │ └── pengurusan/ └── print/ ← semua cetakan ├── lampiran-b.blade.php ├── borang-ik.blade.php └── cadangan-pegawai.blade.php ``` ### 2.2 Enum yang Dicadangkan ```php // app/Enums/StatusPermohonan.php enum StatusPermohonan: string { case DRAF = 'draf'; case BARU = 'baru'; case TUNGGU_BAYAR_PROSES = 'menunggu bayaran proses'; case SEMAK_BAYAR_PROSES = 'semakan bayaran proses'; case LAWATAN_TAPAK = 'lawatan tapak'; case ULASAN_PEGAWAI = 'ulasan pegawai'; case ULASAN_PENGARAH = 'ulasan pengarah'; case SOKONG_KE_MESYUARAT = 'sokong dibawa ke mesyuarat'; case TUNGGU_KEPUTUSAN = 'menunggu keputusan mesyuarat'; case KEPUTUSAN_DITERIMA = 'keputusan diperolehi'; case LESEN_DIKELUARKAN = 'lesen dikeluarkan'; ← BARU case DITOLAK = 'ditolak'; ← BARU public function label(): string { /* Malay labels */ } public function warna(): string { /* badge colour */ } } // app/Enums/StatusMesyuarat.php enum StatusMesyuarat: string { case DILULUSKAN = 'diluluskan'; case DITOLAK = 'ditolak'; case DITANGGUHKAN = 'ditangguhkan'; } // app/Enums/RolePengguna.php enum RolePengguna: string { case SUPER = 'super'; case PEMBANTU_TADBIR = 'pembantu tadbir'; case PP_KESIHATAN = 'penolong pegawai kesihatan'; case PP_TADBIR = 'penolong pegawai tadbir'; case PEGAWAI_TADBIR = 'pegawai tadbir'; case PENGARAH = 'pengarah'; } ``` ### 2.3 Service Layer Empat service class yang dicadangkan. Setiap satu mempunyai satu tanggungjawab yang jelas: **`EpbtService`** — satu tempat untuk semua EPBT interactions: ``` getBilDetails(string $noAkaun): ?array getPaymentReceipt(string $noAkaun): ?object registerBilPelbagai(array $data): array getLicenseAccount(string $noAkaun): ?object ``` **`BilService`** — wrapper untuk bil proses dan bil lesen: ``` simpanBilProses(LesenPenjaja $lesen, string $noAkaun): BilPelbagai semakBayaranBilProses(BilPelbagai $bil): bool semakBayaranLesen(LesenPenjaja $lesen): bool ``` **`ApplicationService`** — status transitions dan business rules: ``` hantarPermohonan(LesenPenjaja $lesen): void hantarKeIk(LesenPenjaja $lesen): BorangUlasanIk hantarKeUlasanPegawai(BorangUlasanIk $borang): void ``` **`MesyuaratService`** — mesyuarat logic: ``` tambahKeMesyuarat(LesenPenjaja $lesen, MesyuaratPelesenan $mesyuarat): void simpanKeputusan(MesyuaratPelesenan $mesyuarat, array $keputusans): void exportLampiranB(MesyuaratPelesenan $mesyuarat): StreamedResponse ``` ### 2.4 Model Inventory | Model | Tindakan | Penambahan | |---|---|---| | `LesenPenjaja` | Kekal, refactor | Tambah `SoftDeletes`, `StatusPermohonan` cast, indexes pada `status_progress` + `kawasan_id` | | `User` | Kekal | Buang boolean flags yang tidak digunakan (`is_admin_lesen_*`) | | `BorangUlasanIk` | Kekal | Tiada perubahan ketara | | `UlasanPegawai` | Kekal | Namanya mengelirukan tapi OK untuk dikekalkan | | `MesyuaratPelesenan` | Kekal | Tiada perubahan ketara | | `BilPelbagai` | Kekal | Tambah index pada `lesen_penjaja_id` | | `BilPelbagaiApi` | Kekal | Gunakan sebagai integration log | | `EpbtBpBil` hingga `EpbtBandar` (11 models) | Kekal | Read-only, sudah betul | | `LesenPenjajaHistory` | **Implement** | Tambah fillable, gunakan sebagai audit trail | | `PbtpayBil/Cart/CartItem/Transaksi` | **Keputusan** | Implement atau padam | | `GrabResitLesen` | Refactor | Jadikan generic `BatchSemakan` atau integrate ke `BilService` | | `UserPolicies` | Kekal | Tapi gunakan sepenuhnya dalam Policy classes | ### 2.5 Naming Convention | Jenis | Convention | Contoh | |---|---|---| | Controller | PascalCase + suffix | `PermohonanController`, `BilController` | | Model | PascalCase domain term | `LesenPenjaja`, `BorangUlasanIk` | | Route name | dot-notation, bahasa Melayu | `permohonan.index`, `bil.proses.simpan` | | Blade view | kebab-case | `borang-permohonan.blade.php` | | Service | PascalCase + Service | `EpbtService`, `BilService` | | Enum | PascalCase | `StatusPermohonan`, `RolePengguna` | | Migration | snake_case, descriptive | `add_soft_deletes_to_lesen_penjajas` | ### 2.6 Approach untuk Validation, Policies, Helpers **Validation:** Setiap action yang menerima input pengguna MESTI ada FormRequest class sendiri. Tidak ada inline `$request->validate([...])` dalam controllers. **Policies:** Buang semua Gate definitions dari `AppServiceProvider`. Guna Policy sepenuhnya. Satu Policy per model utama. Implement semua method yang sekarang masih empty stub. **Helpers:** Gunakan static methods dalam model atau Service, bukan global helper functions. Contoh: `LesenPenjaja::statusLabel($status)` atau `EpbtService::formatCustomer($lesen)`. **Config:** Buat `config/epbt.php` untuk semua tetapan EPBT (host, client_key, dept, cost_center). Jangan hardcode dalam controller. ### 2.7 Pendekatan File Upload / Document Handling - Kekal guna `Storage::disk('local')` dengan streaming melalui controller — ini adalah cara yang betul dan selamat - Standardkan path: `storage/app/private/penjaja/{lesen_id}/{jenis}/{filename}` - Untuk foto IK: `storage/app/private/penjaja/{lesen_id}/ik/{borang_id}/{foto}` - Buat satu helper method dalam model atau service untuk generate path — elak duplicate path logic ### 2.8 Audit Trail Implement `LesenPenjajaHistory` sebagai audit trail yang betul: - Setiap perubahan `status_progress` simpan satu rekod: `lesen_penjaja_id`, `dari_status`, `ke_status`, `tindakan`, `catatan`, `user_id`, `created_at` - Guna Model Observer untuk auto-log perubahan status - Paparan timeline dalam tab "Sejarah" pada halaman detail permohonan ### 2.9 Pendekatan Print / PDF - Kekal guna PHPWord untuk output `.docx` (Lampiran B, borang IK, cadangan pegawai) — ini sudah sesuai untuk keperluan PBT - Extract semua print logic ke `MesyuaratService::exportLampiranB()` dan `PrintService::cetakBorangIk()` - Jika PDF diperlukan pada masa hadapan, pertimbangkan DomPDF — tetapi ini adalah enhancement kemudian, bukan keperluan awal - View untuk print berasingan dalam `resources/views/print/` --- ## Bahagian 3 — Status Flow dan Workflow Sistem ### 3.1 Diagram Aliran Status Lengkap ``` STATUS SIAPA TRIGGER TINDAKAN ────── ───────────── ──────── (null/draf) ←─ Pemohon/Admin ←─ simpan borang, belum hantar ↓ hantar_permohonan() ↓ [PEMOHON atau ADMIN] BARU ─ queue PT ─ ↓ simpanWangProses() [PT] ↓ masukkan No. Bil EPBT MENUNGGU ─ queue PT ─ BAYARAN PROSES ↓ Auto: bila EpbtEcasResit ada resit ↓ [triggered semasa page load atau scheduled job] SEMAKAN ─ queue PT ─ BAYARAN PROSES ↓ hantarPPK() [PT] ↓ buat BorangUlasanIk kosong LAWATAN TAPAK ─ queue IK ─ ↓ simpanUlasan() [IK — lengkapkan borang] ULASAN PEGAWAI ─ queue PP/Pegawai Tadbir ─ ↓ simpan_cadangan() [PP] ULASAN PENGARAH ─ queue Pengarah ─ ↓ simpan_ulasan_cadangan() [Pengarah] │ ├─ jika pengarah_ulasan = 'tidak disokong' (tangguh) │ → kembali ke LAWATAN TAPAK (borang IK baru) │ └─ jika pengarah_ulasan = 'disokong' SOKONG KE ─ PT masukkan dalam mesyuarat ─ MESYUARAT ↓ sahkanSenarai() [PT — kunci senarai] MENUNGGU ─ mesyuarat berlangsung ─ KEPUTUSAN ↓ simpanKeputusanMesyuarat() [PT] KEPUTUSAN ─ status_mesyuarat set ─ DITERIMA │ ├─ 'diluluskan' → PT daftar No. Lesen → LESEN DIKELUARKAN ← BARU ├─ 'ditolak' → DITOLAK ← BARU (status terminal) └─ 'ditangguhkan' → kekal KEPUTUSAN DITERIMA, tunggu mesyuarat berikut LESEN DIKELUARKAN ─ status terminal positif ─ │ └─ Bil Lesen diterima dari EPBT └─ Bayaran semak via EpbtEcasResit ``` ### 3.2 Role yang Bertindak pada Setiap Peringkat | Status | Queue Siapa | Tindakan Yang Boleh | |---|---|---| | (draf) | Pemohon | Edit, Hantar, Padam | | BARU | PT | View detail, Masukkan No. Bil EPBT | | TUNGGU BAYAR PROSES | PT | Semak bayaran (manual/auto) | | SEMAK BAYAR PROSES | PT | Hantar ke IK | | LAWATAN TAPAK | IK | Isi Borang Ulasan, Simpan lokasi, Upload foto | | ULASAN PEGAWAI | PP/Pegawai Tadbir | Tulis cadangan | | ULASAN PENGARAH | Pengarah | Buat ulasan, Sokong atau Tangguh | | SOKONG KE MESYUARAT | PT | Masukkan dalam mesyuarat, Set kadar | | TUNGGU KEPUTUSAN | PT | Simpan keputusan mesyuarat | | KEPUTUSAN DITERIMA | PT | Daftar no. lesen (jika lulus) | | LESEN DIKELUARKAN | PT | Semak bayaran lesen | ### 3.3 History / Status Log Setiap perubahan status perlu dilog dalam `lesen_penjaja_histories`: ``` id lesen_penjaja_id dari_status ← StatusPermohonan enum value ke_status ← StatusPermohonan enum value tindakan ← contoh: 'PT hantar ke IK', 'IK simpan ulasan' catatan ← optional, untuk note tambahan user_id ← siapa buat tindakan created_at ``` Ini membolehkan: - Paparan timeline permohonan - Debug bila status bermasalah - Audit trail untuk pemeriksaan --- ## Bahagian 4 — Role dan Permission Matrix ### 4.1 Clarifikasi Role Berdasarkan kod semasa, terdapat kemungkinan `pp tadbir` dan `pegawai tadbir` adalah dua nama untuk fungsi yang sama. Cadangan: **satukan kepada satu role** iaitu `pegawai tadbir` untuk semua PP/Pegawai. | Role | Fungsi | |---|---| | `super` | Admin penuh sistem | | `pembantu tadbir` | PT — proses permohonan, bil, mesyuarat | | `pp kesihatan` | IK — lawatan tapak | | `pegawai tadbir` | PP — cadangan + Pengarah — ulasan | | `pengarah` | Pengarah — ulasan cadangan, keputusan | | *(tiada role)* | Pemohon awam | ### 4.2 Permission Matrix | Tindakan | super | pembantu tadbir | pp kesihatan | pegawai tadbir | pengarah | pemohon | |---|---|---|---|---|---|---| | Daftar akaun | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Login | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | **Permohonan** | | | | | | | | Hantar permohonan sendiri | ✓ | ✓ | — | — | — | ✓ | | Hantar atas nama (admin) | ✓ | ✓ | — | — | — | — | | Edit permohonan sendiri (draf) | ✓ | — | — | — | — | ✓ | | Padam permohonan sendiri (draf) | ✓ | — | — | — | — | ✓ | | View permohonan sendiri | ✓ | — | — | — | — | ✓ | | **PT Workflow** | | | | | | | | View queue baru/proses/bukafail | ✓ | ✓ | — | — | — | — | | Masuk no bil EPBT | ✓ | ✓ | — | — | — | — | | Hantar ke IK | ✓ | ✓ | — | — | — | — | | Urus mesyuarat | ✓ | ✓ | — | — | — | — | | Cetak Lampiran B | ✓ | ✓ | — | — | — | — | | Daftar no lesen | ✓ | ✓ | — | — | — | — | | Semak bayaran lesen | ✓ | ✓ | — | — | — | — | | **IK Workflow** | | | | | | | | View queue lawatan tapak | ✓ | ✓ | ✓ | — | — | — | | Isi borang ulasan IK | ✓ | — | ✓ | — | — | — | | Upload foto tapak | ✓ | — | ✓ | — | — | — | | Cetak borang IK | ✓ | ✓ | ✓ | ✓ | ✓ | — | | **PP/Pengarah Workflow** | | | | | | | | View queue cadangan PP | ✓ | ✓ | — | ✓ | — | — | | Tulis cadangan PP | ✓ | — | — | ✓ | — | — | | View queue ulasan Pengarah | ✓ | ✓ | — | — | ✓ | — | | Buat ulasan Pengarah | ✓ | — | — | — | ✓ | — | | **Pengurusan** | | | | | | | | Urus pengguna & role | ✓ | — | — | — | — | — | | Urus pengumuman | ✓ | — | — | — | — | — | | Urus carousel | ✓ | — | — | — | — | — | | Urus master data | ✓ | — | — | — | — | — | | **API** | | | | | | | | Akses API Chrome Extension | ✓ | ✓ | ✓ | ✓ | ✓ | — | | Kemaskini status via API | ✓ | ✓ | — | — | — | — | ### 4.3 Implementasi Cadangan Guna **Policy** sepenuhnya, buang Gate definitions dari `AppServiceProvider`. ```php // Satu Policy per model utama: LesenPenjajaPolicy ← view, create, update, hantar, padam BorangUlasanIkPolicy ← view, create, update UlasanPegawaiPolicy ← view, create, update MesyuaratPolicy ← view, create, update, cetak ``` Untuk check role dalam Policy, guna helper: ```php $user->hasRole(RolePengguna::PEMBANTU_TADBIR) ``` --- ## Bahagian 5 — Dashboard dan UI/UX Plan ### 5.1 Dashboard Per Role **Pemohon:** - Senarai semua permohonan saya + badge status berwarna - Butang "Mohon Baru" yang jelas - Notis jika ada dokumen yang perlu dikemaskini - Pengumuman aktif **PT (Pembantu Tadbir):** - 4 count cards: Baru, Tunggu Bayar, Bayar Disahkan, Daftar Lesen - Senarai "Urgent" — permohonan yang melebihi tempoh tertentu (aging indicator) - Senarai mesyuarat akan datang - Quick action: "Carian Permohonan" by No. NRIC atau No. Fail **IK (Inspektor Kesihatan):** - Count card: Dalam Queue Lawatan Tapak - Senarai mengikut kawasan (kawasan yang paling banyak menunggu dahulu) - Aging indicator: berapa hari sejak dihantar ke IK - Quick action: klik terus ke borang ulasan **PP/Pegawai Tadbir:** - Count card: Menunggu Cadangan - Senarai mengikut tarikh dihantar - Aging indicator **Pengarah:** - Count card: Menunggu Ulasan Pengarah - Paparan ringkasan cadangan PP untuk setiap item - Butang Sokong / Tangguh terus dari dashboard - Senarai mesyuarat akan datang **Super/Admin:** - KPI dashboard: permohonan diterima vs diproses vs diluluskan (mengikut tahun) - Kadar kepatuhan KPI (berapa peratus dalam masa 14 hari) - Graf trend bulanan - Breakdown mengikut kawasan - Senarai semua permohonan (carian + filter lengkap) ### 5.2 Prinsip UI/UX Keseluruhan 1. **Satu tema sahaja** — pilih satu daripada 4 tema yang ada, buang yang lain 2. **Bootstrap 5** — lebih mudah untuk team kecil maintain berbanding Tailwind + custom theming 3. **Status badge konsisten** — setiap status ada warna yang sama di semua halaman 4. **Mobile-friendly** — PT mungkin guna tablet semasa kerja 5. **Tindakan yang jelas** — setiap halaman queue ada satu "primary action" yang obvious ### 5.3 Landing Page dan Login - Route `/` perlu redirect ke halaman login atau halaman awam yang betul - Halaman login: ringkas, ada logo MBIP, ada link "Mohon Lesen Baharu" - Selepas login, redirect mengikut role: - Pemohon → `/dashboard` (senarai permohonan saya) - Staf/Admin → `/dashmin` (admin dashboard) ### 5.4 Borang Panjang - Borang permohonan panjang (ada maklumat peribadi, syarikat, lokasi, dokumen) - Cadangan: kekalkan borang satu halaman tapi ada tab atau section headers yang jelas - Simpan `draf` secara automatik — jangan paksa pemohon isi semua sekali gus - Tunjukkan progress indicator (langkah 1/4, 2/4, dll) ### 5.5 Table, Filter, Search - Semua senarai queue perlu ada: - Carian by nama, NRIC, No. Fail - Filter by kawasan - Filter by tarikh (dari-hingga) - Sorting by tarikh mohon (default: terbaru dahulu) - Gunakan DataTables (sudah ada dalam `public/plugins/datatables/`) — cukup untuk keperluan semasa ### 5.6 Bahagian UI Yang Perlu Dibina Semula | Bahagian | Isu Sekarang | Cadangan | |---|---|---| | Landing page | Route `/` → test view | Bina halaman awam yang proper | | `papar_permohonan.blade.php` | Satu fail besar dengan `$show` tabs | Kekal tapi bersihkan, pisahkan partial yang lebih kecil | | Dashboard pemohon | Hanya senarai, tiada context | Tambah status badge + next action hint | | Dashboard admin | Muat semua data pada load | Pindah semak bayaran ke background job | --- ## Bahagian 6 — Rekabentuk Integration, Bil, Bayaran, No Resit, No Lesen ### 6.1 Dua Mode Integrasi EPBT **Mode 1: Manual (Semasa Aktif)** ``` PT masuk No. Bil EPBT secara manual → sistem query second_mysql: EpbtBpBil.where('noakaun', $noAkaun) → simpan butiran dalam BilPelbagai → sistem query EpbtEcasResit untuk resit bayaran → jika ada resit, kemaskini status ke 'semakan bayaran proses' ``` **Mode 2: Auto-Generate (Kod Siap, Dimatikan)** ``` Sistem hantar HTTP POST ke epbt.mbip.gov.my/appsepbtkompaun_ws/... → EPBT create bil, return accountNo → simpan dalam BilPelbagaiApi (sebagai log) → continue ke semak bayaran seperti Mode 1 ``` **Cadangan:** Aktifkan Mode 2 secara berperingkat: 1. Test di persekitaran UAT dulu 2. Sahkan `client_key = 'MPJBT'` dengan MBIP IT 3. Pindah config ke `config/epbt.php` (jangan hardcode) 4. Aktifkan dalam production setelah berjaya UAT ### 6.2 Rekabentuk BilService ```php class BilService { public function simpanBilProses(LesenPenjaja $lesen, string $noAkaun): BilPelbagai { // Buat/semak BilPelbagai // Tarik butiran dari EpbtBpBil (second_mysql) // Semak EpbtEcasResit untuk bayaran // Kemaskini status_progress jika bayar // Log dalam LesenPenjajaHistory } public function semakSemuaBayaranPending(): void { // Untuk scheduled job // Query semua lesen dengan status 'menunggu bayaran proses' // Semak setiap satu, kemaskini jika sudah bayar } } ``` ### 6.3 Aliran No. Fail dan No. Lesen **Clarifikasi yang diperlukan (lihat Bahagian 10):** | Field | Tujuan Dicadangkan | Siapa Isi | Bila | |---|---|---|---| | `no_fail_lesen` | Nombor fail fizikal MBIP (cth: MBIP/PS/2025/001) | PT | Selepas bayaran proses disahkan | | `no_akaun_lesen` | Nombor akaun lesen dalam sistem EPBT | PT | Selepas keputusan mesyuarat diluluskan | | `kod_lesen` | Kod jenis lesen dalam sistem EPBT | PT | Sama masa dengan no_akaun_lesen | | `dt_lesen_dikeluarkan` | Tarikh lesen dikeluarkan | Auto (dari EPBT ElsnAkaun) | Bila no_akaun_lesen disimpan | ### 6.4 Cadangan Scheduled Jobs ```php // routes/console.php atau app/Console/Kernel.php // Setiap 15 minit: semak bayaran yang pending Schedule::job(new SemakBayaranPendingJob)->everyFifteenMinutes(); // Setiap hari tengah malam: semak hutang lesen Schedule::job(new SemakBayaranLesenJob)->dailyAt('23:00'); ``` Ini menggantikan semak manual yang sekarang ada dalam dashboard dan perlu admin klik butang. ### 6.5 Keputusan PBTPay **Pilihan A: Implement PBTPay** - Kira-kira 2-3 minggu kerja - Perlu: `PbtpayBil` model siap, `PbtpayController::checkout()`, callback handler, `PbtpayTransaksi` untuk tracking - Bila pemohon bayar, status kemaskini automatik **Pilihan B: Padam Semua PBTPay Stubs** - Bayaran kekal melalui EPBT eCAS (sistem sedia ada) - Kurang 20 fail untuk dikekalkan dalam codebase - Lebih mudah maintain **Cadangan: Pilihan B dahulu.** Jika PBTPay perlu kemudian, lebih senang bina dari awal dengan codebase yang bersih berbanding extend stubs yang ada. ### 6.6 Fail-Safe dan Duplicate Prevention - Gunakan `firstOrCreate` (sudah ada dalam `simpanWangProses`) — kekalkan pattern ini - Untuk scheduled job semak bayaran: semak `updated_at` terakhir, jangan proses rekod yang baru dikemaskini dalam masa 5 minit (debounce) - Log setiap API call ke EPBT dalam `bil_pelbagai_apis` — sudah ada model untuk ini ### 6.7 Config yang Patut Boleh Dikonfigur Buat `config/epbt.php`: ```php return [ 'host' => env('EPBT_HOST', 'epbt.mbip.gov.my'), 'client_key' => env('EPBT_CLIENT_KEY', 'MPJBT'), 'dept' => env('EPBT_DEPT', 'Jabatan Pelesenan'), 'cr_code' => env('EPBT_CR_CODE', 'H72407'), 'dr_code' => env('EPBT_DR_CODE', 'H72407'), 'cost_center' => env('EPBT_COST_CENTER', '101005'), ]; ``` --- ## Bahagian 7 — Rekabentuk API untuk Chrome Extension ### 7.1 Tujuan API Chrome extension yang akan auto-fill sistem lain (cth: sistem utama EPBT atau sistem lain dalam rangkaian PBT) menggunakan data dari myLesen. Extension menggunakan data permohonan dari myLesen untuk mengisi borang dalam sistem lain tanpa menaip semula. ### 7.2 Authentication Strategy Guna **Laravel Sanctum** untuk API token: - Lebih ringan daripada Passport - Sesuai untuk internal tool - Token boleh dibuat per-user dalam dashboard profile - Token boleh di-revoke bila-bila masa Flow: 1. Staf login ke myLesen web UI biasa 2. Dalam Profile, staf boleh "Jana Token API" untuk Chrome extension 3. Token disimpan dalam extension 4. Setiap request API hantar `Authorization: Bearer {token}` header ### 7.3 Endpoints yang Dicadangkan ``` # Auth POST /api/v1/auth/login → JWT/token (jika guna token-based login) GET /api/v1/auth/me → maklumat pengguna semasa # Carian Permohonan GET /api/v1/permohonan → senarai (dengan filter: nokp, no_fail, status) GET /api/v1/permohonan/{id} → detail permohonan # Tindakan (restricted) PATCH /api/v1/permohonan/{id}/status → kemaskini status (bergantung kebenaran) # Referensi GET /api/v1/kawasan → senarai kawasan GET /api/v1/jenis-penjaja → senarai jenis penjaja ``` ### 7.4 Struktur Response JSON ```json { "data": { "id": 123, "no_fail": "MBIP/PS/2025/001", "no_akaun_lesen": "L001234", "status": "lesen_dikeluarkan", "status_label": "Lesen Dikeluarkan", "pemohon": { "nama": "Ahmad bin Ali", "nokp": "800101-14-1234", "notelefon": "0123456789", "alamat": "No. 1, Jalan Kenanga" }, "perniagaan": { "jenis": "Gerai Makanan", "jenis_jualan": "Nasi Campur", "lokasi": "Batu 9, Jalan Skudai, Johor Bahru" }, "lesen": { "no_akaun": "L001234", "kod_lesen": "PS001", "tarikh_dikeluarkan": "2025-03-15", "kadar_lesen": 150.00, "kadar_sampah": 20.00 }, "diakses_extension_pada": "2026-03-18T10:00:00Z" }, "meta": { "api_version": "v1", "akses_oleh": "ahmadstaff" } } ``` ### 7.5 Status yang Layak untuk Export Hanya permohonan dengan status tertentu yang patut boleh diakses oleh extension: - `lesen_dikeluarkan` — lesen sudah aktif - `keputusan_diterima` dengan `status_mesyuarat = 'diluluskan'` — diluluskan, proses pendaftaran Permohonan dalam proses (baru, tunggu bayar, dll) tidak perlu diakses oleh extension kerana data belum lengkap. ### 7.6 Audit Log Extension Setiap akses API Chrome extension log dalam `api_access_logs`: ``` id, user_id, permohonan_id, action, ip_address, user_agent, created_at ``` Ini membolehkan: - Track siapa akses data permohonan mana - Detect penggunaan tidak biasa (banyak request dari satu IP) - Audit trail untuk pemeriksaan ### 7.7 Security Controls - Rate limiting: 60 requests/minit per user (Laravel built-in throttle) - Token expiry: boleh set dalam Sanctum config (cth: 24 jam atau tiada expiry untuk internal tool) - Token revocation: dari dashboard Profile - IP whitelist: opsional — boleh hadkan akses API kepada rangkaian pejabat sahaja (via middleware) - API responses tidak include data sensitif yang tidak diperlukan (contoh: password_hash, remember_token) ### 7.8 Versioning Gunakan URL versioning: `/api/v1/...` Ini membolehkan API diubah pada masa hadapan tanpa breaking extension yang sedia ada. Jika ada breaking change, buat `/api/v2/...` dan maintain kedua-dua sementara extension dikemaskini. ### 7.9 Tandakan Rekod yang Telah Diakses Tambah field dalam `lesen_penjajas`: ``` extension_diakses_pada timestamp nullable ← bila pertama kali diakses extension extension_diakses_oleh foreign key (user) ← siapa akses dipindahkan_pada timestamp nullable ← bila rekod ditandakan 'telah dipindah' ``` Ini membolehkan admin tahu rekod mana yang sudah dipindahkan ke sistem lain, dan yang mana perlu dikaji semula. --- ## Bahagian 8 — Reuse vs Rewrite ### 8.1 Komponen yang Boleh Reuse (dengan sedikit kemas kini) | Komponen | Tindakan | |---|---| | `LesenPenjaja` model | Tambah SoftDeletes, StatusPermohonan cast | | `BorangUlasanIk` model | Kekal, tiada perubahan ketara | | `UlasanPegawai` model | Kekal, nama boleh kekal walaupun mengelirukan | | `MesyuaratPelesenan` model + pivot | Kekal | | `BilPelbagai`, `BilPelbagaiItem` | Kekal | | `BilPelbagaiApi` | Kekal sebagai integration log | | Semua 11 EPBT models | Kekal, read-only, betul | | `Kawasan`, `Taman`, `Jalan`, `Penempatan` | Kekal | | `JenisPenjaja`, `JenisJualan` | Kekal | | `Syarikat`, `UserSyarikat` | Kekal | | `UserPolicies` | Kekal, tapi implement Policy sepenuhnya | | `Pengumuman`, `GambarCarousel` | Kekal | | `User` model | Kekal, buang boolean flags tidak digunakan | | Layout `appmin.blade.php` | Kekal sebagai base (selepas bersihkan) | | Components blade (`text-input`, dll) | Kekal | | Views queue senarai | Kekal, tambah filter/search | | `papar_permohonan.blade.php` | Kekal, bersihkan, pisahkan partial | | `DataController` (AJAX cascading dropdown) | Kekal | ### 8.2 Komponen yang Guna Logic Sahaja tapi Lebih Baik Ditulis Semula | Komponen | Masalah | Cadangan | |---|---|---| | `Admin\PenjajaController::store()` | Fat method, tiada transaction | Pindah logic ke `ApplicationService`, buat FormRequest | | `PtPenjajaController` | EPBT logic inline | Extract ke `EpbtService`, `BilService` | | `DashboardController` | N+1 query, inline payment sync | Fix query, pindah sync ke scheduled job | | `PegawaiPenjajaController` | OK tapi tiada FormRequest | Tambah FormRequest | | `LaporanPrestasiController` | Syntax error, incomplete | Tulis semula dari awal | | Gates dalam `AppServiceProvider` | Tiada konsistensi | Buang, implement Policy sepenuhnya | | `ProfileController` | OK tapi ada kedua-dua public/admin path yang confusing | Bersihkan | ### 8.3 Komponen yang Patut Dibuang Terus | Komponen | Sebab | |---|---| | `public/test_curl.php` | Risiko keselamatan kritikal | | `mylesen(2).sql` | SQL backup dalam projek | | `app/Http/Controllers/Admin/asal/` (4 files) | Backup lama, tidak digunakan | | `routes/asal/web.php` | Route lama | | `resources/views/admin/penjaja/asal/` | Views lama | | Semua `-ori.blade.php`, `_lama.blade.php` | Fail backup | | `resources/views/fahmi.blade.php` | Test view | | `PbtpayController` + 4 PbtPay models | Jika keputusan tidak implement PBTPay | | `GrabResitLesen` model | Gantikan dengan BilService + scheduled job yang lebih bersih | | `User` boolean flags (`is_admin_lesen_*`) | Tidak digunakan | | 3 daripada 4 admin themes (`public/kai/`, `public/corporate-ui/`, `public/kapella/`) | Hanya satu yang digunakan | --- ## Bahagian 9 — Pelan Implementasi Menyeluruh ### 9.1 Fasa 0 — Pembersihan (1 Minggu) **Tiada perubahan pada business logic, hanya cleanup.** | Task | Keutamaan | Anggaran | |---|---|---| | Padam `public/test_curl.php` | KRITIKAL | 5 min | | Pindah `mylesen(2).sql` keluar dari projek | TINGGI | 5 min | | Padam `app/Http/Controllers/Admin/asal/` | TINGGI | 30 min | | Padam `routes/asal/web.php` | TINGGI | 5 min | | Padam `resources/views/admin/penjaja/asal/` | TINGGI | 30 min | | Padam semua `-ori.blade.php` dan `_lama.blade.php` | SEDERHANA | 30 min | | Tukar route `/` dari `view('fahmi')` ke `redirect('/utama')` | SEDERHANA | 5 min | | Test semua active routes masih berfungsi | WAJIB | 2 jam | | Commit: "cleanup: remove backup files and test files" | — | 10 min | **Jangan proceed ke Fasa 1 sebelum semua test lulus.** ### 9.2 Fasa 1 — Refactor Core (3 Minggu) **Minggu 1: Enum + Config** - Buat `app/Enums/StatusPermohonan.php` - Buat `app/Enums/StatusMesyuarat.php` - Buat `app/Enums/RolePengguna.php` - Gantikan semua string literals dalam 4 controllers - Buat `config/epbt.php` - Test: semua queue screens tunjuk data betul **Minggu 2: Services + FormRequest** - Buat `app/Services/EpbtService.php` — consolidate 4-location duplication - Buat `app/Services/BilService.php` - Buat FormRequest: `StorePermohonanRequest`, `StoreBilRequest` - Extract `Admin\PenjajaController::store()` ke `ApplicationService` - Tambah `DB::transaction()` di mana perlu - Tambah soft deletes pada `lesen_penjajas` **Minggu 3: Policy + Audit Trail** - Implement `LesenPenjajaPolicy` sepenuhnya - Implement `BorangUlasanIkPolicy` sepenuhnya - Buang Gate definitions dari `AppServiceProvider` - Implement `LesenPenjajaHistory` sebagai audit trail - Buat Observer untuk auto-log perubahan status - Fix N+1: implement eager loading di senarai queue ### 9.3 Fasa 2 — Lengkapkan Integrasi (3 Minggu) **Minggu 4: Aktifkan Integrasi** - Aktifkan `BilPelbagaiController::janaBil()` (buang `doNothing()` routing) - Test dalam persekitaran UAT dengan EPBT - Implement `CheckPaymentStatusJob` untuk scheduled polling **Minggu 5: PBTPay Decision + Laporan** - Laksanakan keputusan PBTPay (implement atau padam) - Betulkan `LaporanPrestasiController` — tulis semula dengan metric berguna: - KPI compliance (permohonan dalam masa 14 hari) - Breakdown mengikut kawasan - Trend bulanan **Minggu 6: UAT** - UAT dengan pengguna sebenar (PT, IK, PP, Pengarah) - Fix bugs dari UAT - Update dokumentasi ### 9.4 Fasa 3 — API + UI (4 Minggu) **Minggu 7-8: Chrome Extension API** - Pasang Laravel Sanctum - Buat `routes/api.php` - Buat `Api\AuthController` - Buat `Api\PermohonanController` - Buat API Resources (`PermohonanResource`) - Tambah token management dalam Profile page - Test API dengan Postman atau bruno **Minggu 9-10: UI + Final** - Bina halaman awam / landing page yang betul - Implementasi master data management UI (kawasan, taman, jalan, dll — sekarang tiada UI untuk edit) - UI improvements berdasarkan feedback UAT - Final end-to-end testing - Deployment ### 9.5 Dependency Map ``` Fasa 0 (cleanup) ↓ Fasa 1 — Enum (tiada dependency) ↓ Fasa 1 — Services (perlu Enum dahulu) ↓ Fasa 1 — Policy (perlu Services) ↓ Fasa 2 — Integration (perlu Services dari Fasa 1) ↓ Fasa 3 — API (boleh parallel dengan Fasa 2, tapi perlu Enum + Models siap) ``` ### 9.6 Strategi Migrasi (jika perlu run serentak) Jika sistem lama perlu terus berjalan semasa rebuild: - Kekalkan schema database yang sama — jangan tukar nama jadual atau column - Semua perubahan additive (tambah column, tambah jadual, bukan ubah/padam) - Jika tukar nama column perlu, buat alias atau migration yang tambah column baru dahulu, migrate data, kemudian padam yang lama - Gunakan feature flag: `config('app.use_new_service_layer')` untuk switch antara lama/baru ### 9.7 Strategi Business Rule Preservation - Baca kod lama secara teliti sebelum extract ke Service — jangan assume, confirm behavior - Tulis tests untuk setiap business rule kritikal sebelum refactor - Utamakan integration tests yang test full flow berbanding unit tests sahaja ### 9.8 Strategi Testing / UAT **Fasa 1-2:** Developer test sendiri — jalankan semua route, check semua queue screens masih betul **UAT Fasa 2 (akhir):** Minta pengguna sebenar test: - Pemohon: hantar permohonan baru dari awal hingga hantar - PT: proses permohonan dari baru hingga hantar ke IK - IK: isi borang ulasan, upload foto - PP: tulis cadangan - Pengarah: buat ulasan - PT: proses mesyuarat, keputusan, cetak Lampiran B - PT: daftar lesen --- ## Bahagian 10 — Cadangan Keputusan Awal yang Perlu Dimuktamadkan Senarai keputusan berikut perlu diputuskan **sebelum coding Fasa 1 bermula** untuk elak rework. --- **Keputusan 1: PBTPay — Implement atau Padam?** *Konteks:* PBTPay adalah payment gateway untuk pemohon bayar secara online. Sekarang semua code adalah stub. - **Pilihan A:** Implement PBTPay (kira-kira 2-3 minggu kerja) - **Pilihan B:** Padam semua stub. Bayaran kekal melalui EPBT eCAS semata-mata. - **Cadangan:** Pilihan B untuk jangka pendek. Bina semula jika diperlukan pada masa hadapan. --- **Keputusan 2: Aktifkan Auto-Generate Bil (janaBil)?** *Konteks:* `BilPelbagaiController::janaBil()` sudah siap ditulis. Route sengaja ditukar ke `doNothing()`. - **Soalan untuk MBIP IT:** Adakah `client_key = 'MPJBT'` sah untuk production? Adakah API endpoint EPBT boleh diakses dari server Laravel? - **Cadangan:** Aktifkan dalam UAT dulu, production setelah disahkan. --- **Keputusan 3: Status Terminal untuk Permohonan Selesai** *Konteks:* Sekarang tiada status formal selepas lesen dikeluarkan. - **Pilihan A:** Tambah `status_progress = 'lesen dikeluarkan'` sebagai status terminal - **Pilihan B:** Kekal `keputusan diperolehi`, tapi tambah column boolean `lesen_sudah_dikeluarkan` - **Cadangan:** Pilihan A — lebih jelas, konsisten dengan flow lain. --- **Keputusan 4: Clarify no_akaun_lesen vs no_fail_lesen** *Konteks:* Dua field ini serupa nama. Ada bug dalam kod semasa (simpan ke `no_akaun_lesen` tapi mesej kata `no_fail_lesen`). - **Soalan untuk pentadbiran:** Apakah maksud tepat setiap nombor dalam aliran kerja PBT? Adakah `no_fail_lesen` adalah nombor fail fizikal MBIP (cth: MBIP/PS/2025/001)? - **Cadangan:** Definisikan secara rasmi, kemudian fix bug dalam `simpanNoLesen()`. --- **Keputusan 5: Email Notification** *Konteks:* `MustVerifyEmail` dicomment out dalam `User.php`. Tiada email notification dalam sistem sekarang. - **Pilihan A:** Implement email notification bila status berubah (perlu SMTP config) - **Pilihan B:** Kekal tanpa email — pemohon perlu login untuk tahu status - **Cadangan:** Pilihan B untuk Fasa 1. Email boleh ditambah kemudian jika diperlukan. --- **Keputusan 6: Clarify Role PP — Satu atau Dua?** *Konteks:* `pp tadbir` dan `pegawai tadbir` ada dalam middleware, tapi dalam controller hanya `pegawai tadbir` digunakan untuk PP. - **Soalan:** Adakah `pp tadbir` berbeza daripada `pegawai tadbir` dalam konteks operasi harian? - **Cadangan:** Jika sama fungsi, satukan kepada satu role sahaja. Jika berbeza, define dengan jelas perbezaan akses. --- **Keputusan 7: Laporan Prestasi — Metric Apa Diperlukan?** *Konteks:* `LaporanPrestasiController` ada tapi broken. KPI tracking sudah ada dalam model (`kiraKpi()`, `patuh_kpi` field). - **Soalan untuk pengurusan:** Laporan apa yang benar-benar diperlukan? - **Cadangan minimum:** 1. Laporan KPI bulanan (berapa % permohonan siap dalam 14 hari) 2. Laporan breakdown mengikut kawasan 3. Laporan status semua permohonan terkini --- *Dokumen ini adalah cadangan hidup — boleh dikemaskini apabila keputusan dibuat.* *Rujuk `docs/audit-mylesen.md` untuk maklumat codebase semasa.*