This commit is contained in:
Saufi
2026-06-03 08:51:22 +08:00
commit a14d43fe34
347 changed files with 38197 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class DatabaseSeeder extends Seeder
{
use WithoutModelEvents;
/**
* Seed the application's database.
*/
public function run(): void
{
$this->call(RolePermissionSeeder::class);
$this->call(PositionSeeder::class);
$admin = User::query()->firstOrCreate([
'email' => config('election.seed_admin.email'),
], [
'name' => config('election.seed_admin.name'),
'password' => Hash::make(config('election.seed_admin.password')),
'email_verified_at' => now(),
]);
if ($admin->email_verified_at === null) {
$admin->forceFill(['email_verified_at' => now()])->save();
}
$admin->assignRole('Admin');
$this->call(SampleElectionSeeder::class);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Database\Seeders;
use App\Models\Position;
use Illuminate\Database\Seeder;
class PositionSeeder extends Seeder
{
/**
* Seed default election staffing positions.
*/
public function run(): void
{
$positions = [
['code' => 'PPM', 'name' => 'Penyelia Pusat Mengundi', 'scope' => 'pusat', 'is_public_applyable' => false, 'is_assignable' => true, 'allows_dual_role' => true, 'sort_order' => 10],
['code' => 'KTM', 'name' => 'Ketua Tempat Mengundi', 'scope' => 'saluran', 'is_public_applyable' => true, 'is_assignable' => true, 'allows_dual_role' => true, 'sort_order' => 20],
['code' => 'KPDP', 'name' => 'Kerani Penyemak Daftar Pemilih', 'scope' => 'saluran', 'is_public_applyable' => true, 'is_assignable' => true, 'allows_dual_role' => false, 'sort_order' => 30],
['code' => 'KP', 'name' => 'Kerani Pengundian', 'scope' => 'saluran', 'is_public_applyable' => true, 'is_assignable' => true, 'allows_dual_role' => false, 'sort_order' => 40],
['code' => 'PAPM', 'name' => 'Penolong Penyelia Pusat Mengundi', 'scope' => 'pusat', 'is_public_applyable' => true, 'is_assignable' => true, 'allows_dual_role' => false, 'sort_order' => 50],
['code' => 'POLIS', 'name' => 'Polis Pengiring', 'scope' => 'external', 'is_public_applyable' => false, 'is_assignable' => false, 'allows_dual_role' => false, 'sort_order' => 60],
['code' => 'KKM', 'name' => 'Wakil KKM', 'scope' => 'external', 'is_public_applyable' => false, 'is_assignable' => false, 'allows_dual_role' => false, 'sort_order' => 70],
['code' => 'JKM', 'name' => 'Wakil JKM', 'scope' => 'external', 'is_public_applyable' => false, 'is_assignable' => false, 'allows_dual_role' => false, 'sort_order' => 80],
['code' => 'CALON_SIMPANAN', 'name' => 'Calon Simpanan', 'scope' => 'saluran', 'is_public_applyable' => false, 'is_assignable' => true, 'allows_dual_role' => false, 'sort_order' => 90],
];
foreach ($positions as $position) {
Position::query()->updateOrCreate(
['code' => $position['code']],
$position,
);
}
Position::query()
->where('code', 'CALON_SIMPANAN')
->update([
'code' => 'CALON_TAMBAHAN',
'name' => 'Calon Tambahan',
]);
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
class RolePermissionSeeder extends Seeder
{
/**
* Seed the application's roles and permissions.
*/
public function run(): void
{
app(PermissionRegistrar::class)->forgetCachedPermissions();
$permissions = [
'admin.dashboard.view',
'users.manage',
'roles.manage',
'election.manage',
'hierarchy.manage',
'positions.manage',
'quotas.manage',
'qr.manage',
'applications.view_all',
'applications.create_manual',
'applications.update',
'applications.approve',
'applications.reject',
'assignments.manage_all',
'representatives.manage',
'wheelchairs.manage',
'attendance.view_all',
'attendance.record_all',
'finance.dashboard.view',
'finance.view',
'finance.verify',
'finance.export',
'applications.view_finance_fields',
'exports.generate_all',
'audit.view',
'system_notes.create',
'ppm.dashboard.view',
'applications.view_own_pusat',
'applications.review_own_pusat',
'applications.approve_own_pusat',
'applications.reject_own_pusat',
'applications.change_role_own_pusat',
'assignments.manage_own_pusat',
'representatives.view_own_pusat',
'attendance.record_own_pusat',
'attendance.view_own_pusat',
'exports.generate_own_pusat',
'ktm.dashboard.view',
'ktm.team.view',
'ktm.kp.create',
'ktm.kp.approve',
'ktm.kp.delete_created',
'applications.view_own_team',
'public_application.create',
'public_application.view_own_status',
];
$permissionModels = [];
foreach ($permissions as $permission) {
$permissionModels[$permission] = Permission::findOrCreate($permission, 'web');
}
$rolePermissions = [
'Admin' => $permissions,
'Admin Kewangan' => [
'finance.dashboard.view',
'finance.view',
'finance.verify',
'finance.export',
'applications.view_finance_fields',
],
'PPM' => [
'ppm.dashboard.view',
'applications.view_own_pusat',
'applications.review_own_pusat',
'applications.approve_own_pusat',
'applications.reject_own_pusat',
'applications.change_role_own_pusat',
'assignments.manage_own_pusat',
'representatives.view_own_pusat',
'attendance.record_own_pusat',
'attendance.view_own_pusat',
'exports.generate_own_pusat',
],
'KTM' => [
'ktm.dashboard.view',
'ktm.team.view',
'ktm.kp.create',
'ktm.kp.approve',
'ktm.kp.delete_created',
'applications.view_own_team',
],
'Pemohon' => [
'public_application.create',
'public_application.view_own_status',
],
];
foreach ($rolePermissions as $roleName => $permissionNames) {
$role = Role::findOrCreate($roleName, 'web');
$role->syncPermissions(array_map(
fn (string $permission) => $permissionModels[$permission],
$permissionNames,
));
}
app(PermissionRegistrar::class)->forgetCachedPermissions();
}
}

View File

@@ -0,0 +1,248 @@
<?php
namespace Database\Seeders;
use App\Models\Dun;
use App\Models\Election;
use App\Models\Position;
use App\Models\PositionQuota;
use App\Models\PusatMengundi;
use App\Models\SaluranMengundi;
use App\Models\StaffAssignment;
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class SampleElectionSeeder extends Seeder
{
/**
* Seed sample election hierarchy for development and tests.
*/
public function run(): void
{
$this->cleanupSampleData();
$election = Election::query()->firstOrNew(['code' => 'PRN2026']);
if (! $election->exists) {
$election->public_uuid = (string) Str::uuid();
}
$election->forceFill([
'name' => 'Pilihanraya Negeri 2026',
'status' => 'active',
'starts_at' => now()->toDateString(),
'polling_date' => now()->addWeeks(4)->toDateString(),
])->save();
$election->settings()->updateOrCreate(
['election_id' => $election->id],
[
'registration_start_date' => now()->toDateString(),
'registration_end_date' => now()->addWeeks(3)->toDateString(),
'polling_date' => $election->polling_date,
'is_registration_open' => true,
'is_registration_open_override' => null,
'is_attendance_active' => false,
],
);
$dun = Dun::query()->updateOrCreate(
['election_id' => $election->id, 'code' => 'N49'],
['name' => 'Kota Iskandar'],
);
$pusat = new PusatMengundi;
$pusat->public_uuid = (string) Str::uuid();
$pusat->forceFill([
'election_id' => $election->id,
'code' => 'PM001',
'dun_id' => $dun->id,
'name' => 'SK Seri Contoh',
'address' => 'Jalan Contoh 1, 43000 Kajang, Selangor',
])->save();
$pusat->forceFill([
'registration_url' => url('/pohon/'.$pusat->public_uuid),
])->save();
$this->seedSaluran($election, $pusat);
$this->seedQuotas($election, $pusat);
$this->seedOperationalUsersAndAssignments($election, $pusat);
$pusat->wheelchairAllocation()->updateOrCreate(
['election_id' => $election->id],
[
'allocated_quantity' => 2,
'notes' => 'Sample allocation for Phase 2.',
],
);
}
private function cleanupSampleData(): void
{
$election = Election::query()->where('code', 'PRN2026')->first();
if (! $election) {
return;
}
PositionQuota::query()->where('election_id', $election->id)->forceDelete();
SaluranMengundi::query()->where('election_id', $election->id)->forceDelete();
PusatMengundi::query()->where('election_id', $election->id)->forceDelete();
}
private function seedSaluran(Election $election, PusatMengundi $pusat): void
{
foreach (range(1, 3) as $number) {
$pusat->saluranMengundis()->updateOrCreate(
['number' => (string) $number],
[
'election_id' => $election->id,
'name' => null,
'voter_count' => 450 + ($number * 25),
],
);
}
}
private function seedQuotas(Election $election, PusatMengundi $pusat): void
{
$positions = Position::query()->whereIn('code', [
'PPM',
'PAPM',
'KTM',
'KPDP',
'KP',
'POLIS',
'CALON_TAMBAHAN',
])->get()->keyBy('code');
PositionQuota::query()->updateOrCreate(
[
'election_id' => $election->id,
'pusat_mengundi_id' => $pusat->id,
'saluran_mengundi_id' => null,
'position_id' => $positions['PPM']->id,
],
['quota' => 1],
);
PositionQuota::query()->updateOrCreate(
[
'election_id' => $election->id,
'pusat_mengundi_id' => $pusat->id,
'saluran_mengundi_id' => null,
'position_id' => $positions['PAPM']->id,
],
['quota' => 3],
);
/** @var Collection<int, SaluranMengundi> $salurans */
$salurans = $pusat->saluranMengundis()->get();
foreach ($salurans as $saluran) {
foreach ([
'KTM' => 1,
'KPDP' => 1,
'KP' => 4,
'POLIS' => 1,
'CALON_TAMBAHAN' => 1,
] as $code => $quota) {
PositionQuota::query()->updateOrCreate(
[
'election_id' => $election->id,
'pusat_mengundi_id' => $pusat->id,
'saluran_mengundi_id' => $saluran->id,
'position_id' => $positions[$code]->id,
],
['quota' => $quota],
);
}
}
}
private function seedOperationalUsersAndAssignments(Election $election, PusatMengundi $pusat): void
{
$ppm = $this->seedUser('PPM Contoh', 'ppm@prn.local', ['PPM', 'KTM']);
$ktm = $this->seedUser('KTM Contoh', 'ktm@prn.local', ['KTM']);
$this->seedUser('Admin Kewangan Contoh', 'kewangan@prn.local', ['Admin Kewangan']);
$positions = Position::query()->whereIn('code', ['PPM', 'KTM'])->get()->keyBy('code');
/** @var SaluranMengundi $saluranPertama */
$saluranPertama = $pusat->saluranMengundis()->where('number', '1')->firstOrFail();
/** @var SaluranMengundi $saluranKedua */
$saluranKedua = $pusat->saluranMengundis()->where('number', '2')->firstOrFail();
StaffAssignment::query()->updateOrCreate(
[
'election_id' => $election->id,
'user_id' => $ppm->id,
'position_id' => $positions['PPM']->id,
'pusat_mengundi_id' => $pusat->id,
'saluran_mengundi_id' => null,
],
[
'status' => 'active',
'assigned_at' => now(),
'source' => 'sample_seed',
],
);
StaffAssignment::query()->updateOrCreate(
[
'election_id' => $election->id,
'user_id' => $ppm->id,
'position_id' => $positions['KTM']->id,
'pusat_mengundi_id' => $pusat->id,
'saluran_mengundi_id' => $saluranPertama->id,
],
[
'status' => 'active',
'assigned_at' => now(),
'source' => 'sample_seed_dual_role',
],
);
StaffAssignment::query()->updateOrCreate(
[
'election_id' => $election->id,
'user_id' => $ktm->id,
'position_id' => $positions['KTM']->id,
'pusat_mengundi_id' => $pusat->id,
'saluran_mengundi_id' => $saluranKedua->id,
],
[
'status' => 'active',
'assigned_at' => now(),
'source' => 'sample_seed',
],
);
}
/**
* @param array<int, string> $roles
*/
private function seedUser(string $name, string $email, array $roles): User
{
$user = User::query()->firstOrCreate(
['email' => $email],
[
'name' => $name,
'password' => Hash::make('password'),
'email_verified_at' => now(),
],
);
if ($user->email_verified_at === null) {
$user->forceFill(['email_verified_at' => now()])->save();
}
$user->syncRoles($roles);
return $user;
}
}