call(SamplePsgcSeeder::class); $this->call(RoleSeeder::class); $password = Hash::make('password1'); // 1. Exactly 1 Super Admin User::firstOrCreate( ['email' => 'super@verde.local'], [ 'phone' => '+639000000000', 'password' => $password, 'first_name' => 'System', 'last_name' => 'Super Admin', 'role' => 'super_admin', 'status' => User::STATUS_ACTIVE, 'tenant_id' => null, 'email_verified_at' => now(), ] )->syncRoles(['super_admin']); // 2. Three LGUs (Tenants) // 1. Quezon City (Primary testing LGU) $tenantQC = Tenant::updateOrCreate(['code' => 'QC'], ['name' => 'Quezon City', 'status' => Tenant::STATUS_ACTIVE, 'qr_retail_price_centavos' => 1000]); $this->seedTenantData($tenantQC, $password, 0); // 2. Manila $tenantMNL = Tenant::updateOrCreate(['code' => 'MNL'], ['name' => 'Manila', 'status' => Tenant::STATUS_ACTIVE, 'qr_retail_price_centavos' => 1000]); $this->seedTenantData($tenantMNL, $password, 1); // 3. Pasig $tenantPSG = Tenant::updateOrCreate(['code' => 'PSG'], ['name' => 'Pasig', 'status' => Tenant::STATUS_ACTIVE, 'qr_retail_price_centavos' => 1000]); $this->seedTenantData($tenantPSG, $password, 2); } private function seedTenantData(Tenant $tenant, string $password, int $lguIndex): void { // NO LGU Admin seeded as requested // 1. Infrastructure (3 Dumpsites, 5 DOPs) $barangay = Barangay::where('name', 'like', '%Poblacion%')->first() ?? Barangay::first(); $dumpsites = []; for ($i = 1; $i <= 3; $i++) { $dumpsites[] = Dumpsite::updateOrCreate( ['code' => "DS-{$tenant->code}-00{$i}"], [ 'name' => "{$tenant->code} Landfill {$i}", 'uuid' => (string) Str::uuid(), 'tenant_id' => $tenant->id, 'address_line' => "Zone {$i}", 'coordinates' => new Point(14.67 + ($i * 0.01), 121.08, 4326), ] ); } $dops = []; for ($i = 1; $i <= 5; $i++) { $dops[] = DropOffPoint::updateOrCreate( ['code' => "DOP-{$tenant->code}-00{$i}"], [ 'name' => "{$tenant->code} Plaza {$i}", 'uuid' => (string) Str::uuid(), 'tenant_id' => $tenant->id, '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::firstOrCreate(['user_id' => $resident->id]); Household::updateOrCreate( ['head_user_id' => $resident->id], [ 'uuid' => (string) Str::uuid(), 'tenant_id' => $tenant->id, 'address_line' => "Street {$i}", 'barangay_id' => $barangay->id, 'assigned_drop_off_point_id' => $dops[rand(0, 4)]->id, 'verification_status' => 'approved', 'verified_at' => now(), ] ); } // 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::updateOrCreate(['user_id' => $owner->id], ['business_name' => "Store {$i} - {$tenant->code}", 'verification_status' => 'approved', 'verified_at' => now()]); $store = PartnerStore::updateOrCreate( ['owner_user_id' => $owner->id], [ 'uuid' => (string) Str::uuid(), 'tenant_id' => $tenant->id, 'business_name' => "Store {$i} - {$tenant->code}", 'business_permit_number' => "BP-{$tenant->code}-{$i}", 'address_line' => "Market St {$i}", 'barangay_id' => $barangay->id, 'coordinates' => new Point(14.651 + ($i * 0.001), 121.061, 4326), 'status' => PartnerStore::STATUS_ACTIVE, ] ); $inventory = StoreInventory::firstOrCreate(['store_id' => $store->id], ['current_code_balance' => 500, 'prepaid_balance' => 0]); // Seed actual QR codes for the store only if they don't have any if ($store->qrCodes()->count() === 0) { $this->generator->generate( quantity: 500, purpose: QrCodeBatch::PURPOSE_STORE, targetStoreId: $store->id, notes: "Initial stock for {$store->business_name}" ); // Assign allocated codes to the store QrCode::whereIn('batch_id', QrCodeBatch::where('target_store_id', $store->id)->pluck('id')) ->update([ 'status' => Allocated::$name, 'assigned_to_store_id' => $store->id, 'allocated_at' => now(), ]); } } // 5. Some Initial Sales for testing foreach ($residents as $i => $resident) { $household = Household::where('head_user_id', $resident->id)->first(); $store = PartnerStore::where('tenant_id', $tenant->id)->first(); if ($household && $store && StoreSale::where('household_id', $household->id)->count() === 0) { // Record a sale for 10 codes StoreSale::create([ 'store_id' => $store->id, 'household_id' => $household->id, 'user_id' => $resident->id, 'quantity' => 10, 'retail_price_centavos' => 10000, 'commission_centavos' => 2000, 'sold_at' => now(), ]); // 5 codes are active $codes = QrCode::where('assigned_to_store_id', $store->id) ->where('status', Allocated::$name) ->limit(5) ->get(); foreach($codes as $code) { $code->update([ 'status' => Active::$name, 'assigned_to_household_id' => $household->id, 'assigned_to_store_id' => null, 'activated_at' => now(), ]); } // 5 codes are allocated (purchased but not yet active) $allocatedCodes = QrCode::where('assigned_to_store_id', $store->id) ->where('status', Allocated::$name) ->limit(5) ->get(); foreach($allocatedCodes as $code) { $code->update([ 'status' => Allocated::$name, 'assigned_to_household_id' => $household->id, 'assigned_to_store_id' => null, 'allocated_at' => now(), ]); } // Update store inventory $store->inventory()->decrement('current_code_balance', 10); } } // 6. Operations (5 Trucks, 5 Teams, 5 Routes, mixed Trips) for ($i = 0; $i < 5; $i++) { $truck = Truck::updateOrCreate( ['plate_number' => "VRD-{$tenant->code}-{$i}"], [ 'uuid' => (string) Str::uuid(), 'tenant_id' => $tenant->id, 'capacity_kg' => 3000, 'status' => Truck::STATUS_ACTIVE, ] ); $team = CollectionTeam::updateOrCreate( ['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::firstOrCreate(['team_id' => $team->id, 'user_id' => $helpers[$i]->id], ['role_in_team' => 'helper', 'assigned_from' => now()]); $route = CollectionRoute::updateOrCreate( ['name' => "Route {$tenant->code}-" . ($i + 1)], [ 'uuid' => (string) Str::uuid(), 'tenant_id' => $tenant->id, 'code' => "RT-{$tenant->code}-{$i}", 'default_team_id' => $team->id, 'status' => 'active', ] ); if ($route->stops()->count() === 0) { foreach ($dops as $idx => $dop) { RouteStop::create(['route_id' => $route->id, 'drop_off_point_id' => $dop->id, 'sequence' => $idx + 1]); } } if ($team->trips()->count() === 0) { $this->seedTrips($tenant, $route, $team, $truck, $dumpsites[0], $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::updateOrCreate(['user_id' => $user->id], ['license_number' => "L-{$tenant->code}-{$i}", 'verification_status' => 'approved', 'verified_at' => now()]); if ($role === 'scanner') ScannerProfile::updateOrCreate(['user_id' => $user->id], ['verification_status' => 'approved', 'verified_at' => now()]); if ($role === 'helper') HelperProfile::updateOrCreate(['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') . Str::random(4), '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, ]); } } } private function createStaff($email, $phone, $first, $last, $role, $tenant, $password): User { $u = User::updateOrCreate( ['email' => $email], [ 'phone' => $phone, 'password' => $password, 'first_name' => $first, 'last_name' => $last, 'role' => $role, 'status' => User::STATUS_ACTIVE, 'tenant_id' => $tenant ? $tenant->id : null, 'email_verified_at' => now(), ] ); $u->syncRoles([$role]); NotificationPreference::firstOrCreate(['user_id' => $u->id]); return $u; } }