From 0883d63abb9f5c8ba97bb2173f2598f820148124 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 1 May 2026 21:11:56 +0800 Subject: [PATCH] chore(backend): demo resident seeder for customer-web walkthrough MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three personas covering the main resident states the customer-web needs to render: juan@verde.local → approved household, DOP assigned, 10 free codes already allocated. Tests full dashboard, /codes, /pickups, /tracker, /settings. maria@verde.local → household submitted, verification_status = pending. Tests the awaiting-verification banner + admin-approval flow (set status to approved in /admin/households and the page auto-flips). pedro@verde.local → user only. Tests the /onboarding wizard from scratch. All passwords: "password". Phone + email pre-verified so login works without OTP loops. Idempotent — safe to re-run via: php artisan db:seed --class=DemoResidentSeeder Also generates a 200-code free QR batch when none exists, so the QrAllocator has something to draw from when seeding Juan. Hooked into DatabaseSeeder so `migrate:fresh --seed` includes it. Co-Authored-By: Claude Opus 4.7 (1M context) --- database/seeders/DatabaseSeeder.php | 1 + database/seeders/DemoResidentSeeder.php | 126 ++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 database/seeders/DemoResidentSeeder.php diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 8513312..0f9002b 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -14,6 +14,7 @@ class DatabaseSeeder extends Seeder SamplePsgcSeeder::class, SampleDropOffPointsSeeder::class, SampleDumpsitesSeeder::class, + DemoResidentSeeder::class, ]); } } diff --git a/database/seeders/DemoResidentSeeder.php b/database/seeders/DemoResidentSeeder.php new file mode 100644 index 0000000..564f871 --- /dev/null +++ b/database/seeders/DemoResidentSeeder.php @@ -0,0 +1,126 @@ +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; + } +}