chore(backend): demo resident seeder for customer-web walkthrough
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) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ class DatabaseSeeder extends Seeder
|
||||
SamplePsgcSeeder::class,
|
||||
SampleDropOffPointsSeeder::class,
|
||||
SampleDumpsitesSeeder::class,
|
||||
DemoResidentSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
126
database/seeders/DemoResidentSeeder.php
Normal file
126
database/seeders/DemoResidentSeeder.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Barangay;
|
||||
use App\Models\Household;
|
||||
use App\Models\NotificationPreference;
|
||||
use App\Models\QrCodeBatch;
|
||||
use App\Models\ResidentProfile;
|
||||
use App\Models\User;
|
||||
use App\Services\DropOff\DropOffPointFinder;
|
||||
use App\Services\Qr\BatchGenerator;
|
||||
use App\Services\Qr\QrAllocator;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use MatanYadaev\EloquentSpatial\Objects\Point;
|
||||
|
||||
/**
|
||||
* Demo resident accounts for the customer-web walkthrough. Idempotent —
|
||||
* safe to re-run. Skips QR allocation when a household already has codes.
|
||||
*
|
||||
* Logins (all password "password"):
|
||||
* juan@verde.local → approved household, 10 free codes (full dashboard)
|
||||
* maria@verde.local → pending household (status banner state)
|
||||
* pedro@verde.local → no household (lands on onboarding wizard)
|
||||
*/
|
||||
class DemoResidentSeeder extends Seeder
|
||||
{
|
||||
public function run(QrAllocator $allocator, BatchGenerator $batchGen, DropOffPointFinder $dops): void
|
||||
{
|
||||
$barangay = Barangay::first();
|
||||
if (! $barangay) {
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user