first commit
This commit is contained in:
45
app/Http/Controllers/AuthController.php
Normal file
45
app/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function showLogin()
|
||||
{
|
||||
if (Auth::check()) {
|
||||
return redirect()->route('sesi.index');
|
||||
}
|
||||
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
public function login(Request $request)
|
||||
{
|
||||
$credentials = $request->validate([
|
||||
'email' => ['required', 'email'],
|
||||
'password' => ['required'],
|
||||
]);
|
||||
|
||||
if (Auth::attempt($credentials, $request->boolean('remember'))) {
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->intended(route('sesi.index'));
|
||||
}
|
||||
|
||||
return back()->withErrors([
|
||||
'email' => 'Emel atau kata laluan tidak sah.',
|
||||
])->onlyInput('email');
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect()->route('login');
|
||||
}
|
||||
}
|
||||
121
app/Http/Controllers/CheckinController.php
Normal file
121
app/Http/Controllers/CheckinController.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\KehadiranTaklimat;
|
||||
use App\Models\SesiTaklimat;
|
||||
use App\Models\Sppm\PetugasPermohonan;
|
||||
use Illuminate\Database\UniqueConstraintViolationException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CheckinController extends Controller
|
||||
{
|
||||
/**
|
||||
* Papar borang daftar kehadiran selepas petugas scan QR DUN.
|
||||
*/
|
||||
public function show(string $token)
|
||||
{
|
||||
$sesi = SesiTaklimat::where('qr_token', $token)->firstOrFail();
|
||||
$sesi->load('dun');
|
||||
|
||||
if (! $sesi->is_active) {
|
||||
return view('checkin.closed', compact('sesi'));
|
||||
}
|
||||
|
||||
return view('checkin.form', compact('sesi'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sahkan no KP dengan SPPM dan rekod kehadiran.
|
||||
*/
|
||||
public function submit(Request $request, string $token)
|
||||
{
|
||||
$sesi = SesiTaklimat::where('qr_token', $token)->firstOrFail();
|
||||
$sesi->load('dun');
|
||||
|
||||
if (! $sesi->is_active) {
|
||||
return view('checkin.closed', compact('sesi'));
|
||||
}
|
||||
|
||||
$request->validate(
|
||||
['no_kp' => ['required', 'string', 'max:20']],
|
||||
['no_kp.required' => 'Sila masukkan nombor kad pengenalan anda.'],
|
||||
);
|
||||
|
||||
$noKp = preg_replace('/[^0-9A-Za-z]/', '', $request->input('no_kp'));
|
||||
|
||||
// Semak dalam SPPM: no_kp padan + status 'assigned' + DUN yang betul
|
||||
$petugas = PetugasPermohonan::assignedUntukDun($sesi->dun_id)
|
||||
->whereRaw("REPLACE(REPLACE(no_kp, '-', ''), ' ', '') = ?", [$noKp])
|
||||
->with(['assignments' => fn ($q) => $q->orderBy('urutan')->with(['pusatMengundi', 'saluran', 'jawatan'])])
|
||||
->first();
|
||||
|
||||
if (! $petugas) {
|
||||
return back()->withErrors([
|
||||
'no_kp' => 'Maaf, rekod anda tidak ditemui sebagai petugas bertugas (assigned) untuk '
|
||||
. ($sesi->dun->nama ?? 'DUN ini')
|
||||
. '. Sila semak nombor kad pengenalan atau rujuk kaunter pendaftaran.',
|
||||
])->onlyInput('no_kp');
|
||||
}
|
||||
|
||||
// Jika sudah daftar dalam sesi ini, papar semula slip sedia ada
|
||||
$kehadiran = KehadiranTaklimat::where('sesi_taklimat_id', $sesi->id)
|
||||
->where('permohonan_id', $petugas->id)
|
||||
->first();
|
||||
|
||||
$sudahDaftar = (bool) $kehadiran;
|
||||
|
||||
if (! $kehadiran) {
|
||||
$kehadiran = $this->rekodKehadiran($sesi, $petugas, $noKp);
|
||||
}
|
||||
|
||||
return view('checkin.slip', [
|
||||
'sesi' => $sesi,
|
||||
'petugas' => $petugas,
|
||||
'kehadiran' => $kehadiran,
|
||||
'penempatan' => $petugas->butiranPenempatan(),
|
||||
'sudahDaftar' => $sudahDaftar,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Jana nombor siri seterusnya dan simpan kehadiran secara atomik.
|
||||
*/
|
||||
private function rekodKehadiran(SesiTaklimat $sesi, PetugasPermohonan $petugas, string $noKp): KehadiranTaklimat
|
||||
{
|
||||
$attempts = 0;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
return DB::transaction(function () use ($sesi, $petugas, $noKp) {
|
||||
$nomborSeterusnya = (int) KehadiranTaklimat::where('sesi_taklimat_id', $sesi->id)
|
||||
->lockForUpdate()
|
||||
->max('nombor_siri') + 1;
|
||||
|
||||
return KehadiranTaklimat::create([
|
||||
'sesi_taklimat_id' => $sesi->id,
|
||||
'permohonan_id' => $petugas->id,
|
||||
'no_kp' => $noKp,
|
||||
'nombor_siri' => $nomborSeterusnya,
|
||||
'hadir_at' => now(),
|
||||
]);
|
||||
});
|
||||
} catch (UniqueConstraintViolationException $e) {
|
||||
// Hantar berganda: rekod petugas ini mungkin sudah wujud
|
||||
$sediaAda = KehadiranTaklimat::where('sesi_taklimat_id', $sesi->id)
|
||||
->where('permohonan_id', $petugas->id)
|
||||
->first();
|
||||
|
||||
if ($sediaAda) {
|
||||
return $sediaAda;
|
||||
}
|
||||
|
||||
// Perlumbaan nombor siri — cuba semula sehingga 3 kali
|
||||
if (++$attempts >= 3) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
73
app/Http/Controllers/KaunterController.php
Normal file
73
app/Http/Controllers/KaunterController.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\KehadiranTaklimat;
|
||||
use App\Models\SesiTaklimat;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class KaunterController extends Controller
|
||||
{
|
||||
/**
|
||||
* Skrin kaunter: masukkan nombor siri slip petugas.
|
||||
*/
|
||||
public function index(Request $request, SesiTaklimat $sesi)
|
||||
{
|
||||
$this->pastikanAkses($request, $sesi);
|
||||
$sesi->load('dun');
|
||||
|
||||
$kehadiran = null;
|
||||
$penempatan = null;
|
||||
$ralat = null;
|
||||
|
||||
if ($request->filled('nombor_siri')) {
|
||||
$nombor = (int) ltrim($request->input('nombor_siri'), '0');
|
||||
|
||||
$kehadiran = KehadiranTaklimat::where('sesi_taklimat_id', $sesi->id)
|
||||
->where('nombor_siri', $nombor)
|
||||
->with('permohonan.assignments.pusatMengundi', 'permohonan.assignments.saluran', 'permohonan.assignments.jawatan')
|
||||
->first();
|
||||
|
||||
if ($kehadiran) {
|
||||
$penempatan = $kehadiran->permohonan?->butiranPenempatan();
|
||||
} else {
|
||||
$ralat = 'Nombor siri tidak ditemui dalam sesi ini.';
|
||||
}
|
||||
}
|
||||
|
||||
return view('kaunter.index', compact('sesi', 'kehadiran', 'penempatan', 'ralat'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rekod petugas telah ambil borang SPR.
|
||||
*/
|
||||
public function serahBorang(Request $request, SesiTaklimat $sesi, KehadiranTaklimat $kehadiran)
|
||||
{
|
||||
$this->pastikanAkses($request, $sesi);
|
||||
|
||||
if ((int) $kehadiran->sesi_taklimat_id !== (int) $sesi->id) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
if (! $kehadiran->borang_diambil) {
|
||||
$kehadiran->update([
|
||||
'borang_diambil' => true,
|
||||
'borang_diambil_at' => now(),
|
||||
'borang_diambil_oleh_user_id' => $request->user()->id,
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('kaunter.index', $sesi)
|
||||
->with('status', 'Borang direkodkan sebagai telah diserahkan kepada petugas (siri ' . $kehadiran->nombor_siri_papar . ').');
|
||||
}
|
||||
|
||||
private function pastikanAkses(Request $request, SesiTaklimat $sesi): void
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if ($user->isPengurus() && (int) $sesi->dun_id !== (int) $user->dun_id) {
|
||||
abort(403, 'Sesi ini bukan untuk DUN anda.');
|
||||
}
|
||||
}
|
||||
}
|
||||
43
app/Http/Controllers/PengurusController.php
Normal file
43
app/Http/Controllers/PengurusController.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\SesiTaklimat;
|
||||
use App\Models\Sppm\PetugasPermohonan;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PengurusController extends Controller
|
||||
{
|
||||
/**
|
||||
* Senarai petugas hadir dan tidak/belum hadir bagi satu sesi taklimat.
|
||||
*/
|
||||
public function senarai(Request $request, SesiTaklimat $sesi)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if ($user->isPengurus() && (int) $sesi->dun_id !== (int) $user->dun_id) {
|
||||
abort(403, 'Sesi ini bukan untuk DUN anda.');
|
||||
}
|
||||
|
||||
$sesi->load('dun');
|
||||
|
||||
// Semua petugas 'assigned' untuk DUN sesi ini
|
||||
$semuaPetugas = PetugasPermohonan::assignedUntukDun($sesi->dun_id)
|
||||
->with(['assignments' => fn ($q) => $q->orderBy('urutan')->with(['pusatMengundi', 'saluran', 'jawatan'])])
|
||||
->orderBy('nama_ic')
|
||||
->get();
|
||||
|
||||
$kehadiran = $sesi->kehadiran()->get()->keyBy('permohonan_id');
|
||||
|
||||
[$hadir, $tidakHadir] = $semuaPetugas->partition(
|
||||
fn (PetugasPermohonan $p) => $kehadiran->has($p->id)
|
||||
);
|
||||
|
||||
return view('pengurus.senarai', [
|
||||
'sesi' => $sesi,
|
||||
'hadir' => $hadir->values(),
|
||||
'tidakHadir' => $tidakHadir->values(),
|
||||
'kehadiran' => $kehadiran,
|
||||
]);
|
||||
}
|
||||
}
|
||||
120
app/Http/Controllers/SesiTaklimatController.php
Normal file
120
app/Http/Controllers/SesiTaklimatController.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\SesiTaklimat;
|
||||
use App\Models\Sppm\Dun;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class SesiTaklimatController extends Controller
|
||||
{
|
||||
/**
|
||||
* Senarai sesi taklimat. Pengurus hanya nampak DUN sendiri.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$sesi = SesiTaklimat::withCount('kehadiran')
|
||||
->when($user->isPengurus(), fn ($q) => $q->where('dun_id', $user->dun_id))
|
||||
->orderByDesc('tarikh')
|
||||
->get();
|
||||
|
||||
$sesi->load('dun');
|
||||
|
||||
return view('sesi.index', compact('sesi'));
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$senaraiDun = Dun::where('is_active', true)
|
||||
->when($user->isPengurus(), fn ($q) => $q->where('id', $user->dun_id))
|
||||
->orderBy('code')
|
||||
->get();
|
||||
|
||||
return view('sesi.create', compact('senaraiDun'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$data = $request->validate([
|
||||
'dun_id' => [
|
||||
'required',
|
||||
'integer',
|
||||
// Hanya 1 sesi dalam 1 hari untuk setiap dun
|
||||
Rule::unique('sesi_taklimat', 'dun_id')->where('tarikh', $request->input('tarikh')),
|
||||
],
|
||||
'tarikh' => ['required', 'date'],
|
||||
'nama' => ['required', 'string', 'max:255'],
|
||||
'lokasi' => ['required', 'string', 'max:255'],
|
||||
'masa' => ['nullable', 'string', 'max:100'],
|
||||
], [
|
||||
'dun_id.unique' => 'Sesi taklimat untuk DUN ini sudah wujud pada tarikh tersebut. Hanya 1 sesi dibenarkan sehari bagi setiap DUN.',
|
||||
]);
|
||||
|
||||
if ($user->isPengurus() && (int) $data['dun_id'] !== (int) $user->dun_id) {
|
||||
abort(403, 'Anda hanya boleh mencipta sesi untuk DUN anda sendiri.');
|
||||
}
|
||||
|
||||
if (! Dun::where('id', $data['dun_id'])->where('is_active', true)->exists()) {
|
||||
return back()->withErrors(['dun_id' => 'DUN tidak sah.'])->withInput();
|
||||
}
|
||||
|
||||
$data['created_by_user_id'] = $user->id;
|
||||
|
||||
$sesi = SesiTaklimat::create($data);
|
||||
|
||||
return redirect()->route('sesi.show', $sesi)->with('status', 'Sesi taklimat berjaya dicipta.');
|
||||
}
|
||||
|
||||
public function show(Request $request, SesiTaklimat $sesi)
|
||||
{
|
||||
$this->pastikanAkses($request, $sesi);
|
||||
|
||||
$sesi->load('dun');
|
||||
$sesi->loadCount([
|
||||
'kehadiran',
|
||||
'kehadiran as borang_diambil_count' => fn ($q) => $q->where('borang_diambil', true),
|
||||
]);
|
||||
|
||||
$checkinUrl = route('checkin.show', $sesi->qr_token);
|
||||
|
||||
return view('sesi.show', compact('sesi', 'checkinUrl'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Papar QR untuk dicetak / dipamerkan di lokasi taklimat.
|
||||
*/
|
||||
public function qr(Request $request, SesiTaklimat $sesi)
|
||||
{
|
||||
$this->pastikanAkses($request, $sesi);
|
||||
|
||||
$sesi->load('dun');
|
||||
$checkinUrl = route('checkin.show', $sesi->qr_token);
|
||||
|
||||
return view('sesi.qr', compact('sesi', 'checkinUrl'));
|
||||
}
|
||||
|
||||
public function toggle(Request $request, SesiTaklimat $sesi)
|
||||
{
|
||||
$this->pastikanAkses($request, $sesi);
|
||||
|
||||
$sesi->update(['is_active' => ! $sesi->is_active]);
|
||||
|
||||
return back()->with('status', $sesi->is_active ? 'Sesi diaktifkan.' : 'Sesi ditutup.');
|
||||
}
|
||||
|
||||
private function pastikanAkses(Request $request, SesiTaklimat $sesi): void
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if ($user->isPengurus() && (int) $sesi->dun_id !== (int) $user->dun_id) {
|
||||
abort(403, 'Sesi ini bukan untuk DUN anda.');
|
||||
}
|
||||
}
|
||||
}
|
||||
19
app/Http/Middleware/EnsureRole.php
Normal file
19
app/Http/Middleware/EnsureRole.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureRole
|
||||
{
|
||||
public function handle(Request $request, Closure $next, string ...$roles): Response
|
||||
{
|
||||
if (! $request->user() || ! in_array($request->user()->role, $roles, true)) {
|
||||
abort(403, 'Anda tidak mempunyai kebenaran untuk akses halaman ini.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
37
app/Models/KehadiranTaklimat.php
Normal file
37
app/Models/KehadiranTaklimat.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Sppm\PetugasPermohonan;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class KehadiranTaklimat extends Model
|
||||
{
|
||||
protected $table = 'kehadiran_taklimat';
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'hadir_at' => 'datetime',
|
||||
'borang_diambil' => 'boolean',
|
||||
'borang_diambil_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function sesi(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(SesiTaklimat::class, 'sesi_taklimat_id');
|
||||
}
|
||||
|
||||
public function permohonan(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PetugasPermohonan::class, 'permohonan_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Nombor siri berpad minimum 3 digit (001, 002, 1001).
|
||||
*/
|
||||
public function getNomborSiriPaparAttribute(): string
|
||||
{
|
||||
return str_pad((string) $this->nombor_siri, 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
}
|
||||
37
app/Models/SesiTaklimat.php
Normal file
37
app/Models/SesiTaklimat.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Sppm\Dun;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SesiTaklimat extends Model
|
||||
{
|
||||
protected $table = 'sesi_taklimat';
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'tarikh' => 'date',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (SesiTaklimat $sesi) {
|
||||
$sesi->qr_token ??= Str::random(32);
|
||||
});
|
||||
}
|
||||
|
||||
public function dun(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Dun::class, 'dun_id');
|
||||
}
|
||||
|
||||
public function kehadiran(): HasMany
|
||||
{
|
||||
return $this->hasMany(KehadiranTaklimat::class, 'sesi_taklimat_id');
|
||||
}
|
||||
}
|
||||
12
app/Models/Sppm/Dun.php
Normal file
12
app/Models/Sppm/Dun.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Sppm;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Dun extends Model
|
||||
{
|
||||
protected $connection = 'sppm';
|
||||
protected $table = 'dun';
|
||||
protected $guarded = [];
|
||||
}
|
||||
12
app/Models/Sppm/JawatanTetapan.php
Normal file
12
app/Models/Sppm/JawatanTetapan.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Sppm;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class JawatanTetapan extends Model
|
||||
{
|
||||
protected $connection = 'sppm';
|
||||
protected $table = 'jawatan_tetapan';
|
||||
protected $guarded = [];
|
||||
}
|
||||
28
app/Models/Sppm/PetugasAssignment.php
Normal file
28
app/Models/Sppm/PetugasAssignment.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Sppm;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class PetugasAssignment extends Model
|
||||
{
|
||||
protected $connection = 'sppm';
|
||||
protected $table = 'petugas_assignments';
|
||||
protected $guarded = [];
|
||||
|
||||
public function pusatMengundi(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PusatMengundi::class, 'pusat_mengundi_id');
|
||||
}
|
||||
|
||||
public function saluran(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Saluran::class, 'saluran_id');
|
||||
}
|
||||
|
||||
public function jawatan(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(JawatanTetapan::class, 'jawatan_tetapan_id');
|
||||
}
|
||||
}
|
||||
76
app/Models/Sppm/PetugasPermohonan.php
Normal file
76
app/Models/Sppm/PetugasPermohonan.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Sppm;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class PetugasPermohonan extends Model
|
||||
{
|
||||
protected $connection = 'sppm';
|
||||
protected $table = 'petugas_permohonan';
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* Petugas berstatus 'assigned' untuk sesuatu DUN. Padanan DUN melalui
|
||||
* dun_id permohonan atau dun pusat mengundi penempatan.
|
||||
*/
|
||||
public function scopeAssignedUntukDun($query, int $dunId)
|
||||
{
|
||||
return $query->where('status', 'assigned')
|
||||
->where(function ($q) use ($dunId) {
|
||||
$q->where('dun_id', $dunId)
|
||||
->orWhereHas('assignments.pusatMengundi', fn ($qq) => $qq->where('dun_id', $dunId));
|
||||
});
|
||||
}
|
||||
|
||||
public function dun(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Dun::class, 'dun_id');
|
||||
}
|
||||
|
||||
public function assignments(): HasMany
|
||||
{
|
||||
return $this->hasMany(PetugasAssignment::class, 'permohonan_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Penempatan utama petugas (urutan terendah).
|
||||
*/
|
||||
public function assignmentUtama(): ?PetugasAssignment
|
||||
{
|
||||
if ($this->relationLoaded('assignments')) {
|
||||
return $this->assignments->sortBy('urutan')->first();
|
||||
}
|
||||
|
||||
return $this->assignments()
|
||||
->with(['pusatMengundi', 'saluran', 'jawatan'])
|
||||
->orderBy('urutan')
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maklumat penempatan untuk paparan: pusat mengundi, saluran, jawatan.
|
||||
* Diambil daripada petugas_assignments; fallback kepada medan permohonan.
|
||||
*/
|
||||
public function butiranPenempatan(): array
|
||||
{
|
||||
$assignment = $this->assignmentUtama();
|
||||
|
||||
$pusatMengundi = $assignment?->pusatMengundi
|
||||
?? ($this->pusat_mengundi_id ? PusatMengundi::find($this->pusat_mengundi_id) : null);
|
||||
|
||||
$saluran = $assignment?->saluran
|
||||
?? ($this->assigned_saluran_id ? Saluran::find($this->assigned_saluran_id) : null);
|
||||
|
||||
$jawatan = $assignment?->jawatan
|
||||
?? ($this->jawatan_id ? JawatanTetapan::find($this->jawatan_id) : null);
|
||||
|
||||
return [
|
||||
'pusat_mengundi' => $pusatMengundi?->nama ?? '-',
|
||||
'saluran' => $saluran ? 'Saluran ' . $saluran->nombor_saluran : '-',
|
||||
'jawatan' => $jawatan?->nama ?? '-',
|
||||
];
|
||||
}
|
||||
}
|
||||
12
app/Models/Sppm/PusatMengundi.php
Normal file
12
app/Models/Sppm/PusatMengundi.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Sppm;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PusatMengundi extends Model
|
||||
{
|
||||
protected $connection = 'sppm';
|
||||
protected $table = 'pusat_mengundi';
|
||||
protected $guarded = [];
|
||||
}
|
||||
12
app/Models/Sppm/Saluran.php
Normal file
12
app/Models/Sppm/Saluran.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Sppm;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Saluran extends Model
|
||||
{
|
||||
protected $connection = 'sppm';
|
||||
protected $table = 'saluran';
|
||||
protected $guarded = [];
|
||||
}
|
||||
42
app/Models/User.php
Normal file
42
app/Models/User.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
#[Fillable(['name', 'email', 'password', 'role', 'dun_id'])]
|
||||
#[Hidden(['password', 'remember_token'])]
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
public function isAdmin(): bool
|
||||
{
|
||||
return $this->role === 'admin';
|
||||
}
|
||||
|
||||
public function isPengurus(): bool
|
||||
{
|
||||
return $this->role === 'pengurus';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
}
|
||||
24
app/Providers/AppServiceProvider.php
Normal file
24
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user