From 5d8a749cb3343443653bcc6f915a9e82937bd4fa Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Thu, 16 Apr 2026 20:42:47 +0200 Subject: [PATCH] fix: seeder creates User accounts for approved/no_show persons The DevSeeder was creating approved persons without linked User accounts, which can't happen in production (approval flow always creates accounts). Added linkUsersToApprovedPersons() helper that runs after person creation in each event seeder, creating User accounts via firstOrCreate for approved and no_show persons that lack user_id. Also added safeguard tests verifying the approval flow creates user accounts and reuses existing ones. Co-Authored-By: Claude Opus 4.6 (1M context) --- api/database/seeders/DevSeeder.php | 39 +++++++++++++++++++++- api/tests/Feature/Person/PersonTest.php | 43 +++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/api/database/seeders/DevSeeder.php b/api/database/seeders/DevSeeder.php index b99fb9df..2433ab4c 100644 --- a/api/database/seeders/DevSeeder.php +++ b/api/database/seeders/DevSeeder.php @@ -586,8 +586,9 @@ class DevSeeder extends Seeder // Person with unique email (no match expected) Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Unique', 'last_name' => 'Persoon', 'email' => 'unique.persoon@nowhere.test', 'phone' => '+31612345044', 'status' => 'pending']); + $linked = $this->linkUsersToApprovedPersons($festival); $personCount = Person::where('event_id', $festival->id)->count(); - $this->command->info(" {$personCount} persons created"); + $this->command->info(" {$personCount} persons created ({$linked} user accounts linked)"); // ── Named shift assignments (22) ── @@ -975,6 +976,8 @@ class DevSeeder extends Seeder Person::factory()->count(3)->create(['event_id' => $braderie->id, 'crowd_type_id' => $vol, 'status' => 'pending']); Person::factory()->count(2)->create(['event_id' => $braderie->id, 'crowd_type_id' => $vol, 'status' => 'applied']); + $this->linkUsersToApprovedPersons($braderie); + $this->command->info(' Braderie Dorpstown 2026 complete'); }); } @@ -1094,6 +1097,8 @@ class DevSeeder extends Seeder Person::factory()->count(2)->create(['event_id' => $ijsbaan->id, 'crowd_type_id' => $crewType, 'status' => 'pending']); Person::factory()->count(5)->approved()->create(['event_id' => $ijsbaan->id, 'crowd_type_id' => $guestType]); + $this->linkUsersToApprovedPersons($ijsbaan); + // ── Shift assignments (~80) ── $openShifts = collect($allShifts)->filter(fn (Shift $shift) => $shift->status === 'open' && $shift->slots_open_for_claiming > 0); @@ -1241,6 +1246,8 @@ class DevSeeder extends Seeder // 2 suppliers Person::factory()->count(2)->approved()->create(['event_id' => $koningsdag->id, 'crowd_type_id' => $supplierType]); + $this->linkUsersToApprovedPersons($koningsdag); + // ── Shift assignments (~150) ── // 120 completed + 12 cancelled + 8 no-show + 5 rejected + 5 pending @@ -1348,6 +1355,36 @@ class DevSeeder extends Seeder // Helpers // ========================================================================= + /** + * Create User accounts for approved/no_show persons that lack one. + * Mirrors the production approval flow (PersonController::approve). + */ + private function linkUsersToApprovedPersons(Event $event): int + { + $linked = 0; + + Person::withoutGlobalScopes() + ->where('event_id', $event->id) + ->whereIn('status', ['approved', 'no_show']) + ->whereNull('user_id') + ->each(function (Person $person) use (&$linked): void { + $user = User::firstOrCreate( + ['email' => strtolower($person->email)], + [ + 'first_name' => $person->first_name, + 'last_name' => $person->last_name, + 'password' => Hash::make('password'), + ] + ); + + $person->user_id = $user->id; + $person->save(); + $linked++; + }); + + return $linked; + } + private function createCrowdList( Event $event, string $name, diff --git a/api/tests/Feature/Person/PersonTest.php b/api/tests/Feature/Person/PersonTest.php index a6e0a291..60572577 100644 --- a/api/tests/Feature/Person/PersonTest.php +++ b/api/tests/Feature/Person/PersonTest.php @@ -196,4 +196,47 @@ class PersonTest extends TestCase $response->assertUnauthorized(); } + + public function test_approve_creates_user_account_for_person(): void + { + $person = Person::factory()->create([ + 'event_id' => $this->event->id, + 'crowd_type_id' => $this->crowdType->id, + 'status' => 'pending', + 'email' => 'volunteer@example.com', + ]); + + $this->assertNull($person->user_id); + + Sanctum::actingAs($this->orgAdmin); + + $response = $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons/{$person->id}/approve"); + + $response->assertOk(); + $person->refresh(); + + $this->assertNotNull($person->user_id); + $this->assertDatabaseHas('users', ['email' => 'volunteer@example.com']); + } + + public function test_approve_reuses_existing_user_account(): void + { + $existingUser = User::factory()->create(['email' => 'existing@example.com']); + + $person = Person::factory()->create([ + 'event_id' => $this->event->id, + 'crowd_type_id' => $this->crowdType->id, + 'status' => 'pending', + 'email' => 'existing@example.com', + ]); + + Sanctum::actingAs($this->orgAdmin); + + $this->postJson("/api/v1/organisations/{$this->organisation->id}/events/{$this->event->id}/persons/{$person->id}/approve") + ->assertOk(); + + $person->refresh(); + + $this->assertEquals($existingUser->id, $person->user_id); + } }