diff --git a/api/database/seeders/DevSeeder.php b/api/database/seeders/DevSeeder.php index 05d09f30..290f990b 100644 --- a/api/database/seeders/DevSeeder.php +++ b/api/database/seeders/DevSeeder.php @@ -4,127 +4,170 @@ declare(strict_types=1); namespace Database\Seeders; +use App\Enums\CrowdListType; +use App\Enums\ShiftAssignmentStatus; +use App\Models\Company; +use App\Models\CrowdList; use App\Models\CrowdType; use App\Models\Event; use App\Models\FestivalSection; +use App\Models\Location; use App\Models\Organisation; use App\Models\Person; +use App\Models\Shift; +use App\Models\ShiftAssignment; use App\Models\TimeSlot; use App\Models\User; +use App\Models\UserOrganisationTag; +use App\Models\VolunteerAvailability; use Illuminate\Database\Seeder; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; class DevSeeder extends Seeder { + private Organisation $org; + + /** @var array */ + private array $users = []; + + /** @var array */ + private array $crowdTypes = []; + + /** @var array */ + private array $companies = []; + + /** @var array */ + private array $personTags = []; + public function run(): void { $this->call(RoleSeeder::class); - // 1. Super Admin - $admin = User::firstOrCreate( - ['email' => 'admin@crewli.test'], - [ - 'name' => 'Dev Admin', - 'password' => Hash::make('password'), - ], - ); - $admin->assignRole('super_admin'); + $this->seedOrganisation(); + $this->seedEchtFeesten(); + $this->seedIJsbaan(); + $this->seedKoningsdag(); + $this->seedNachtVanDeKaap(); + } - // Test organisation - $org = Organisation::firstOrCreate( - ['slug' => 'test-festival-bv'], - [ - 'name' => 'Test Festival BV', + // ========================================================================= + // Organisation: Stichting Feestfabriek + // ========================================================================= + + private function seedOrganisation(): void + { + DB::transaction(function (): void { + $this->command->info('Seeding organisation: Stichting Feestfabriek...'); + + $this->org = Organisation::create([ + 'name' => 'Stichting Feestfabriek', + 'slug' => 'stichting-feestfabriek', 'billing_status' => 'active', 'settings' => [], - ], - ); + ]); - // 2. Org Admin - $orgAdmin = User::firstOrCreate( - ['email' => 'orgadmin@crewli.test'], - [ - 'name' => 'Org Admin', - 'password' => Hash::make('password'), - ], - ); - if (!$org->users()->where('user_id', $orgAdmin->id)->exists()) { - $org->users()->attach($orgAdmin, ['role' => 'org_admin']); - } + // ── Users (8) ── - // Second test organisation (for testing the organisation switcher) - $org2 = Organisation::firstOrCreate( - ['slug' => 'zomerfest-nederland'], - [ - 'name' => 'Zomerfest Nederland', - 'billing_status' => 'trial', - 'settings' => [], - ], - ); + $usersData = [ + ['email' => 'admin@crewli.test', 'name' => 'Super Admin', 'app_role' => 'super_admin', 'org_role' => 'org_admin'], + ['email' => 'bert@feestfabriek.nl', 'name' => 'Bert Hausmans', 'org_role' => 'org_admin'], + ['email' => 'lisa@feestfabriek.nl', 'name' => 'Lisa van den Berg', 'org_role' => 'org_member'], + ['email' => 'ahmed@feestfabriek.nl', 'name' => 'Ahmed Yilmaz', 'org_role' => 'org_member'], + ['email' => 'sara@feestfabriek.nl', 'name' => 'Sara de Groot', 'org_role' => 'org_member'], + ['email' => 'tom@feestfabriek.nl', 'name' => 'Tom Visser', 'org_role' => 'org_member'], + ['email' => 'nina@feestfabriek.nl', 'name' => 'Nina Jansen', 'org_role' => 'org_member'], + ['email' => 'mark@feestfabriek.nl', 'name' => 'Mark de Boer', 'org_role' => 'org_member'], + ]; - // Attach admin and orgAdmin to second org too - if (!$org2->users()->where('user_id', $admin->id)->exists()) { - $org2->users()->attach($admin, ['role' => 'org_admin']); - } - if (!$org2->users()->where('user_id', $orgAdmin->id)->exists()) { - $org2->users()->attach($orgAdmin, ['role' => 'org_member']); - } + foreach ($usersData as $data) { + $user = User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'password' => Hash::make('password'), + ]); - // 3. Org Member - $member = User::firstOrCreate( - ['email' => 'crew@crewli.test'], - [ - 'name' => 'Crew Member', - 'password' => Hash::make('password'), - ], - ); - if (!$org->users()->where('user_id', $member->id)->exists()) { - $org->users()->attach($member, ['role' => 'org_member']); - } + if (isset($data['app_role'])) { + $user->assignRole($data['app_role']); + } - // 4. Default Crowd Types for Test Festival BV - $crowdTypes = [ - ['name' => 'Crew', 'system_type' => 'CREW', 'color' => '#3b82f6'], - ['name' => 'Vrijwilliger', 'system_type' => 'VOLUNTEER', 'color' => '#10b981'], - ['name' => 'Artiest', 'system_type' => 'ARTIST', 'color' => '#8b5cf6'], - ['name' => 'Gast', 'system_type' => 'GUEST', 'color' => '#f59e0b'], - ['name' => 'Pers', 'system_type' => 'PRESS', 'color' => '#6366f1'], - ]; + $this->org->users()->attach($user, ['role' => $data['org_role']]); + $this->users[$data['email']] = $user; + } - foreach ($crowdTypes as $ct) { - CrowdType::firstOrCreate( - [ - 'organisation_id' => $org->id, - 'system_type' => $ct['system_type'], - ], - [ - 'name' => $ct['name'], - 'color' => $ct['color'], - ], - ); - } + // ── Companies (6) ── - // 5. Flat event (backward compatible single event) - Event::firstOrCreate( - ['organisation_id' => $org->id, 'slug' => 'test-event-01'], - [ - 'name' => 'Test Event 01', - 'start_date' => '2026-08-15', - 'end_date' => '2026-08-15', - 'status' => 'draft', - 'event_type' => 'event', - 'parent_event_id' => null, - ], - ); + $companiesData = [ + ['name' => 'Tap & Co Horeca', 'type' => 'supplier', 'contact_name' => 'Jan Tapper', 'contact_email' => 'jan@tapco.nl', 'contact_phone' => '+31612340001'], + ['name' => 'SecureEvent BV', 'type' => 'supplier', 'contact_name' => 'Klaas Veilig', 'contact_email' => 'klaas@secureevent.nl', 'contact_phone' => '+31612340002'], + ['name' => 'Podiumtechniek Rijnmond', 'type' => 'supplier', 'contact_name' => 'Pieter Geluid', 'contact_email' => 'pieter@podiumtechniek.nl', 'contact_phone' => '+31612340003'], + ['name' => 'Brouwerij De Schelde', 'type' => 'partner', 'contact_name' => 'Eva Brouwer', 'contact_email' => 'eva@brouwerijdeschelde.nl', 'contact_phone' => '+31612340004'], + ['name' => 'Van Dijk Catering', 'type' => 'supplier', 'contact_name' => 'Maria van Dijk', 'contact_email' => 'maria@vandijkcatering.nl', 'contact_phone' => '+31612340005'], + ['name' => 'Rotterdam Festivals', 'type' => 'agency', 'contact_name' => 'Femke de Wit', 'contact_email' => 'femke@rotterdamfestivals.nl', 'contact_phone' => '+31612340006'], + ]; - // ============================================= - // 6. Festival: Echt Feesten 2026 - // ============================================= + foreach ($companiesData as $data) { + $company = Company::create(['organisation_id' => $this->org->id, ...$data]); + $this->companies[$data['name']] = $company; + } - $festival = Event::firstOrCreate( - ['organisation_id' => $org->id, 'slug' => 'echt-feesten-2026'], - [ + // ── Crowd Types (7) ── + + $crowdTypesData = [ + ['name' => 'Vrijwilliger', 'system_type' => 'VOLUNTEER', 'color' => '#4CAF50', 'icon' => 'tabler-heart-handshake'], + ['name' => 'Medewerker', 'system_type' => 'CREW', 'color' => '#2196F3', 'icon' => 'tabler-id-badge-2'], + ['name' => 'Artiest', 'system_type' => 'ARTIST', 'color' => '#9C27B0', 'icon' => 'tabler-microphone-2'], + ['name' => 'Pers', 'system_type' => 'PRESS', 'color' => '#FF9800', 'icon' => 'tabler-camera'], + ['name' => 'Gast', 'system_type' => 'GUEST', 'color' => '#607D8B', 'icon' => 'tabler-ticket'], + ['name' => 'Leverancier', 'system_type' => 'SUPPLIER', 'color' => '#795548', 'icon' => 'tabler-truck-delivery'], + ['name' => 'Partner', 'system_type' => 'PARTNER', 'color' => '#FFC107', 'icon' => 'tabler-handshake'], + ]; + + foreach ($crowdTypesData as $data) { + $ct = CrowdType::create(['organisation_id' => $this->org->id, ...$data, 'is_active' => true]); + $this->crowdTypes[$data['system_type']] = $ct; + } + + // ── Person Tags (10) ── + + $personTagsData = [ + ['name' => 'Tapper', 'category' => 'Horeca', 'icon' => 'tabler-beer', 'color' => '#FF9800', 'is_active' => true, 'sort_order' => 1], + ['name' => 'Barista', 'category' => 'Horeca', 'icon' => 'tabler-coffee', 'color' => '#795548', 'is_active' => true, 'sort_order' => 2], + ['name' => 'EHBO', 'category' => 'Veiligheid', 'icon' => 'tabler-first-aid-kit', 'color' => '#F44336', 'is_active' => true, 'sort_order' => 3], + ['name' => 'BHV', 'category' => 'Veiligheid', 'icon' => 'tabler-fire-extinguisher', 'color' => '#E91E63', 'is_active' => true, 'sort_order' => 4], + ['name' => 'Rijbewijs B', 'category' => 'Logistiek', 'icon' => 'tabler-car', 'color' => '#607D8B', 'is_active' => true, 'sort_order' => 5], + ['name' => 'Heftruck', 'category' => 'Logistiek', 'icon' => 'tabler-forklift', 'color' => '#9E9E9E', 'is_active' => true, 'sort_order' => 6], + ['name' => 'Duits', 'category' => 'Taal', 'icon' => 'tabler-flag', 'color' => '#000000', 'is_active' => true, 'sort_order' => 7], + ['name' => 'Engels', 'category' => 'Taal', 'icon' => 'tabler-flag', 'color' => '#1565C0', 'is_active' => true, 'sort_order' => 8], + ['name' => 'Podiumervaring', 'category' => 'Techniek', 'icon' => 'tabler-speakerphone', 'color' => '#9C27B0', 'is_active' => true, 'sort_order' => 9], + ['name' => 'Teamleider', 'category' => 'Rol', 'icon' => 'tabler-crown', 'color' => '#FFC107', 'is_active' => false, 'sort_order' => 10], + ]; + + foreach ($personTagsData as $data) { + $tag = $this->org->personTags()->create($data); + $this->personTags[$data['name']] = $tag; + } + + $this->command->info(' Organisation, 8 users, 6 companies, 7 crowd types, 10 person tags created'); + }); + } + + // ========================================================================= + // Event 1: Echt Feesten 2026 (festival, registration_open, 150 persons) + // ========================================================================= + + private function seedEchtFeesten(): void + { + DB::transaction(function (): void { + $this->command->info('Seeding Echt Feesten 2026...'); + + // ── Event hierarchy ── + + $festival = Event::create([ + 'organisation_id' => $this->org->id, 'name' => 'Echt Feesten 2026', + 'slug' => 'echt-feesten-2026', 'start_date' => '2026-07-10', 'end_date' => '2026-07-12', 'timezone' => 'Europe/Amsterdam', @@ -132,177 +175,1010 @@ class DevSeeder extends Seeder 'event_type' => 'festival', 'event_type_label' => 'Festival', 'sub_event_label' => 'Programmaonderdeel', - 'parent_event_id' => null, - ], - ); + ]); - // --- Sub-events --- - - $vrijdag = Event::firstOrCreate( - ['organisation_id' => $org->id, 'slug' => 'echt-feesten-2026-vrijdag'], - [ + $vrijdag = Event::create([ + 'organisation_id' => $this->org->id, + 'parent_event_id' => $festival->id, 'name' => 'Vrijdag', + 'slug' => 'echt-feesten-2026-vrijdag', 'start_date' => '2026-07-10', 'end_date' => '2026-07-10', 'timezone' => 'Europe/Amsterdam', 'status' => 'registration_open', 'event_type' => 'event', - 'parent_event_id' => $festival->id, - ], - ); + ]); - Event::firstOrCreate( - ['organisation_id' => $org->id, 'slug' => 'echt-feesten-2026-zaterdag'], - [ + $zaterdag = Event::create([ + 'organisation_id' => $this->org->id, + 'parent_event_id' => $festival->id, 'name' => 'Zaterdag', + 'slug' => 'echt-feesten-2026-zaterdag', 'start_date' => '2026-07-11', 'end_date' => '2026-07-11', 'timezone' => 'Europe/Amsterdam', 'status' => 'registration_open', 'event_type' => 'event', - 'parent_event_id' => $festival->id, - ], - ); + ]); - Event::firstOrCreate( - ['organisation_id' => $org->id, 'slug' => 'echt-feesten-2026-zondag'], - [ + $zondag = Event::create([ + 'organisation_id' => $this->org->id, + 'parent_event_id' => $festival->id, 'name' => 'Zondag', + 'slug' => 'echt-feesten-2026-zondag', 'start_date' => '2026-07-12', 'end_date' => '2026-07-12', 'timezone' => 'Europe/Amsterdam', - 'status' => 'registration_open', + 'status' => 'published', 'event_type' => 'event', - 'parent_event_id' => $festival->id, - ], - ); + ]); - // --- Festival-level sections (on the parent) --- + $subEvents = ['vrijdag' => $vrijdag, 'zaterdag' => $zaterdag, 'zondag' => $zondag]; - FestivalSection::firstOrCreate( - ['event_id' => $festival->id, 'name' => 'EHBO'], - [ - 'type' => 'cross_event', - 'category' => 'Veiligheid', - 'icon' => 'tabler-first-aid-kit', - 'sort_order' => 1, - 'responder_self_checkin' => true, - 'crew_auto_accepts' => false, - ], - ); + // Attach event-level user roles + $eventRoles = [ + 'lisa@feestfabriek.nl' => 'event_manager', + 'ahmed@feestfabriek.nl' => 'event_manager', + 'sara@feestfabriek.nl' => 'volunteer_coordinator', + 'tom@feestfabriek.nl' => 'staff_coordinator', + 'nina@feestfabriek.nl' => 'artist_manager', + ]; + foreach ($eventRoles as $email => $role) { + $festival->users()->attach($this->users[$email], ['role' => $role]); + } - FestivalSection::firstOrCreate( - ['event_id' => $festival->id, 'name' => 'Nachtsecurity'], - [ - 'type' => 'standard', - 'category' => 'Veiligheid', - 'icon' => 'tabler-shield', - 'sort_order' => 2, - 'responder_self_checkin' => true, - 'crew_auto_accepts' => false, - ], - ); + // ── Locations (5, on parent) ── - // --- Sub-event sections (on Vrijdag) --- + $locations = []; + $locationsData = [ + 'hoofdpodium' => ['name' => 'Hoofdpodium', 'address' => 'Parkweg 1, Echt', 'lat' => 51.1039, 'lng' => 5.8718], + 'theatertent' => ['name' => 'Theatertent', 'address' => 'Parkweg 1A, Echt', 'lat' => 51.1041, 'lng' => 5.8725], + 'ingang' => ['name' => 'Festivalterrein Ingang', 'address' => 'Parkweg 2, Echt', 'lat' => 51.1035, 'lng' => 5.8710], + 'backstage' => ['name' => 'Backstage Area', 'address' => 'Parkweg 1B, Echt', 'lat' => 51.1043, 'lng' => 5.8720], + 'camping' => ['name' => 'Camping', 'address' => 'Kampeerweg 5, Echt', 'lat' => 51.1050, 'lng' => 5.8740], + ]; + foreach ($locationsData as $key => $data) { + $locations[$key] = Location::create(['event_id' => $festival->id, ...$data]); + } - FestivalSection::firstOrCreate( - ['event_id' => $vrijdag->id, 'name' => 'Hoofdpodium Bar'], - [ - 'type' => 'standard', - 'category' => 'Bar', - 'icon' => 'tabler-beer', - 'sort_order' => 1, - 'responder_self_checkin' => true, - 'crew_auto_accepts' => false, - ], - ); + // ── Festival-level sections (4) ── - FestivalSection::firstOrCreate( - ['event_id' => $vrijdag->id, 'name' => 'Backstage'], - [ - 'type' => 'standard', - 'category' => 'Hospitality', - 'icon' => 'tabler-armchair', - 'sort_order' => 2, - 'responder_self_checkin' => true, - 'crew_auto_accepts' => false, - ], - ); + $ehbo = FestivalSection::create([ + 'event_id' => $festival->id, 'name' => 'EHBO', 'type' => 'cross_event', + 'category' => 'Veiligheid', 'icon' => 'tabler-first-aid-kit', 'sort_order' => 1, + 'responder_self_checkin' => true, 'crew_auto_accepts' => false, + ]); - // --- Festival-level time slots (on the parent) --- + $nachtsecurity = FestivalSection::create([ + 'event_id' => $festival->id, 'name' => 'Nachtsecurity', 'type' => 'standard', + 'category' => 'Veiligheid', 'icon' => 'tabler-shield', 'sort_order' => 2, + 'responder_self_checkin' => true, 'crew_auto_accepts' => false, + ]); - TimeSlot::firstOrCreate( - ['event_id' => $festival->id, 'name' => 'Opbouw'], - [ - 'date' => '2026-07-09', - 'start_time' => '08:00:00', - 'end_time' => '18:00:00', - 'duration_hours' => 10.00, - 'person_type' => 'CREW', - ], - ); + $terreinploeg = FestivalSection::create([ + 'event_id' => $festival->id, 'name' => 'Terreinploeg', 'type' => 'standard', + 'category' => 'Productie', 'icon' => 'tabler-shovel', 'sort_order' => 3, + 'responder_self_checkin' => true, 'crew_auto_accepts' => true, + ]); - TimeSlot::firstOrCreate( - ['event_id' => $festival->id, 'name' => 'Afbraak'], - [ - 'date' => '2026-07-13', - 'start_time' => '08:00:00', - 'end_time' => '18:00:00', - 'duration_hours' => 10.00, - 'person_type' => 'CREW', - ], - ); + $accreditatiebalie = FestivalSection::create([ + 'event_id' => $festival->id, 'name' => 'Accreditatiebalie', 'type' => 'cross_event', + 'category' => 'Ontvangst', 'icon' => 'tabler-id-badge', 'sort_order' => 4, + 'responder_self_checkin' => true, 'crew_auto_accepts' => true, + ]); - // --- Sub-event time slots (on Vrijdag) --- + // ── Sub-event sections (5 per sub-event) ── - TimeSlot::firstOrCreate( - ['event_id' => $vrijdag->id, 'name' => 'Vrijdag Avond'], - [ - 'date' => '2026-07-10', - 'start_time' => '18:00:00', - 'end_time' => '02:00:00', - 'duration_hours' => 8.00, - 'person_type' => 'VOLUNTEER', - ], - ); + $sectionDefs = [ + 'hoofdbar' => ['name' => 'Hoofdpodium Bar', 'category' => 'Bar', 'icon' => 'tabler-beer', 'crew_auto_accepts' => true], + 'theaterbar' => ['name' => 'Theatertent Bar', 'category' => 'Bar', 'icon' => 'tabler-beer', 'crew_auto_accepts' => true], + 'hospitality' => ['name' => 'Backstage Hospitality', 'category' => 'Hospitality', 'icon' => 'tabler-armchair', 'crew_auto_accepts' => false], + 'podiumtechniek' => ['name' => 'Podiumtechniek', 'category' => 'Techniek', 'icon' => 'tabler-speakerphone', 'crew_auto_accepts' => false], + 'ingang' => ['name' => 'Ingang & Tickets', 'category' => 'Ontvangst', 'icon' => 'tabler-ticket', 'crew_auto_accepts' => true], + ]; - TimeSlot::firstOrCreate( - ['event_id' => $vrijdag->id, 'name' => 'Vrijdag Nacht'], - [ - 'date' => '2026-07-10', - 'start_time' => '22:00:00', - 'end_time' => '04:00:00', - 'duration_hours' => 6.00, - 'person_type' => 'CREW', - ], - ); + $sections = []; + foreach ($subEvents as $dayKey => $subEvent) { + $order = 1; + foreach ($sectionDefs as $key => $def) { + $sections["{$dayKey}_{$key}"] = FestivalSection::create([ + 'event_id' => $subEvent->id, + 'name' => $def['name'], + 'type' => 'standard', + 'category' => $def['category'], + 'icon' => $def['icon'], + 'sort_order' => $order++, + 'responder_self_checkin' => true, + 'crew_auto_accepts' => $def['crew_auto_accepts'], + ]); + } + } - // --- Festival-level persons (5 volunteers on the parent) --- + // ── Festival-level time slots (5) ── - $volunteerType = CrowdType::where('organisation_id', $org->id) - ->where('system_type', 'VOLUNTEER') - ->first(); + $fSlots = []; + $fSlots['opbouw1'] = TimeSlot::create(['event_id' => $festival->id, 'name' => 'Opbouw Dag 1', 'person_type' => 'CREW', 'date' => '2026-07-09', 'start_time' => '08:00', 'end_time' => '18:00', 'duration_hours' => 10.00]); + $fSlots['opbouw2'] = TimeSlot::create(['event_id' => $festival->id, 'name' => 'Opbouw Dag 2', 'person_type' => 'CREW', 'date' => '2026-07-10', 'start_time' => '08:00', 'end_time' => '14:00', 'duration_hours' => 6.00]); + $fSlots['nacht_vr'] = TimeSlot::create(['event_id' => $festival->id, 'name' => 'Nachtbewaking Vr→Za', 'person_type' => 'CREW', 'date' => '2026-07-10', 'start_time' => '02:00', 'end_time' => '08:00', 'duration_hours' => 6.00]); + $fSlots['nacht_za'] = TimeSlot::create(['event_id' => $festival->id, 'name' => 'Nachtbewaking Za→Zo', 'person_type' => 'CREW', 'date' => '2026-07-11', 'start_time' => '02:00', 'end_time' => '08:00', 'duration_hours' => 6.00]); + $fSlots['afbraak'] = TimeSlot::create(['event_id' => $festival->id, 'name' => 'Afbraak', 'person_type' => 'CREW', 'date' => '2026-07-13', 'start_time' => '08:00', 'end_time' => '18:00', 'duration_hours' => 10.00]); - $festivalPersons = [ - ['name' => 'Sanne de Vries', 'email' => 'sanne.devries@example.nl', 'phone' => '+31612345001'], - ['name' => 'Pieter Jansen', 'email' => 'pieter.jansen@example.nl', 'phone' => '+31612345002'], - ['name' => 'Marieke van den Berg', 'email' => 'marieke.vandenberg@example.nl', 'phone' => '+31612345003'], - ['name' => 'Thijs Bakker', 'email' => 'thijs.bakker@example.nl', 'phone' => '+31612345004'], - // Uses the seeded orgAdmin email to test identity matching later - ['name' => 'Org Admin Volunteer', 'email' => 'orgadmin@crewli.test', 'phone' => '+31612345005'], - ]; + // ── Sub-event time slots ── - foreach ($festivalPersons as $personData) { - Person::firstOrCreate( - ['event_id' => $festival->id, 'email' => $personData['email']], - [ - 'crowd_type_id' => $volunteerType->id, - 'name' => $personData['name'], - 'phone' => $personData['phone'], - 'status' => 'approved', - 'is_blacklisted' => false, - ], + $ts = []; + $ts['vr_early'] = TimeSlot::create(['event_id' => $vrijdag->id, 'name' => 'Vrijdag Early', 'person_type' => 'VOLUNTEER', 'date' => '2026-07-10', 'start_time' => '14:00', 'end_time' => '18:00', 'duration_hours' => 4.00]); + $ts['vr_avond'] = TimeSlot::create(['event_id' => $vrijdag->id, 'name' => 'Vrijdag Avond', 'person_type' => 'VOLUNTEER', 'date' => '2026-07-10', 'start_time' => '18:00', 'end_time' => '02:00', 'duration_hours' => 8.00]); + $ts['vr_pers'] = TimeSlot::create(['event_id' => $vrijdag->id, 'name' => 'Vrijdag Pers', 'person_type' => 'PRESS', 'date' => '2026-07-10', 'start_time' => '16:00', 'end_time' => '22:00', 'duration_hours' => 6.00]); + $ts['za_dag'] = TimeSlot::create(['event_id' => $zaterdag->id, 'name' => 'Zaterdag Dag', 'person_type' => 'VOLUNTEER', 'date' => '2026-07-11', 'start_time' => '10:00', 'end_time' => '18:00', 'duration_hours' => 8.00]); + $ts['za_avond'] = TimeSlot::create(['event_id' => $zaterdag->id, 'name' => 'Zaterdag Avond', 'person_type' => 'VOLUNTEER', 'date' => '2026-07-11', 'start_time' => '18:00', 'end_time' => '02:00', 'duration_hours' => 8.00]); + $ts['za_crew'] = TimeSlot::create(['event_id' => $zaterdag->id, 'name' => 'Zaterdag Crew', 'person_type' => 'CREW', 'date' => '2026-07-11', 'start_time' => '08:00', 'end_time' => '20:00', 'duration_hours' => 12.00]); + $ts['zo_dag'] = TimeSlot::create(['event_id' => $zondag->id, 'name' => 'Zondag Dag', 'person_type' => 'VOLUNTEER', 'date' => '2026-07-12', 'start_time' => '10:00', 'end_time' => '18:00', 'duration_hours' => 8.00]); + $ts['zo_avond'] = TimeSlot::create(['event_id' => $zondag->id, 'name' => 'Zondag Avond', 'person_type' => 'VOLUNTEER', 'date' => '2026-07-12', 'start_time' => '18:00', 'end_time' => '22:00', 'duration_hours' => 4.00]); + + // ── Shifts (~55) ── + + $allShifts = []; + $s = []; // named shifts for explicit assignments + + // EHBO: 6 shifts across volunteer time slots + foreach (['vr_early', 'vr_avond', 'za_dag', 'za_avond', 'zo_dag', 'zo_avond'] as $key) { + $isFull = $key === 'za_dag'; + $shift = Shift::create([ + 'festival_section_id' => $ehbo->id, + 'time_slot_id' => $ts[$key]->id, + 'title' => 'EHBO Post', + 'slots_total' => $isFull ? 4 : 3, + 'slots_open_for_claiming' => $isFull ? 0 : 2, + 'status' => $isFull ? 'full' : 'open', + ]); + $allShifts[] = $shift; + $s["ehbo_{$key}"] = $shift; + } + + // Nachtsecurity: 2 shifts + foreach (['nacht_vr', 'nacht_za'] as $key) { + $shift = Shift::create([ + 'festival_section_id' => $nachtsecurity->id, + 'time_slot_id' => $fSlots[$key]->id, + 'title' => 'Nachtbewaker', + 'slots_total' => 6, + 'slots_open_for_claiming' => 0, + 'status' => 'open', + ]); + $allShifts[] = $shift; + $s["nacht_{$key}"] = $shift; + } + + // Terreinploeg: 3 shifts + foreach (['opbouw1', 'opbouw2', 'afbraak'] as $key) { + $shift = Shift::create([ + 'festival_section_id' => $terreinploeg->id, + 'time_slot_id' => $fSlots[$key]->id, + 'title' => 'Terreinmedewerker', + 'slots_total' => $key === 'afbraak' ? 20 : 15, + 'slots_open_for_claiming' => $key === 'afbraak' ? 16 : 12, + 'status' => $key === 'afbraak' ? 'draft' : 'open', + ]); + $allShifts[] = $shift; + $s["terrein_{$key}"] = $shift; + } + + // Accreditatiebalie: 4 shifts + foreach (['vr_early', 'za_dag', 'zo_dag', 'za_avond'] as $key) { + $shift = Shift::create([ + 'festival_section_id' => $accreditatiebalie->id, + 'time_slot_id' => $ts[$key]->id, + 'title' => 'Accreditatiemedewerker', + 'slots_total' => 3, + 'slots_open_for_claiming' => 2, + 'status' => 'open', + ]); + $allShifts[] = $shift; + $s["accred_{$key}"] = $shift; + } + + // Sub-event shifts + $slotMap = [ + 'vrijdag' => ['vr_early', 'vr_avond'], + 'zaterdag' => ['za_dag', 'za_avond'], + 'zondag' => ['zo_dag', 'zo_avond'], + ]; + + $locationMap = [ + 'hoofdbar' => 'hoofdpodium', + 'theaterbar' => 'theatertent', + 'hospitality' => 'backstage', + 'podiumtechniek' => 'hoofdpodium', + 'ingang' => 'ingang', + ]; + + $titleMap = [ + 'hoofdbar' => 'Tapper', + 'theaterbar' => 'Tapper', + 'hospitality' => 'Hospitality', + 'podiumtechniek' => 'Stagehand', + 'ingang' => 'Ticketcontrole', + ]; + + foreach ($slotMap as $dayKey => $slotKeys) { + foreach (array_keys($sectionDefs) as $secKey) { + foreach ($slotKeys as $slotKey) { + $sectionModel = $sections["{$dayKey}_{$secKey}"]; + + $slotsTotal = match ($secKey) { + 'hoofdbar', 'theaterbar' => rand(8, 12), + 'ingang' => rand(6, 8), + default => rand(3, 4), + }; + $slotsOpen = match ($secKey) { + 'hoofdbar', 'theaterbar' => rand(6, 10), + 'ingang' => rand(4, 6), + default => 0, + }; + + $isDraft = $dayKey === 'zondag' && $slotKey === 'zo_avond'; + + $shift = Shift::create([ + 'festival_section_id' => $sectionModel->id, + 'time_slot_id' => $ts[$slotKey]->id, + 'location_id' => $locations[$locationMap[$secKey]]->id, + 'title' => $titleMap[$secKey], + 'slots_total' => $slotsTotal, + 'slots_open_for_claiming' => $slotsOpen, + 'status' => $isDraft ? 'draft' : 'open', + 'allow_overlap' => $secKey === 'podiumtechniek', + ]); + + $allShifts[] = $shift; + $s["{$secKey}_{$slotKey}"] = $shift; + } + } + } + + $shiftCount = count($allShifts); + $this->command->info(" {$shiftCount} shifts created"); + + // ── Named persons (30) ── + + $vol = $this->crowdTypes['VOLUNTEER']->id; + $crew = $this->crowdTypes['CREW']->id; + $press = $this->crowdTypes['PRESS']->id; + $guest = $this->crowdTypes['GUEST']->id; + $supplier = $this->crowdTypes['SUPPLIER']->id; + + // Create user accounts for volunteers who need user_id links + $volunteerUsers = []; + foreach ([ + ['name' => 'Jan de Vries', 'email' => 'jan@gmail.com'], + ['name' => 'Ahmed Hassan', 'email' => 'ahmed.h@gmail.com'], + ['name' => 'Tom Visser', 'email' => 'tom.visser@gmail.com'], + ['name' => 'Lotte de Jong', 'email' => 'lotte@gmail.com'], + ['name' => 'Pieter Geluid', 'email' => 'pieter@podiumtechniek.nl'], + ] as $u) { + $volunteerUsers[$u['email']] = User::create([ + 'name' => $u['name'], + 'email' => $u['email'], + 'password' => Hash::make('password'), + ]); + } + + // 15 named volunteers + $jan = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'user_id' => $volunteerUsers['jan@gmail.com']->id, 'name' => 'Jan de Vries', 'email' => 'jan@gmail.com', 'phone' => '+31612345001', 'status' => 'approved']); + $lisaB = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'name' => 'Lisa Bakker', 'email' => 'lisa.bakker@hotmail.com', 'phone' => '+31612345002', 'status' => 'approved']); + $ahmedP = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'user_id' => $volunteerUsers['ahmed.h@gmail.com']->id, 'name' => 'Ahmed Hassan', 'email' => 'ahmed.h@gmail.com', 'phone' => '+31612345003', 'status' => 'approved']); + $saraJ = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'name' => 'Sara Jansen', 'email' => 'sara.j@outlook.com', 'phone' => '+31612345004', 'status' => 'approved']); + $tomV = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'user_id' => $volunteerUsers['tom.visser@gmail.com']->id, 'name' => 'Tom Visser', 'email' => 'tom.visser@gmail.com', 'phone' => '+31612345005', 'status' => 'approved']); + $fatima = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'name' => 'Fatima El Amrani', 'email' => 'fatima@gmail.com', 'phone' => '+31612345006', 'status' => 'approved']); + $daan = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'name' => 'Daan Smit', 'email' => 'daan.smit@gmail.com', 'phone' => '+31612345007', 'status' => 'pending']); + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'name' => 'Sophie Mulder', 'email' => 'sophie.m@hotmail.com', 'phone' => '+31612345008', 'status' => 'pending']); + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'name' => 'Jesse van Dijk', 'email' => 'jesse@gmail.com', 'phone' => '+31612345009', 'status' => 'applied']); + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'name' => 'Noa Hendriks', 'email' => 'noa.h@outlook.com', 'phone' => '+31612345010', 'status' => 'applied']); + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'name' => 'Kevin Bos', 'email' => 'kevin.bos@gmail.com', 'phone' => '+31612345011', 'status' => 'rejected', 'admin_notes' => 'Vorig jaar no-show']); + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'name' => 'Priya Sharma', 'email' => 'priya@gmail.com', 'phone' => '+31612345012', 'status' => 'invited']); + $lotte = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'user_id' => $volunteerUsers['lotte@gmail.com']->id, 'name' => 'Lotte de Jong', 'email' => 'lotte@gmail.com', 'phone' => '+31612345013', 'status' => 'approved']); + $robin = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'name' => 'Robin Peters', 'email' => 'robin.p@hotmail.com', 'phone' => '+31612345014', 'status' => 'approved']); + $emma = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'name' => 'Emma Willems', 'email' => 'emma.w@gmail.com', 'phone' => '+31612345015', 'status' => 'no_show', 'is_blacklisted' => true]); + + // 6 named crew + $klaas = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $crew, 'company_id' => $this->companies['SecureEvent BV']->id, 'name' => 'Klaas Veilig Jr.', 'email' => 'klaas.jr@secureevent.nl', 'phone' => '+31612345016', 'status' => 'approved']); + $dennis = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $crew, 'company_id' => $this->companies['SecureEvent BV']->id, 'name' => 'Dennis Schild', 'email' => 'dennis@secureevent.nl', 'phone' => '+31612345017', 'status' => 'approved']); + $pieter = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $crew, 'company_id' => $this->companies['Podiumtechniek Rijnmond']->id, 'user_id' => $volunteerUsers['pieter@podiumtechniek.nl']->id, 'name' => 'Pieter Geluid', 'email' => 'pieter@podiumtechniek.nl', 'phone' => '+31612345018', 'status' => 'approved']); + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $crew, 'company_id' => $this->companies['Podiumtechniek Rijnmond']->id, 'name' => 'Marco Licht', 'email' => 'marco@podiumtechniek.nl', 'phone' => '+31612345019', 'status' => 'approved']); + $eva = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $crew, 'company_id' => $this->companies['Brouwerij De Schelde']->id, 'name' => 'Eva Brouwer', 'email' => 'eva@brouwerijdeschelde.nl', 'phone' => '+31612345020', 'status' => 'approved']); + $maria = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $crew, 'company_id' => $this->companies['Van Dijk Catering']->id, 'name' => 'Maria van Dijk', 'email' => 'maria@vandijkcatering.nl', 'phone' => '+31612345021', 'status' => 'approved']); + + // 3 named press + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $press, 'name' => 'Joris van Laar', 'email' => 'joris@pers.nl', 'phone' => '+31612345022', 'status' => 'approved']); + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $press, 'name' => 'Tamara Smeets', 'email' => 'tamara@pers.nl', 'phone' => '+31612345023', 'status' => 'approved']); + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $press, 'name' => 'Sven Pieterse', 'email' => 'sven@pers.nl', 'phone' => '+31612345024', 'status' => 'pending']); + + // 4 named guests + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $guest, 'name' => 'Burgemeester Jan Slagter', 'email' => 'burgemeester@echt.nl', 'phone' => '+31612345025', 'status' => 'approved']); + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $guest, 'name' => 'Wethouder Petra Kamps', 'email' => 'wethouder@echt.nl', 'phone' => '+31612345026', 'status' => 'approved']); + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $guest, 'name' => 'Jan Sponsor', 'email' => 'sponsor@bedrijf.nl', 'phone' => '+31612345027', 'status' => 'approved']); + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $guest, 'name' => 'Gast van Artiest', 'email' => 'artiestgast@gmail.com', 'phone' => '+31612345028', 'status' => 'invited']); + + // 2 named suppliers + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $supplier, 'company_id' => $this->companies['Tap & Co Horeca']->id, 'name' => 'Hans Tapper', 'email' => 'hans@tapco.nl', 'phone' => '+31612345029', 'status' => 'approved']); + Person::create(['event_id' => $festival->id, 'crowd_type_id' => $supplier, 'company_id' => $this->companies['Van Dijk Catering']->id, 'name' => 'Frank van Dijk', 'email' => 'frank@vandijkcatering.nl', 'phone' => '+31612345030', 'status' => 'pending']); + + // ── Factory persons (120) ── + + $companyIds = collect($this->companies)->pluck('id'); + + // 80 factory volunteers + $factoryVolunteers = collect(); + $factoryVolunteers = $factoryVolunteers->merge(Person::factory()->count(55)->approved()->create(['event_id' => $festival->id, 'crowd_type_id' => $vol])); + $factoryVolunteers = $factoryVolunteers->merge(Person::factory()->count(10)->create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'status' => 'pending'])); + $factoryVolunteers = $factoryVolunteers->merge(Person::factory()->count(8)->create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'status' => 'applied'])); + $factoryVolunteers = $factoryVolunteers->merge(Person::factory()->count(4)->create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'status' => 'invited'])); + $factoryVolunteers = $factoryVolunteers->merge(Person::factory()->count(2)->create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'status' => 'rejected'])); + $factoryVolunteers = $factoryVolunteers->merge(Person::factory()->count(1)->create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'status' => 'no_show'])); + + // 19 factory crew + Person::factory()->count(12)->approved() + ->sequence(fn () => ['company_id' => $companyIds->random()]) + ->create(['event_id' => $festival->id, 'crowd_type_id' => $crew]); + Person::factory()->count(5)->approved()->create(['event_id' => $festival->id, 'crowd_type_id' => $crew]); + Person::factory()->count(2)->create(['event_id' => $festival->id, 'crowd_type_id' => $crew, 'status' => 'pending']); + + // 7 factory press + Person::factory()->count(5)->approved()->create(['event_id' => $festival->id, 'crowd_type_id' => $press]); + Person::factory()->count(2)->create(['event_id' => $festival->id, 'crowd_type_id' => $press, 'status' => 'pending']); + + // 8 factory guests + Person::factory()->count(6)->approved()->create(['event_id' => $festival->id, 'crowd_type_id' => $guest]); + Person::factory()->count(2)->create(['event_id' => $festival->id, 'crowd_type_id' => $guest, 'status' => 'invited']); + + // 6 factory suppliers + Person::factory()->count(4)->approved()->create(['event_id' => $festival->id, 'crowd_type_id' => $supplier]); + Person::factory()->count(2)->create(['event_id' => $festival->id, 'crowd_type_id' => $supplier, 'status' => 'pending']); + + $personCount = Person::where('event_id', $festival->id)->count(); + $this->command->info(" {$personCount} persons created"); + + // ── Named shift assignments (22) ── + + $tom = $this->users['tom@feestfabriek.nl']; + + $namedAssignments = [ + // Jan de Vries: 3 bar shifts + ['person' => $jan, 'shift' => 'hoofdbar_vr_early', 'status' => ShiftAssignmentStatus::APPROVED, 'auto' => true], + ['person' => $jan, 'shift' => 'hoofdbar_vr_avond', 'status' => ShiftAssignmentStatus::APPROVED, 'auto' => false], + ['person' => $jan, 'shift' => 'hoofdbar_za_dag', 'status' => ShiftAssignmentStatus::APPROVED], + // Lisa Bakker + ['person' => $lisaB, 'shift' => 'hoofdbar_vr_early', 'status' => ShiftAssignmentStatus::APPROVED, 'auto' => true], + ['person' => $lisaB, 'shift' => 'hoofdbar_za_dag', 'status' => ShiftAssignmentStatus::PENDING_APPROVAL], + ['person' => $lisaB, 'shift' => 'theaterbar_zo_dag', 'status' => ShiftAssignmentStatus::APPROVED, 'auto' => true], + // Ahmed Hassan: 4 EHBO shifts + ['person' => $ahmedP, 'shift' => 'ehbo_vr_early', 'status' => ShiftAssignmentStatus::APPROVED], + ['person' => $ahmedP, 'shift' => 'ehbo_vr_avond', 'status' => ShiftAssignmentStatus::APPROVED], + ['person' => $ahmedP, 'shift' => 'ehbo_za_dag', 'status' => ShiftAssignmentStatus::APPROVED], + ['person' => $ahmedP, 'shift' => 'ehbo_za_avond', 'status' => ShiftAssignmentStatus::APPROVED], + // Sara Jansen + ['person' => $saraJ, 'shift' => 'theaterbar_vr_avond', 'status' => ShiftAssignmentStatus::PENDING_APPROVAL], + // Tom Visser (volunteer) + ['person' => $tomV, 'shift' => 'podiumtechniek_vr_avond', 'status' => ShiftAssignmentStatus::APPROVED], + // Fatima + ['person' => $fatima, 'shift' => 'hoofdbar_za_avond', 'status' => ShiftAssignmentStatus::APPROVED], + // Lotte: terreinploeg + ['person' => $lotte, 'shift' => 'terrein_opbouw1', 'status' => ShiftAssignmentStatus::APPROVED, 'auto' => true], + ['person' => $lotte, 'shift' => 'terrein_afbraak', 'status' => ShiftAssignmentStatus::APPROVED, 'auto' => true], + // Robin + ['person' => $robin, 'shift' => 'theaterbar_za_dag', 'status' => ShiftAssignmentStatus::APPROVED, 'auto' => true], + // Klaas: nachtsecurity, assigned by Tom + ['person' => $klaas, 'shift' => 'nacht_nacht_vr', 'status' => ShiftAssignmentStatus::APPROVED, 'assigned_by' => $tom->id], + // Dennis: nachtsecurity + ['person' => $dennis, 'shift' => 'nacht_nacht_za', 'status' => ShiftAssignmentStatus::APPROVED, 'assigned_by' => $tom->id], + // Daan: rejected + ['person' => $daan, 'shift' => 'hoofdbar_vr_early', 'status' => ShiftAssignmentStatus::REJECTED, 'reason' => 'Nog niet goedgekeurd'], + // Emma: cancelled + ['person' => $emma, 'shift' => 'ingang_vr_early', 'status' => ShiftAssignmentStatus::CANCELLED], + // Pieter: podiumtechniek with overlap + ['person' => $pieter, 'shift' => 'podiumtechniek_vr_avond', 'status' => ShiftAssignmentStatus::APPROVED], + ['person' => $pieter, 'shift' => 'podiumtechniek_za_avond', 'status' => ShiftAssignmentStatus::APPROVED], + ]; + + $usedPersonSlots = []; // track person_id => [time_slot_ids] for factory assignments + + foreach ($namedAssignments as $a) { + $shift = $s[$a['shift']]; + $isApproved = $a['status'] === ShiftAssignmentStatus::APPROVED; + ShiftAssignment::create([ + 'shift_id' => $shift->id, + 'person_id' => $a['person']->id, + 'time_slot_id' => $shift->time_slot_id, + 'status' => $a['status'], + 'auto_approved' => $a['auto'] ?? false, + 'assigned_by' => $a['assigned_by'] ?? null, + 'assigned_at' => now(), + 'approved_at' => $isApproved ? now() : null, + 'rejection_reason' => $a['reason'] ?? null, + ]); + $usedPersonSlots[$a['person']->id][] = $shift->time_slot_id; + } + + // ── Factory shift assignments (~100) ── + + $approvedPersons = Person::where('event_id', $festival->id) + ->where('status', 'approved') + ->get(); + + $openShifts = collect($allShifts)->filter(fn (Shift $shift) => $shift->status === 'open'); + + $statusPool = array_merge( + array_fill(0, 75, ShiftAssignmentStatus::APPROVED), + array_fill(0, 15, ShiftAssignmentStatus::PENDING_APPROVAL), + array_fill(0, 5, ShiftAssignmentStatus::REJECTED), + array_fill(0, 5, ShiftAssignmentStatus::CANCELLED), ); - } + shuffle($statusPool); + $statusIdx = 0; + + foreach ($approvedPersons->shuffle() as $person) { + $existing = $usedPersonSlots[$person->id] ?? []; + $available = $openShifts->filter(fn (Shift $shift) => !in_array($shift->time_slot_id, $existing)); + + if ($available->isEmpty()) { + continue; + } + + $numShifts = min(rand(2, 3), $available->count()); + $picked = $available->shuffle()->take($numShifts); + + foreach ($picked as $shift) { + $status = $statusPool[$statusIdx % count($statusPool)]; + $statusIdx++; + $isApproved = in_array($status, [ShiftAssignmentStatus::APPROVED, ShiftAssignmentStatus::COMPLETED]); + + ShiftAssignment::create([ + 'shift_id' => $shift->id, + 'person_id' => $person->id, + 'time_slot_id' => $shift->time_slot_id, + 'status' => $status, + 'auto_approved' => $isApproved && rand(0, 1) === 1, + 'assigned_at' => now(), + 'approved_at' => $isApproved ? now() : null, + 'rejection_reason' => $status === ShiftAssignmentStatus::REJECTED ? 'Geen beschikbare plek' : null, + ]); + $existing[] = $shift->time_slot_id; + } + $usedPersonSlots[$person->id] = $existing; + } + + $assignmentCount = ShiftAssignment::whereIn('shift_id', collect($allShifts)->pluck('id'))->count(); + $this->command->info(" {$assignmentCount} shift assignments created"); + + // ── Volunteer availabilities (~70) ── + + $volSlotKeys = ['vr_early', 'vr_avond', 'za_dag', 'za_avond', 'zo_dag', 'zo_avond']; + $volSlots = collect($volSlotKeys)->map(fn (string $k) => $ts[$k]); + + // Named availabilities + $namedAvails = [ + [$jan, $volSlots->random(4), fn () => rand(2, 5)], + [$lisaB, $volSlots->random(5), fn () => rand(1, 5)], + [$ahmedP, $volSlots, fn () => 5], + [$saraJ, collect([$ts['vr_avond'], $ts['za_avond']]), fn () => rand(1, 5)], + [$fatima, collect([$ts['za_dag'], $ts['za_avond'], $ts['zo_dag']]), fn () => rand(1, 5)], + ]; + + $usedAvails = []; + foreach ($namedAvails as [$person, $slots, $prefFn]) { + foreach ($slots as $slot) { + $combo = "{$person->id}_{$slot->id}"; + if (isset($usedAvails[$combo])) { + continue; + } + VolunteerAvailability::create([ + 'person_id' => $person->id, + 'time_slot_id' => $slot->id, + 'preference_level' => $prefFn(), + 'submitted_at' => now(), + ]); + $usedAvails[$combo] = true; + } + } + + // Factory availabilities for approved volunteers + $approvedVolunteers = Person::where('event_id', $festival->id) + ->where('status', 'approved') + ->whereHas('crowdType', fn ($q) => $q->where('system_type', 'VOLUNTEER')) + ->get(); + + foreach ($approvedVolunteers as $person) { + $numSlots = rand(2, 5); + foreach ($volSlots->shuffle()->take($numSlots) as $slot) { + $combo = "{$person->id}_{$slot->id}"; + if (isset($usedAvails[$combo])) { + continue; + } + VolunteerAvailability::create([ + 'person_id' => $person->id, + 'time_slot_id' => $slot->id, + 'preference_level' => rand(1, 5), + 'submitted_at' => now(), + ]); + $usedAvails[$combo] = true; + } + } + + // ── Crowd Lists (7) ── + + $bert = $this->users['bert@feestfabriek.nl']; + $allApprovedVolunteers = Person::where('event_id', $festival->id)->where('status', 'approved') + ->whereHas('crowdType', fn ($q) => $q->where('system_type', 'VOLUNTEER'))->get(); + $allApprovedCrew = Person::where('event_id', $festival->id)->where('status', 'approved') + ->whereHas('crowdType', fn ($q) => $q->where('system_type', 'CREW'))->get(); + $allApprovedGuests = Person::where('event_id', $festival->id)->where('status', 'approved') + ->whereHas('crowdType', fn ($q) => $q->where('system_type', 'GUEST'))->get(); + + $this->createCrowdList($festival, 'Vaste Bar Crew', CrowdListType::INTERNAL, $this->crowdTypes['VOLUNTEER'], null, false, null, $allApprovedVolunteers->take(20), $bert); + $this->createCrowdList($festival, 'EHBO Team', CrowdListType::INTERNAL, $this->crowdTypes['VOLUNTEER'], null, false, 10, $allApprovedVolunteers->shuffle()->take(5), $bert); + $this->createCrowdList($festival, 'Terreinploeg Pool', CrowdListType::INTERNAL, $this->crowdTypes['VOLUNTEER'], null, true, 25, $allApprovedVolunteers->shuffle()->take(15), $bert); + $this->createCrowdList($festival, 'SecureEvent Beveiliging', CrowdListType::EXTERNAL, $this->crowdTypes['CREW'], $this->companies['SecureEvent BV'], true, 15, $allApprovedCrew->shuffle()->take(8), $bert); + $this->createCrowdList($festival, 'Podiumtechniek Crew', CrowdListType::EXTERNAL, $this->crowdTypes['CREW'], $this->companies['Podiumtechniek Rijnmond'], false, 6, $allApprovedCrew->shuffle()->take(5), $bert); + $this->createCrowdList($festival, 'Catering Crew', CrowdListType::EXTERNAL, $this->crowdTypes['CREW'], $this->companies['Van Dijk Catering'], false, 8, $allApprovedCrew->shuffle()->take(4), $bert); + $this->createCrowdList($festival, 'VIP Gastenlijst', CrowdListType::INTERNAL, $this->crowdTypes['GUEST'], null, true, 30, $allApprovedGuests->take(12), $bert); + + // ── Event person activations (crew on sub-events) ── + + $activations = [ + [$klaas, [$vrijdag, $zaterdag, $zondag]], + [$dennis, [$zaterdag, $zondag]], + [$eva, [$vrijdag]], + [$maria, [$vrijdag, $zaterdag, $zondag]], + ]; + + foreach ($activations as [$person, $events]) { + foreach ($events as $event) { + DB::table('event_person_activations')->insert([ + 'event_id' => $event->id, + 'person_id' => $person->id, + ]); + } + } + + // Activate remaining approved crew on 1-3 random sub-events + $subEventIds = [$vrijdag->id, $zaterdag->id, $zondag->id]; + $activatedPersonIds = collect($activations)->pluck('0.id'); + $remainingCrew = $allApprovedCrew->reject(fn (Person $p) => $activatedPersonIds->contains($p->id)); + + foreach ($remainingCrew as $person) { + $numEvents = rand(1, 3); + $selectedEvents = collect($subEventIds)->shuffle()->take($numEvents); + foreach ($selectedEvents as $eventId) { + DB::table('event_person_activations')->insert([ + 'event_id' => $eventId, + 'person_id' => $person->id, + ]); + } + } + + // ── User Organisation Tags ── + + $tagData = [ + ['user' => $volunteerUsers['jan@gmail.com'], 'tag' => 'Tapper', 'source' => 'self_reported', 'proficiency' => 'experienced'], + ['user' => $volunteerUsers['jan@gmail.com'], 'tag' => 'EHBO', 'source' => 'organiser_assigned', 'proficiency' => 'beginner', 'assigned_by' => $bert->id], + ['user' => $volunteerUsers['ahmed.h@gmail.com'], 'tag' => 'EHBO', 'source' => 'self_reported', 'proficiency' => 'expert'], + ['user' => $volunteerUsers['ahmed.h@gmail.com'], 'tag' => 'BHV', 'source' => 'organiser_assigned', 'proficiency' => 'expert', 'assigned_by' => $bert->id], + ['user' => $volunteerUsers['ahmed.h@gmail.com'], 'tag' => 'Rijbewijs B', 'source' => 'self_reported', 'proficiency' => null], + ['user' => $volunteerUsers['tom.visser@gmail.com'], 'tag' => 'Podiumervaring', 'source' => 'self_reported', 'proficiency' => 'experienced'], + ['user' => $volunteerUsers['tom.visser@gmail.com'], 'tag' => 'Engels', 'source' => 'self_reported', 'proficiency' => 'experienced'], + ['user' => $volunteerUsers['tom.visser@gmail.com'], 'tag' => 'Duits', 'source' => 'self_reported', 'proficiency' => 'beginner'], + ['user' => $volunteerUsers['lotte@gmail.com'], 'tag' => 'Rijbewijs B', 'source' => 'self_reported', 'proficiency' => null], + ['user' => $volunteerUsers['lotte@gmail.com'], 'tag' => 'Heftruck', 'source' => 'organiser_assigned', 'proficiency' => 'experienced', 'assigned_by' => $bert->id], + ]; + + foreach ($tagData as $td) { + UserOrganisationTag::create([ + 'user_id' => $td['user']->id, + 'organisation_id' => $this->org->id, + 'person_tag_id' => $this->personTags[$td['tag']]->id, + 'source' => $td['source'], + 'assigned_by_user_id' => $td['assigned_by'] ?? null, + 'proficiency' => $td['proficiency'], + 'assigned_at' => now(), + ]); + } + + $this->command->info(' Echt Feesten 2026 complete'); + }); + } + + // ========================================================================= + // Event 2: IJsbaan Winterpark (series, published, 60 persons) + // ========================================================================= + + private function seedIJsbaan(): void + { + DB::transaction(function (): void { + $this->command->info('Seeding IJsbaan Winterpark...'); + + // ── Event hierarchy ── + + $ijsbaan = Event::create([ + 'organisation_id' => $this->org->id, + 'name' => 'IJsbaan Winterpark', + 'slug' => 'ijsbaan-winterpark', + 'start_date' => '2026-12-05', + 'end_date' => '2027-01-25', + 'timezone' => 'Europe/Amsterdam', + 'status' => 'published', + 'event_type' => 'series', + 'event_type_label' => 'Schaatsseizoen', + 'sub_event_label' => 'Editie', + ]); + + $weeksData = [ + ['name' => 'Week 1 — Opening', 'start' => '2026-12-05', 'end' => '2026-12-07', 'status' => 'published', 'zaterdag' => '2026-12-05', 'zondag' => '2026-12-06'], + ['name' => 'Week 2 — Kerst Special', 'start' => '2026-12-19', 'end' => '2026-12-21', 'status' => 'published', 'zaterdag' => '2026-12-19', 'zondag' => '2026-12-20'], + ['name' => 'Week 3 — Nieuwjaar', 'start' => '2027-01-02', 'end' => '2027-01-04', 'status' => 'draft', 'zaterdag' => '2027-01-03', 'zondag' => '2027-01-04'], + ['name' => 'Week 4 — Afsluiting', 'start' => '2027-01-23', 'end' => '2027-01-25', 'status' => 'draft', 'zaterdag' => '2027-01-24', 'zondag' => '2027-01-25'], + ]; + + $weeks = []; + foreach ($weeksData as $i => $wd) { + $weeks[$i] = Event::create([ + 'organisation_id' => $this->org->id, + 'parent_event_id' => $ijsbaan->id, + 'name' => $wd['name'], + 'slug' => 'ijsbaan-week-' . ($i + 1), + 'start_date' => $wd['start'], + 'end_date' => $wd['end'], + 'timezone' => 'Europe/Amsterdam', + 'status' => $wd['status'], + 'event_type' => 'event', + ]); + } + + // ── Locations (2, on parent) ── + + Location::create(['event_id' => $ijsbaan->id, 'name' => 'IJsbaan Kralingse Plas', 'address' => 'Kralingseweg 200, Rotterdam', 'lat' => 51.9300, 'lng' => 4.5100]); + Location::create(['event_id' => $ijsbaan->id, 'name' => 'Verwarmde tent', 'address' => 'Kralingseweg 200A, Rotterdam', 'lat' => 51.9302, 'lng' => 4.5105]); + + // ── Sections, time slots, shifts per sub-event ── + + $sectionDefs = [ + ['name' => 'Schaatsbaan Bar', 'category' => 'Bar', 'icon' => 'tabler-beer'], + ['name' => 'Schaatsverhuur', 'category' => 'Ontvangst', 'icon' => 'tabler-ticket'], + ['name' => 'Terrein', 'category' => 'Productie', 'icon' => 'tabler-shovel'], + ]; + + $allShifts = []; + + foreach ($weeks as $i => $week) { + $wd = $weeksData[$i]; + $isOpen = in_array($wd['status'], ['published', 'registration_open']); + + // Sections + $weekSections = []; + foreach ($sectionDefs as $order => $sd) { + $weekSections[] = FestivalSection::create([ + 'event_id' => $week->id, + 'name' => $sd['name'], + 'type' => 'standard', + 'category' => $sd['category'], + 'icon' => $sd['icon'], + 'sort_order' => $order + 1, + 'responder_self_checkin' => true, + 'crew_auto_accepts' => true, + ]); + } + + // Time slots + $zatSlot = TimeSlot::create(['event_id' => $week->id, 'name' => 'Zaterdag', 'person_type' => 'VOLUNTEER', 'date' => $wd['zaterdag'], 'start_time' => '10:00', 'end_time' => '18:00', 'duration_hours' => 8.00]); + $zonSlot = TimeSlot::create(['event_id' => $week->id, 'name' => 'Zondag', 'person_type' => 'VOLUNTEER', 'date' => $wd['zondag'], 'start_time' => '10:00', 'end_time' => '18:00', 'duration_hours' => 8.00]); + + // Shifts + foreach ($weekSections as $section) { + foreach ([$zatSlot, $zonSlot] as $slot) { + $shift = Shift::create([ + 'festival_section_id' => $section->id, + 'time_slot_id' => $slot->id, + 'title' => $section->name, + 'slots_total' => 6, + 'slots_open_for_claiming' => 6, + 'status' => $isOpen ? 'open' : 'draft', + ]); + $allShifts[] = $shift; + } + } + } + + // ── Persons (60, all factory) ── + + $vol = $this->crowdTypes['VOLUNTEER']->id; + $crewType = $this->crowdTypes['CREW']->id; + $guestType = $this->crowdTypes['GUEST']->id; + + $approvedVol = Person::factory()->count(35)->approved()->create(['event_id' => $ijsbaan->id, 'crowd_type_id' => $vol]); + Person::factory()->count(5)->create(['event_id' => $ijsbaan->id, 'crowd_type_id' => $vol, 'status' => 'pending']); + Person::factory()->count(3)->create(['event_id' => $ijsbaan->id, 'crowd_type_id' => $vol, 'status' => 'applied']); + $approvedCrew = Person::factory()->count(10)->approved()->create(['event_id' => $ijsbaan->id, 'crowd_type_id' => $crewType]); + 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]); + + // ── Shift assignments (~80) ── + + $openShifts = collect($allShifts)->filter(fn (Shift $shift) => $shift->status === 'open'); + $draftShifts = collect($allShifts)->filter(fn (Shift $shift) => $shift->status === 'draft'); + $allApproved = $approvedVol->merge($approvedCrew); + + // Week 1+2: 5-6 per open shift + foreach ($openShifts as $shift) { + $assigned = $allApproved->shuffle()->take(rand(5, 6)); + foreach ($assigned as $person) { + ShiftAssignment::create([ + 'shift_id' => $shift->id, + 'person_id' => $person->id, + 'time_slot_id' => $shift->time_slot_id, + 'status' => ShiftAssignmentStatus::APPROVED, + 'auto_approved' => true, + 'assigned_at' => now(), + 'approved_at' => now(), + ]); + } + } + + // ~10 pending for week 3 draft shifts + $pendingCount = 0; + foreach ($draftShifts->take(4) as $shift) { + $assigned = $allApproved->shuffle()->take(rand(2, 3)); + foreach ($assigned as $person) { + ShiftAssignment::create([ + 'shift_id' => $shift->id, + 'person_id' => $person->id, + 'time_slot_id' => $shift->time_slot_id, + 'status' => ShiftAssignmentStatus::PENDING_APPROVAL, + 'assigned_at' => now(), + ]); + $pendingCount++; + if ($pendingCount >= 10) { + break 2; + } + } + } + + // ── Crowd Lists (2) ── + + $bert = $this->users['bert@feestfabriek.nl']; + $this->createCrowdList($ijsbaan, 'IJsbaan Vrijwilligerspool', CrowdListType::INTERNAL, $this->crowdTypes['VOLUNTEER'], null, false, null, $approvedVol, $bert); + $this->createCrowdList($ijsbaan, 'IJsbaan Vaste Crew', CrowdListType::INTERNAL, $this->crowdTypes['CREW'], null, false, null, $approvedCrew, $bert); + + $personCount = Person::where('event_id', $ijsbaan->id)->count(); + $this->command->info(" {$personCount} persons, " . count($allShifts) . ' shifts created'); + }); + } + + // ========================================================================= + // Event 3: Koningsdag Rotterdam 2026 (flat event, closed, 100 persons) + // ========================================================================= + + private function seedKoningsdag(): void + { + DB::transaction(function (): void { + $this->command->info('Seeding Koningsdag Rotterdam 2026...'); + + // ── Event ── + + $koningsdag = Event::create([ + 'organisation_id' => $this->org->id, + 'name' => 'Koningsdag Rotterdam 2026', + 'slug' => 'koningsdag-rotterdam-2026', + 'start_date' => '2026-04-27', + 'end_date' => '2026-04-27', + 'timezone' => 'Europe/Amsterdam', + 'status' => 'closed', + 'event_type' => 'event', + ]); + + // ── Locations (3) ── + + $locErasmus = Location::create(['event_id' => $koningsdag->id, 'name' => 'Erasmusbrug podium', 'address' => 'Erasmusbrug, Rotterdam', 'lat' => 51.9090, 'lng' => 4.4870]); + $locWillems = Location::create(['event_id' => $koningsdag->id, 'name' => 'Willemsplein', 'address' => 'Willemsplein, Rotterdam', 'lat' => 51.9070, 'lng' => 4.4860]); + $locOudeHaven = Location::create(['event_id' => $koningsdag->id, 'name' => 'Oude Haven', 'address' => 'Oude Haven, Rotterdam', 'lat' => 51.9200, 'lng' => 4.4950]); + + // ── Sections (4) ── + + $secPodium = FestivalSection::create(['event_id' => $koningsdag->id, 'name' => 'Podium Erasmusbrug', 'type' => 'standard', 'category' => 'Podium', 'icon' => 'tabler-microphone-2', 'sort_order' => 1, 'responder_self_checkin' => true]); + $secBar = FestivalSection::create(['event_id' => $koningsdag->id, 'name' => 'Bar Willemsplein', 'type' => 'standard', 'category' => 'Bar', 'icon' => 'tabler-beer', 'sort_order' => 2, 'responder_self_checkin' => true, 'crew_auto_accepts' => true]); + $secKids = FestivalSection::create(['event_id' => $koningsdag->id, 'name' => 'Kinderactiviteiten', 'type' => 'standard', 'category' => 'Entertainment', 'icon' => 'tabler-balloon', 'sort_order' => 3, 'responder_self_checkin' => true]); + $secBev = FestivalSection::create(['event_id' => $koningsdag->id, 'name' => 'Beveiliging', 'type' => 'standard', 'category' => 'Veiligheid', 'icon' => 'tabler-shield', 'sort_order' => 4, 'responder_self_checkin' => true]); + + $kSections = [$secPodium, $secBar, $secKids, $secBev]; + $kLocations = [$locErasmus, $locWillems, $locOudeHaven, $locOudeHaven]; + + // ── Time Slots (3) ── + + $tsOchtend = TimeSlot::create(['event_id' => $koningsdag->id, 'name' => 'Ochtend', 'person_type' => 'VOLUNTEER', 'date' => '2026-04-27', 'start_time' => '09:00', 'end_time' => '13:00', 'duration_hours' => 4.00]); + $tsMiddag = TimeSlot::create(['event_id' => $koningsdag->id, 'name' => 'Middag', 'person_type' => 'VOLUNTEER', 'date' => '2026-04-27', 'start_time' => '13:00', 'end_time' => '18:00', 'duration_hours' => 5.00]); + $tsAvond = TimeSlot::create(['event_id' => $koningsdag->id, 'name' => 'Avond', 'person_type' => 'VOLUNTEER', 'date' => '2026-04-27', 'start_time' => '18:00', 'end_time' => '23:00', 'duration_hours' => 5.00]); + $kSlots = [$tsOchtend, $tsMiddag, $tsAvond]; + + // ── Shifts (12, all completed) ── + + $kShifts = []; + foreach ($kSections as $i => $section) { + foreach ($kSlots as $slot) { + $slotsTotal = $section->name === 'Bar Willemsplein' ? 15 : rand(8, 12); + $kShifts[] = Shift::create([ + 'festival_section_id' => $section->id, + 'time_slot_id' => $slot->id, + 'location_id' => $kLocations[$i]->id, + 'title' => match ($section->name) { + 'Bar Willemsplein' => 'Tapper', + 'Podium Erasmusbrug' => 'Podiummedewerker', + 'Kinderactiviteiten' => 'Animator', + 'Beveiliging' => 'Beveiliger', + }, + 'slots_total' => $slotsTotal, + 'slots_open_for_claiming' => 0, + 'status' => 'completed', + ]); + } + } + + // ── Persons (100, all factory) ── + + $vol = $this->crowdTypes['VOLUNTEER']->id; + $crewType = $this->crowdTypes['CREW']->id; + $pressType = $this->crowdTypes['PRESS']->id; + $guestType = $this->crowdTypes['GUEST']->id; + $supplierType = $this->crowdTypes['SUPPLIER']->id; + + // 75 volunteers (55 approved, 8 no_show, 5 rejected, 4 approved no-shift, 3 rejected) + $kApprovedVol = Person::factory()->count(59)->approved()->create(['event_id' => $koningsdag->id, 'crowd_type_id' => $vol]); + $kNoShowVol = Person::factory()->count(8)->create(['event_id' => $koningsdag->id, 'crowd_type_id' => $vol, 'status' => 'no_show']); + Person::factory()->count(8)->create(['event_id' => $koningsdag->id, 'crowd_type_id' => $vol, 'status' => 'rejected']); + + // 12 crew (8 with SecureEvent BV) + $kCrew = Person::factory()->count(8)->approved()->create(['event_id' => $koningsdag->id, 'crowd_type_id' => $crewType, 'company_id' => $this->companies['SecureEvent BV']->id]); + $kCrew = $kCrew->merge(Person::factory()->count(4)->approved()->create(['event_id' => $koningsdag->id, 'crowd_type_id' => $crewType])); + + // 6 press + Person::factory()->count(6)->approved()->create(['event_id' => $koningsdag->id, 'crowd_type_id' => $pressType]); + + // 5 guests (with Rotterdam Festivals) + $kGuests = Person::factory()->count(5)->approved()->create(['event_id' => $koningsdag->id, 'crowd_type_id' => $guestType, 'company_id' => $this->companies['Rotterdam Festivals']->id]); + + // 2 suppliers + Person::factory()->count(2)->approved()->create(['event_id' => $koningsdag->id, 'crowd_type_id' => $supplierType]); + + // ── Shift assignments (~150) ── + + // 120 completed + 12 cancelled + 8 no-show + 5 rejected + 5 pending + $statusPool = array_merge( + array_fill(0, 120, 'completed'), + array_fill(0, 12, 'cancelled'), + array_fill(0, 5, 'rejected'), + array_fill(0, 5, 'pending'), + ); + shuffle($statusPool); + $statusIdx = 0; + + // Assign approved volunteers 2-3 shifts each + foreach ($kApprovedVol as $person) { + $numShifts = rand(2, 3); + $picked = collect($kShifts)->shuffle()->take($numShifts); + foreach ($picked as $shift) { + $statusKey = $statusPool[$statusIdx % count($statusPool)] ?? 'completed'; + $statusIdx++; + $status = match ($statusKey) { + 'completed' => ShiftAssignmentStatus::COMPLETED, + 'cancelled' => ShiftAssignmentStatus::CANCELLED, + 'rejected' => ShiftAssignmentStatus::REJECTED, + 'pending' => ShiftAssignmentStatus::PENDING_APPROVAL, + }; + $isCompleted = $status === ShiftAssignmentStatus::COMPLETED; + ShiftAssignment::create([ + 'shift_id' => $shift->id, + 'person_id' => $person->id, + 'time_slot_id' => $shift->time_slot_id, + 'status' => $status, + 'auto_approved' => $isCompleted, + 'assigned_at' => now(), + 'approved_at' => $isCompleted ? now() : null, + 'hours_completed' => $isCompleted ? round(rand(35, 80) / 10, 1) : null, + 'checked_in_at' => $isCompleted ? now() : null, + 'checked_out_at' => $isCompleted ? now()->addHours(rand(3, 8)) : null, + ]); + } + } + + // No-show persons: approved assignment but no check-in + foreach ($kNoShowVol as $person) { + $shift = collect($kShifts)->random(); + ShiftAssignment::create([ + 'shift_id' => $shift->id, + 'person_id' => $person->id, + 'time_slot_id' => $shift->time_slot_id, + 'status' => ShiftAssignmentStatus::APPROVED, + 'auto_approved' => true, + 'assigned_at' => now(), + 'approved_at' => now(), + 'checked_in_at' => null, + ]); + } + + // ── Crowd Lists (3) ── + + $bert = $this->users['bert@feestfabriek.nl']; + $allKVolunteers = Person::where('event_id', $koningsdag->id)->where('status', 'approved') + ->whereHas('crowdType', fn ($q) => $q->where('system_type', 'VOLUNTEER'))->get(); + + $this->createCrowdList($koningsdag, 'Koningsdag Crew 2026', CrowdListType::INTERNAL, $this->crowdTypes['VOLUNTEER'], null, false, null, $allKVolunteers->take(55), $bert); + $this->createCrowdList($koningsdag, 'Beveiliging Koningsdag', CrowdListType::EXTERNAL, $this->crowdTypes['CREW'], $this->companies['SecureEvent BV'], false, null, $kCrew->take(8), $bert); + $this->createCrowdList($koningsdag, 'Gemeente Gasten', CrowdListType::EXTERNAL, $this->crowdTypes['GUEST'], $this->companies['Rotterdam Festivals'], false, null, $kGuests, $bert); + + $personCount = Person::where('event_id', $koningsdag->id)->count(); + $assignCount = ShiftAssignment::whereIn('shift_id', collect($kShifts)->pluck('id'))->count(); + $this->command->info(" {$personCount} persons, 12 shifts, {$assignCount} assignments"); + }); + } + + // ========================================================================= + // Event 4: Nacht van de Kaap 2026 (flat event, draft, empty) + // ========================================================================= + + private function seedNachtVanDeKaap(): void + { + DB::transaction(function (): void { + $this->command->info('Seeding Nacht van de Kaap 2026...'); + + $event = Event::create([ + 'organisation_id' => $this->org->id, + 'name' => 'Nacht van de Kaap 2026', + 'slug' => 'nacht-van-de-kaap-2026', + 'start_date' => '2026-09-12', + 'end_date' => '2026-09-12', + 'timezone' => 'Europe/Amsterdam', + 'status' => 'draft', + 'event_type' => 'event', + ]); + + Location::create(['event_id' => $event->id, 'name' => 'Katendrecht', 'address' => 'Deliplein, Rotterdam', 'lat' => 51.8950, 'lng' => 4.4850]); + + FestivalSection::create(['event_id' => $event->id, 'name' => 'Kaapse Bar', 'type' => 'standard', 'category' => 'Bar', 'icon' => 'tabler-beer', 'sort_order' => 1, 'responder_self_checkin' => true]); + FestivalSection::create(['event_id' => $event->id, 'name' => 'Podium Deliplein', 'type' => 'standard', 'category' => 'Podium', 'icon' => 'tabler-microphone-2', 'sort_order' => 2, 'responder_self_checkin' => true]); + + TimeSlot::create(['event_id' => $event->id, 'name' => 'Nacht', 'person_type' => 'VOLUNTEER', 'date' => '2026-09-12', 'start_time' => '20:00', 'end_time' => '04:00', 'duration_hours' => 8.00]); + + $this->command->info(' Empty draft event created'); + }); + } + + // ========================================================================= + // Helpers + // ========================================================================= + + private function createCrowdList( + Event $event, + string $name, + CrowdListType $type, + CrowdType $crowdType, + ?Company $company, + bool $autoApprove, + ?int $maxPersons, + Collection $persons, + User $addedBy, + ): CrowdList { + $list = CrowdList::create([ + 'event_id' => $event->id, + 'crowd_type_id' => $crowdType->id, + 'name' => $name, + 'type' => $type, + 'recipient_company_id' => $company?->id, + 'auto_approve' => $autoApprove, + 'max_persons' => $maxPersons, + ]); + + $pivotData = $persons->mapWithKeys(fn (Person $p) => [ + $p->id => ['added_at' => now(), 'added_by_user_id' => $addedBy->id], + ])->all(); + + $list->persons()->attach($pivotData); + + return $list; } }