Files
mylesen/docs/rebuild-architecture.md
2026-05-14 16:27:38 +08:00

1031 lines
39 KiB
Markdown

# 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.*