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) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 20:42:47 +02:00
parent ef7c482b4a
commit 5d8a749cb3
2 changed files with 81 additions and 1 deletions

View File

@@ -586,8 +586,9 @@ class DevSeeder extends Seeder
// Person with unique email (no match expected) // 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']); 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(); $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) ── // ── 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(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']); 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'); $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(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]); Person::factory()->count(5)->approved()->create(['event_id' => $ijsbaan->id, 'crowd_type_id' => $guestType]);
$this->linkUsersToApprovedPersons($ijsbaan);
// ── Shift assignments (~80) ── // ── Shift assignments (~80) ──
$openShifts = collect($allShifts)->filter(fn (Shift $shift) => $shift->status === 'open' && $shift->slots_open_for_claiming > 0); $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 // 2 suppliers
Person::factory()->count(2)->approved()->create(['event_id' => $koningsdag->id, 'crowd_type_id' => $supplierType]); Person::factory()->count(2)->approved()->create(['event_id' => $koningsdag->id, 'crowd_type_id' => $supplierType]);
$this->linkUsersToApprovedPersons($koningsdag);
// ── Shift assignments (~150) ── // ── Shift assignments (~150) ──
// 120 completed + 12 cancelled + 8 no-show + 5 rejected + 5 pending // 120 completed + 12 cancelled + 8 no-show + 5 rejected + 5 pending
@@ -1348,6 +1355,36 @@ class DevSeeder extends Seeder
// Helpers // 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( private function createCrowdList(
Event $event, Event $event,
string $name, string $name,

View File

@@ -196,4 +196,47 @@ class PersonTest extends TestCase
$response->assertUnauthorized(); $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);
}
} }