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