Aplikasi kehadiran KTM ambil peti undi (SPR 2026)
Sistem untuk admin DUN merekod waktu KTM (Ketua Tempat Mengundi) hadir mengambil peti undi. Data KTM dibaca daripada DB sppm (spr2026) melalui connection kedua — hanya penempatan berjawatan KTM yang dipaparkan. - Dashboard: senarai KTM ikut pusat mengundi (asc) & saluran (asc), status BELUM HADIR (merah) / HADIR (hijau) + tarikh & masa - Carian nama/no. KP, tapisan pusat mengundi & status - Tanda hadir / batal dengan kawalan DUN (admin terhad ke DUN sendiri) - Eksport Excel (worksheet Hadir & Belum Hadir) guna SimpleXlsx - Peranan: superadmin (urus pengguna, semua DUN) & admin DUN - Docker: nginx + php-fpm + queue + scheduler + webhook deploy, port 127.0.0.1:8009, TZ Asia/Kuala_Lumpur di semua container - Zon waktu aplikasi: Asia/Kuala_Lumpur (APP_TIMEZONE) Berasaskan scaffold spr2026_taklimat. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
40
app/Models/KehadiranKtm.php
Normal file
40
app/Models/KehadiranKtm.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Sppm\PetugasAssignment;
|
||||
use App\Models\Sppm\PetugasPermohonan;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class KehadiranKtm extends Model
|
||||
{
|
||||
protected $table = 'kehadiran_ktm';
|
||||
|
||||
protected $fillable = [
|
||||
'assignment_id',
|
||||
'permohonan_id',
|
||||
'dun_id',
|
||||
'hadir_at',
|
||||
'dicatat_oleh_user_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'hadir_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function assignment(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PetugasAssignment::class, 'assignment_id');
|
||||
}
|
||||
|
||||
public function permohonan(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PetugasPermohonan::class, 'permohonan_id');
|
||||
}
|
||||
|
||||
public function dicatatOleh(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'dicatat_oleh_user_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 = [];
|
||||
}
|
||||
49
app/Models/Sppm/PetugasAssignment.php
Normal file
49
app/Models/Sppm/PetugasAssignment.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?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 permohonan(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PetugasPermohonan::class, 'permohonan_id');
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Penempatan KTM (Ketua Tempat Mengundi) sahaja.
|
||||
*/
|
||||
public function scopeKtm($query)
|
||||
{
|
||||
return $query->whereHas('jawatan', fn ($q) => $q->where('kod', 'KTM'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Penempatan dalam DUN tertentu (melalui pusat mengundi penempatan).
|
||||
*/
|
||||
public function scopeUntukDun($query, int $dunId)
|
||||
{
|
||||
return $query->whereHas('pusatMengundi', fn ($q) => $q->where('dun_id', $dunId));
|
||||
}
|
||||
}
|
||||
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 ?? '-',
|
||||
];
|
||||
}
|
||||
}
|
||||
18
app/Models/Sppm/PusatMengundi.php
Normal file
18
app/Models/Sppm/PusatMengundi.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Sppm;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class PusatMengundi extends Model
|
||||
{
|
||||
protected $connection = 'sppm';
|
||||
protected $table = 'pusat_mengundi';
|
||||
protected $guarded = [];
|
||||
|
||||
public function dun(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Dun::class, 'dun_id');
|
||||
}
|
||||
}
|
||||
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 = [];
|
||||
}
|
||||
58
app/Models/User.php
Normal file
58
app/Models/User.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use App\Models\Sppm\Dun;
|
||||
use App\Notifications\ResetKataLaluanNotification;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
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 isSuperadmin(): bool
|
||||
{
|
||||
return $this->role === 'superadmin';
|
||||
}
|
||||
|
||||
public function isAdminDun(): bool
|
||||
{
|
||||
return $this->role === 'admin';
|
||||
}
|
||||
|
||||
public function dun(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Dun::class, 'dun_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Guna e-mel dalam Bahasa Melayu untuk pautan set semula kata laluan.
|
||||
*/
|
||||
public function sendPasswordResetNotification(#[\SensitiveParameter] $token): void
|
||||
{
|
||||
$this->notify(new ResetKataLaluanNotification($token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user