command->warn('No barangay seeded — run SamplePsgcSeeder first.'); return; } // Make sure a free QR batch exists so allocateFreeToHousehold has // codes to hand out. Idempotent — only creates if no free batch. if (! QrCodeBatch::where('purpose', QrCodeBatch::PURPOSE_FREE)->exists()) { $batchGen->generate(quantity: 200, purpose: QrCodeBatch::PURPOSE_FREE); $this->command->info('Generated free QR batch (200 codes).'); } // Use the barangay centroid as the demo address pin (always inside // the boundary so geo-resolve and DOP-finder both succeed). $lat = $barangay->centroid?->latitude ?? 14.6760; $lng = $barangay->centroid?->longitude ?? 121.0437; // 1) Juan — approved household + 10 free codes $juan = $this->createResident('juan@verde.local', '+639170000001', 'Juan', 'Dela Cruz'); $this->createHousehold($juan, $barangay, $lat, $lng, 'approved', $allocator, $dops); // 2) Maria — pending household $maria = $this->createResident('maria@verde.local', '+639170000002', 'Maria', 'Santos'); $this->createHousehold($maria, $barangay, $lat + 0.001, $lng + 0.001, 'pending', null, $dops); // 3) Pedro — no household (tests onboarding flow) $this->createResident('pedro@verde.local', '+639170000003', 'Pedro', 'Reyes'); $this->command->info('Demo residents seeded:'); $this->command->info(' juan@verde.local → approved + 10 codes'); $this->command->info(' maria@verde.local → pending review'); $this->command->info(' pedro@verde.local → fresh, no household yet'); $this->command->info(' (password for all: "password")'); } private function createResident(string $email, string $phone, string $first, string $last): User { $user = User::firstOrCreate( ['email' => $email], [ 'phone' => $phone, 'password' => Hash::make('password'), 'first_name' => $first, 'last_name' => $last, 'role' => User::ROLE_RESIDENT, 'status' => User::STATUS_ACTIVE, 'email_verified_at' => now(), 'phone_verified_at' => now(), 'preferred_language' => 'en', ], ); $user->syncRoles([User::ROLE_RESIDENT]); ResidentProfile::firstOrCreate(['user_id' => $user->id]); NotificationPreference::firstOrCreate(['user_id' => $user->id]); return $user; } private function createHousehold( User $head, Barangay $barangay, float $lat, float $lng, string $status, ?QrAllocator $allocator, DropOffPointFinder $dops, ): Household { // Auto-pick the nearest active DOP (matches the production flow // in HouseholdController::store). $dop = $dops->nearest($lat, $lng); $household = Household::firstOrCreate( ['head_user_id' => $head->id], [ 'barangay_id' => $barangay->id, 'address_line' => '123 Demo Street, '.$barangay->name, 'coordinates' => new Point($lat, $lng, 4326), 'household_size' => 4, 'verification_status' => $status, 'verified_at' => $status === 'approved' ? now() : null, 'proof_of_residency_path' => 'demo/proof.jpg', 'assigned_drop_off_point_id' => $dop?->id, ], ); if ($status === 'approved' && $allocator) { // Allocator is idempotent — skips when codes already exist. $allocator->allocateFreeToHousehold($household); } return $household; } }