diff --git a/database/seeders/DevelopmentSeeder.php b/database/seeders/DevelopmentSeeder.php index 96985b0..80e8e7d 100644 --- a/database/seeders/DevelopmentSeeder.php +++ b/database/seeders/DevelopmentSeeder.php @@ -8,8 +8,10 @@ use App\Models\DriverProfile; use App\Models\DropOffPoint; use App\Models\Dumpsite; use App\Models\HelperProfile; +use App\Models\Household; use App\Models\NotificationPreference; use App\Models\PartnerStore; +use App\Models\ResidentProfile; use App\Models\Route as CollectionRoute; use App\Models\RouteStop; use App\Models\ScannerProfile; @@ -34,8 +36,8 @@ class DevelopmentSeeder extends Seeder { $password = Hash::make('password1'); - // 0. Super Admin (Global / Tenant-less) - $super = User::firstOrCreate( + // 1. Exactly 1 Super Admin + User::firstOrCreate( ['email' => 'super@verde.local'], [ 'phone' => '+639000000000', @@ -47,12 +49,13 @@ class DevelopmentSeeder extends Seeder 'tenant_id' => null, 'email_verified_at' => now(), ] - ); - $super->syncRoles(['super_admin']); + )->syncRoles(['super_admin']); + // 2. Three LGUs (Tenants) $lguConfigs = [ ['code' => 'SPB', 'name' => 'San Pascual, Batangas'], ['code' => 'BAU', 'name' => 'Bauan, Batangas'], + ['code' => 'LEM', 'name' => 'Lemery, Batangas'], ]; foreach ($lguConfigs as $index => $config) { @@ -73,139 +76,62 @@ class DevelopmentSeeder extends Seeder private function seedTenantData(Tenant $tenant, string $password, int $lguIndex): void { - // 1. Multiple Admins - for ($i = 1; $i <= 2; $i++) { - $admin = User::firstOrCreate( - ['email' => "admin{$i}.{$tenant->code}@verde.local"], - [ - 'phone' => "+63900{$lguIndex}00000{$i}", - 'password' => $password, - 'first_name' => "Verde", - 'last_name' => "Admin {$i} ({$tenant->code})", - 'role' => User::ROLE_ADMIN, - 'status' => User::STATUS_ACTIVE, - 'tenant_id' => $tenant->id, - 'email_verified_at' => now(), - ] - ); - $admin->syncRoles([User::ROLE_ADMIN]); - } + // NO LGU Admin seeded as requested - // 2. Geography + // 1. Infrastructure (3 Dumpsites, 5 DOPs) $barangay = Barangay::where('name', 'like', '%Poblacion%')->first() ?? Barangay::first(); - $dop = DropOffPoint::firstOrCreate( - ['name' => "{$tenant->code} Main Plaza"], - [ + $dumpsites = []; + for ($i = 1; $i <= 3; $i++) { + $dumpsites[] = Dumpsite::create([ + 'name' => "{$tenant->code} Landfill {$i}", 'uuid' => (string) Str::uuid(), 'tenant_id' => $tenant->id, - 'code' => "DOP-{$tenant->code}-001", - 'barangay_id' => $barangay->id, - 'address_line' => 'City Center', - 'coordinates' => new Point(14.65 + ($lguIndex * 0.1), 121.06, 4326), - ] - ); - - $dumpsite = Dumpsite::firstOrCreate( - ['name' => "{$tenant->code} Sanitary Landfill"], - [ - 'uuid' => (string) Str::uuid(), - 'tenant_id' => $tenant->id, - 'code' => "DS-{$tenant->code}-001", - 'address_line' => 'Waste Management Zone', - 'coordinates' => new Point(14.67 + ($lguIndex * 0.1), 121.08, 4326), - ] - ); - - // 3. Operational Staff (2 Drivers, 2 Helpers, 2 Scanners) - for ($i = 1; $i <= 2; $i++) { - $driver = $this->createStaff("driver{$i}.{$tenant->code}@verde.local", "+63918{$lguIndex}00000{$i}", "Driver", "{$i}", User::ROLE_DRIVER, $tenant, $password); - DriverProfile::updateOrCreate(['user_id' => $driver->id], ['license_number' => "L-{$tenant->code}-{$i}", 'verification_status' => 'approved', 'verified_at' => now()]); - - $helper = $this->createStaff("helper{$i}.{$tenant->code}@verde.local", "+63919{$lguIndex}00000{$i}", "Helper", "{$i}", User::ROLE_HELPER, $tenant, $password); - HelperProfile::updateOrCreate(['user_id' => $helper->id], ['verification_status' => 'approved', 'verified_at' => now()]); - - $scanner = $this->createStaff("scanner{$i}.{$tenant->code}@verde.local", "+63920{$lguIndex}00000{$i}", "Scanner", "{$i}", User::ROLE_SCANNER, $tenant, $password); - ScannerProfile::updateOrCreate(['user_id' => $scanner->id], ['verification_status' => 'approved', 'verified_at' => now()]); - - // 4. Assets & Teams - $truck = Truck::create([ - 'plate_number' => "VRD-{$tenant->code}-{$i}", - 'uuid' => (string) Str::uuid(), - 'tenant_id' => $tenant->id, - 'model' => 'Isuzu Elf', - 'capacity_kg' => 3000, - 'status' => Truck::STATUS_ACTIVE, + 'code' => "DS-{$tenant->code}-00{$i}", + 'address_line' => "Zone {$i}", + 'coordinates' => new Point(14.67 + ($i * 0.01), 121.08, 4326), ]); - - $team = CollectionTeam::create([ - 'name' => "{$tenant->code} Crew {$i}", - 'uuid' => (string) Str::uuid(), - 'tenant_id' => $tenant->id, - 'driver_id' => $driver->id, - 'scanner_id' => $scanner->id, - 'truck_id' => $truck->id, - 'status' => CollectionTeam::STATUS_ACTIVE, - ]); - TeamMember::create(['team_id' => $team->id, 'user_id' => $helper->id, 'role_in_team' => 'helper', 'assigned_from' => now()]); - - // 5. Routes & Dynamic Trips - $route = CollectionRoute::create([ - 'uuid' => (string) Str::uuid(), - 'tenant_id' => $tenant->id, - 'code' => "RT-{$tenant->code}-{$i}", - 'name' => "{$tenant->code} Route {$i}", - 'default_dumpsite_id' => $dumpsite->id, - 'default_team_id' => $team->id, - 'status' => CollectionRoute::STATUS_ACTIVE, - ]); - RouteStop::create(['route_id' => $route->id, 'drop_off_point_id' => $dop->id, 'sequence' => 1, 'estimated_duration_at_stop_minutes' => 15]); - - // Seed Trips for different time periods - $tripScenarios = [ - ['date' => now(), 'status' => Trip::STATUS_IN_PROGRESS, 'label' => 'Today'], - ['date' => now()->subDay(), 'status' => Trip::STATUS_COMPLETED, 'label' => 'Yesterday'], - ['date' => now()->subMonth(), 'status' => Trip::STATUS_COMPLETED, 'label' => 'Last Month'], - ['date' => now()->subMonths(2), 'status' => Trip::STATUS_COMPLETED, 'label' => 'Historical'], - ]; - - foreach ($tripScenarios as $scenario) { - $tripDate = $scenario['date']; - $isCompleted = $scenario['status'] === Trip::STATUS_COMPLETED; - - $trip = Trip::create([ - 'uuid' => (string) Str::uuid(), - 'tenant_id' => $tenant->id, - 'trip_number' => "TRIP-{$tenant->code}-{$i}-" . $tripDate->format('Ymd'), - 'route_id' => $route->id, - 'team_id' => $team->id, - 'truck_id' => $truck->id, - 'dumpsite_id' => $dumpsite->id, - 'scheduled_date' => $tripDate->format('Y-m-d'), - 'status' => $scenario['status'], - 'current_load_kg' => $isCompleted ? rand(1500, 2800) : 0, - 'actual_start_time' => $isCompleted ? $tripDate->copy()->setHour(8) : ($scenario['label'] === 'Today' ? now()->subHours(2) : null), - 'actual_end_time' => $isCompleted ? $tripDate->copy()->setHour(14) : null, - ]); - - TripStop::create([ - 'trip_id' => $trip->id, - 'drop_off_point_id' => $dop->id, - 'sequence' => 1, - 'status' => $isCompleted ? 'completed' : 'pending', - 'total_scans' => $isCompleted ? rand(50, 100) : 0, - 'estimated_load_added_kg' => $isCompleted ? rand(500, 800) : 0, - ]); - } } - // 6. Multiple Stores - for ($i = 1; $i <= 2; $i++) { - $storeOwner = $this->createStaff("store{$i}.{$tenant->code}@verde.local", "+63921{$lguIndex}00000{$i}", "Store", "Owner {$i}", User::ROLE_STORE_PARTNER, $tenant, $password); - StorePartnerProfile::updateOrCreate(['user_id' => $storeOwner->id], ['business_name' => "Store {$i} - {$tenant->code}", 'verification_status' => 'approved', 'verified_at' => now()]); - + $dops = []; + for ($i = 1; $i <= 5; $i++) { + $dops[] = DropOffPoint::create([ + 'name' => "{$tenant->code} Plaza {$i}", + 'uuid' => (string) Str::uuid(), + 'tenant_id' => $tenant->id, + 'code' => "DOP-{$tenant->code}-00{$i}", + 'barangay_id' => $barangay->id, + 'address_line' => "Point {$i}", + 'coordinates' => new Point(14.65 + ($i * 0.01), 121.06, 4326), + ]); + } + + // 2. Staff & Residents (10 each) + $drivers = $this->seedStaff($tenant, $password, $lguIndex, 'driver', 10); + $scanners = $this->seedStaff($tenant, $password, $lguIndex, 'scanner', 10); + $helpers = $this->seedStaff($tenant, $password, $lguIndex, 'helper', 10); + $residents = $this->seedStaff($tenant, $password, $lguIndex, 'resident', 10); + + // 3. Households (10 per LGU) + foreach ($residents as $i => $resident) { + ResidentProfile::create(['user_id' => $resident->id, 'verification_status' => 'approved', 'verified_at' => now()]); + Household::create([ + 'uuid' => (string) Str::uuid(), + 'tenant_id' => $tenant->id, + 'name' => "{$resident->last_name} Household", + 'address_line' => "Street {$i}", + 'barangay_id' => $barangay->id, + 'resident_user_id' => $resident->id, + 'assigned_drop_off_point_id' => $dops[rand(0, 4)]->id, + ]); + } + + // 4. Store Admins (5 per LGU) + for ($i = 1; $i <= 5; $i++) { + $owner = $this->createStaff("store{$i}.{$tenant->code}@verde.local", "+63921{$lguIndex}000{$i}", "Store", "Owner {$i}", User::ROLE_STORE_PARTNER, $tenant, $password); + StorePartnerProfile::create(['user_id' => $owner->id, 'business_name' => "Store {$i} - {$tenant->code}", 'verification_status' => 'approved', 'verified_at' => now()]); $store = PartnerStore::create([ - 'owner_user_id' => $storeOwner->id, + 'owner_user_id' => $owner->id, 'uuid' => (string) Str::uuid(), 'tenant_id' => $tenant->id, 'business_name' => "Store {$i} - {$tenant->code}", @@ -215,7 +141,101 @@ class DevelopmentSeeder extends Seeder 'coordinates' => new Point(14.651 + ($i * 0.001), 121.061, 4326), 'status' => PartnerStore::STATUS_ACTIVE, ]); - StoreInventory::create(['store_id' => $store->id, 'current_code_balance' => 100]); + StoreInventory::create(['store_id' => $store->id, 'current_code_balance' => 500]); + } + + // 5. Operations (5 Trucks, 5 Teams, 5 Routes, mixed Trips) + for ($i = 0; $i < 5; $i++) { + $truck = Truck::create([ + 'plate_number' => "VRD-{$tenant->code}-{$i}", + 'uuid' => (string) Str::uuid(), + 'tenant_id' => $tenant->id, + 'capacity_kg' => 3000, + 'status' => Truck::STATUS_ACTIVE, + ]); + + $team = CollectionTeam::create([ + 'name' => "{$tenant->code} Team " . ($i + 1), + 'uuid' => (string) Str::uuid(), + 'tenant_id' => $tenant->id, + 'driver_id' => $drivers[$i]->id, + 'scanner_id' => $scanners[$i]->id, + 'truck_id' => $truck->id, + 'status' => CollectionTeam::STATUS_ACTIVE, + ]); + TeamMember::create(['team_id' => $team->id, 'user_id' => $helpers[$i]->id, 'role_in_team' => 'helper', 'assigned_from' => now()]); + + $route = CollectionRoute::create([ + 'uuid' => (string) Str::uuid(), + 'tenant_id' => $tenant->id, + 'code' => "RT-{$tenant->code}-{$i}", + 'name' => "{$tenant->code} Route " . ($i + 1), + 'default_dumpsite_id' => $dumpsites[rand(0, 2)]->id, + 'default_team_id' => $team->id, + 'status' => CollectionRoute::STATUS_ACTIVE, + ]); + RouteStop::create(['route_id' => $route->id, 'drop_off_point_id' => $dops[rand(0, 4)]->id, 'sequence' => 1]); + + $this->seedTrips($tenant, $route, $team, $truck, $dumpsites[rand(0, 2)], $dops); + } + } + + private function seedStaff($tenant, $password, $lguIndex, $role, $count): array + { + $users = []; + $prefix = [ + 'driver' => '918', + 'scanner' => '920', + 'helper' => '919', + 'resident' => '945' + ][$role]; + + for ($i = 1; $i <= $count; $i++) { + $user = $this->createStaff("{$role}{$i}.{$tenant->code}@verde.local", "+63{$prefix}{$lguIndex}000{$i}", ucfirst($role), "{$i} ({$tenant->code})", "{$role}", $tenant, $password); + + if ($role === 'driver') DriverProfile::create(['user_id' => $user->id, 'license_number' => "L-{$tenant->code}-{$i}", 'verification_status' => 'approved', 'verified_at' => now()]); + if ($role === 'scanner') ScannerProfile::create(['user_id' => $user->id, 'verification_status' => 'approved', 'verified_at' => now()]); + if ($role === 'helper') HelperProfile::create(['user_id' => $user->id, 'verification_status' => 'approved', 'verified_at' => now()]); + + $users[] = $user; + } + return $users; + } + + private function seedTrips($tenant, $route, $team, $truck, $dumpsite, $dops): void + { + $scenarios = [ + ['date' => now(), 'status' => Trip::STATUS_IN_PROGRESS], + ['date' => now()->subDay(), 'status' => Trip::STATUS_COMPLETED], + ['date' => now()->subMonth(), 'status' => Trip::STATUS_COMPLETED], + ]; + + foreach ($scenarios as $scenario) { + $isComp = $scenario['status'] === Trip::STATUS_COMPLETED; + $trip = Trip::create([ + 'uuid' => (string) Str::uuid(), + 'tenant_id' => $tenant->id, + 'trip_number' => "TRIP-{$tenant->code}-" . $scenario['date']->format('YmdHi') . rand(10, 99), + 'route_id' => $route->id, + 'team_id' => $team->id, + 'truck_id' => $truck->id, + 'dumpsite_id' => $dumpsite->id, + 'scheduled_date' => $scenario['date']->format('Y-m-d'), + 'status' => $scenario['status'], + 'current_load_kg' => $isComp ? rand(1000, 2500) : 0, + 'actual_start_time' => $scenario['date']->copy()->setHour(8), + 'actual_end_time' => $isComp ? $scenario['date']->copy()->setHour(12) : null, + ]); + + foreach ($dops as $idx => $dop) { + TripStop::create([ + 'trip_id' => $trip->id, + 'drop_off_point_id' => $dop->id, + 'sequence' => $idx + 1, + 'status' => $isComp ? 'completed' : 'pending', + 'total_scans' => $isComp ? rand(10, 30) : 0, + ]); + } } } @@ -229,7 +249,7 @@ class DevelopmentSeeder extends Seeder 'last_name' => $last, 'role' => $role, 'status' => User::STATUS_ACTIVE, - 'tenant_id' => $tenant->id, + 'tenant_id' => $tenant ? $tenant->id : null, 'email_verified_at' => now(), ]); $u->syncRoles([$role]);