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

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

  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

// 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.

// 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

  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)
  • 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

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: 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:

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

{
  "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.