39 KiB
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
- Bukan rewrite penuh — aliran bisnes sedia ada sudah betul. Yang perlu diperbaiki adalah cara kod disusun.
- Laravel convention — gunakan apa yang sudah ada dalam Laravel (FormRequest, Policy, Enum, Queue) sebelum bina sendiri.
- Tidak over-engineer — tiada Repository pattern, tiada CQRS, tiada microservices. Service class yang mudah sudah cukup.
- Satu sumber kebenaran — status, roles, dan constants dalam Enum dan Config, bukan string literals.
- 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
- Pakai PBTPay atau bayaran kekal melalui EPBT eCAS sahaja?
- Aktifkan
janaBil()auto-generate atau kekal manual? - Apa status terminal yang jelas untuk permohonan yang selesai?
- Clarify peranan
no_akaun_lesenvsno_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
// 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_progresssimpan 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()danPrintService::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.
// 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:
$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
- Satu tema sahaja — pilih satu daripada 4 tema yang ada, buang yang lain
- Bootstrap 5 — lebih mudah untuk team kecil maintain berbanding Tailwind + custom theming
- Status badge konsisten — setiap status ada warna yang sama di semua halaman
- Mobile-friendly — PT mungkin guna tablet semasa kerja
- 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)
- Pemohon →
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
drafsecara 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:
- Test di persekitaran UAT dulu
- Sahkan
client_key = 'MPJBT'dengan MBIP IT - Pindah config ke
config/epbt.php(jangan hardcode) - Aktifkan dalam production setelah berjaya UAT
6.2 Rekabentuk BilService
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
// 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:
PbtpayBilmodel siap,PbtpayController::checkout(), callback handler,PbtpayTransaksiuntuk 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 dalamsimpanWangProses) — kekalkan pattern ini - Untuk scheduled job semak bayaran: semak
updated_atterakhir, 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:
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:
- Staf login ke myLesen web UI biasa
- Dalam Profile, staf boleh "Jana Token API" untuk Chrome extension
- Token disimpan dalam extension
- 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
{
"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 aktifkeputusan_diterimadenganstatus_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()keApplicationService - Tambah
DB::transaction()di mana perlu - Tambah soft deletes pada
lesen_penjajas
Minggu 3: Policy + Audit Trail
- Implement
LesenPenjajaPolicysepenuhnya - Implement
BorangUlasanIkPolicysepenuhnya - Buang Gate definitions dari
AppServiceProvider - Implement
LesenPenjajaHistorysebagai 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()(buangdoNothing()routing) - Test dalam persekitaran UAT dengan EPBT
- Implement
CheckPaymentStatusJobuntuk 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 booleanlesen_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_lesenadalah 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 tadbirberbeza daripadapegawai tadbirdalam 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:
- Laporan KPI bulanan (berapa % permohonan siap dalam 14 hari)
- Laporan breakdown mengikut kawasan
- Laporan status semua permohonan terkini
Dokumen ini adalah cadangan hidup — boleh dikemaskini apabila keputusan dibuat.
Rujuk docs/audit-mylesen.md untuk maklumat codebase semasa.