*/ 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); $this->seedOrganisation(); $this->seedEchtFeesten(); $this->seedBraderie(); $this->seedIJsbaan(); $this->seedKoningsdag(); $this->seedNachtVanDeKaap(); } // ========================================================================= // 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' => [], ]); // ── Users (8) ── $usersData = [ ['email' => 'admin@crewli.test', 'first_name' => 'Super', 'last_name' => 'Admin', 'app_role' => 'super_admin', 'org_role' => 'org_admin', 'date_of_birth' => '1985-01-15'], ['email' => 'bert@feestfabriek.nl', 'first_name' => 'Bert', 'last_name' => 'Hausmans', 'org_role' => 'org_admin', 'date_of_birth' => '1990-06-28'], ['email' => 'lisa@feestfabriek.nl', 'first_name' => 'Lisa', 'last_name' => 'van den Berg', 'org_role' => 'org_member', 'date_of_birth' => '1993-05-12'], ['email' => 'ahmed@feestfabriek.nl', 'first_name' => 'Ahmed', 'last_name' => 'Yilmaz', 'org_role' => 'org_member', 'date_of_birth' => '1989-09-03'], ['email' => 'sara@feestfabriek.nl', 'first_name' => 'Sara', 'last_name' => 'de Groot', 'org_role' => 'org_member', 'date_of_birth' => '1991-08-24'], ['email' => 'tom@feestfabriek.nl', 'first_name' => 'Tom', 'last_name' => 'Visser', 'org_role' => 'org_member', 'date_of_birth' => '1994-11-07'], ['email' => 'nina@feestfabriek.nl', 'first_name' => 'Nina', 'last_name' => 'Jansen', 'org_role' => 'org_member', 'date_of_birth' => '1996-02-14'], ['email' => 'mark@feestfabriek.nl', 'first_name' => 'Mark', 'last_name' => 'de Boer', 'org_role' => 'org_member', 'date_of_birth' => '1988-03-17'], ]; foreach ($usersData as $data) { $user = User::create([ 'first_name' => $data['first_name'], 'last_name' => $data['last_name'], 'email' => $data['email'], 'date_of_birth' => $data['date_of_birth'] ?? null, 'password' => Hash::make('password'), ]); if (isset($data['app_role'])) { $user->assignRole($data['app_role']); } $this->org->users()->attach($user, ['role' => $data['org_role']]); $this->users[$data['email']] = $user; } // ── Companies (6) ── $companiesData = [ ['name' => 'Tap & Co Horeca', 'type' => 'supplier', 'contact_first_name' => 'Jan', 'contact_last_name' => 'Tapper', 'contact_email' => 'jan@tapco.nl', 'contact_phone' => '+31612340001'], ['name' => 'SecureEvent BV', 'type' => 'supplier', 'contact_first_name' => 'Klaas', 'contact_last_name' => 'Veilig', 'contact_email' => 'klaas@secureevent.nl', 'contact_phone' => '+31612340002'], ['name' => 'Podiumtechniek Rijnmond', 'type' => 'supplier', 'contact_first_name' => 'Pieter', 'contact_last_name' => 'Geluid', 'contact_email' => 'pieter@podiumtechniek.nl', 'contact_phone' => '+31612340003'], ['name' => 'Brouwerij De Schelde', 'type' => 'partner', 'contact_first_name' => 'Eva', 'contact_last_name' => 'Brouwer', 'contact_email' => 'eva@brouwerijdeschelde.nl', 'contact_phone' => '+31612340004'], ['name' => 'Van Dijk Catering', 'type' => 'supplier', 'contact_first_name' => 'Maria', 'contact_last_name' => 'van Dijk', 'contact_email' => 'maria@vandijkcatering.nl', 'contact_phone' => '+31612340005'], ['name' => 'Rotterdam Festivals', 'type' => 'agency', 'contact_first_name' => 'Femke', 'contact_last_name' => 'de Wit', 'contact_email' => 'femke@rotterdamfestivals.nl', 'contact_phone' => '+31612340006'], ]; foreach ($companiesData as $data) { $company = Company::create(['organisation_id' => $this->org->id, ...$data]); $this->companies[$data['name']] = $company; } // ── 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; } // ── Registration Field Templates (system defaults) ── \App\Services\RegistrationFieldTemplateService::seedSystemTemplates($this->org); $this->command->info(' Organisation, 8 users, 6 companies, 7 crowd types, 10 person tags, 11 registration templates 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', 'status' => 'registration_open', 'event_type' => 'festival', 'event_type_label' => 'Festival', 'sub_event_label' => 'Programmaonderdeel', 'registration_welcome_text' => 'Wij zoeken enthousiaste vrijwilligers voor Echt Feesten 2026! Word onderdeel van ons team en beleef het festival van de andere kant. Gratis toegang, maaltijden, en een onvergetelijke ervaring.', ]); $vrijdag = Event::create([ 'organisation_id' => $this->org->id, 'parent_event_id' => $festival->id, 'name' => 'Dag 1 — 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', ]); $zaterdag = Event::create([ 'organisation_id' => $this->org->id, 'parent_event_id' => $festival->id, 'name' => 'Dag 2 — 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', ]); $zondag = Event::create([ 'organisation_id' => $this->org->id, 'parent_event_id' => $festival->id, 'name' => 'Dag 3 — Zondag', 'slug' => 'echt-feesten-2026-zondag', 'start_date' => '2026-07-12', 'end_date' => '2026-07-12', 'timezone' => 'Europe/Amsterdam', 'status' => 'published', 'event_type' => 'event', ]); $subEvents = ['vrijdag' => $vrijdag, 'zaterdag' => $zaterdag, 'zondag' => $zondag]; // 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]); } // ── Locations (5, on parent) ── $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]); } // ── Festival-level sections (4) ── $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, 'show_in_registration' => false, ]); $security = FestivalSection::create([ 'event_id' => $festival->id, 'name' => 'Security', 'type' => 'cross_event', 'category' => 'Veiligheid', 'icon' => 'tabler-shield', 'sort_order' => 2, 'responder_self_checkin' => true, 'crew_auto_accepts' => false, 'show_in_registration' => false, ]); $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, 'show_in_registration' => false, ]); $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, 'show_in_registration' => false, ]); // ── Sub-event sections (5 per sub-event) ── $sectionDefs = [ 'hoofdbar' => ['name' => 'Hoofdpodium Bar', 'category' => 'Bar', 'icon' => 'tabler-beer', 'crew_auto_accepts' => true, 'show_in_registration' => true, 'registration_description' => 'Tap bier en drankjes voor festivalgangers bij het hoofdpodium'], 'theaterbar' => ['name' => 'Theatertent Bar', 'category' => 'Bar', 'icon' => 'tabler-beer', 'crew_auto_accepts' => true, 'show_in_registration' => true, 'registration_description' => 'Bediening in de overdekte theatertent'], 'hospitality' => ['name' => 'Backstage Hospitality', 'category' => 'Hospitality', 'icon' => 'tabler-armchair', 'crew_auto_accepts' => false, 'show_in_registration' => true, 'registration_description' => 'Ontvang en begeleid artiesten en gasten backstage'], 'podiumtechniek' => ['name' => 'Podiumtechniek', 'category' => 'Techniek', 'icon' => 'tabler-speakerphone', 'crew_auto_accepts' => false, 'show_in_registration' => true, 'registration_description' => 'Help met geluid- en lichttechniek bij de podia'], 'ingang' => ['name' => 'Ingang & Tickets', 'category' => 'Ontvangst', 'icon' => 'tabler-ticket', 'crew_auto_accepts' => true, 'show_in_registration' => true, 'registration_description' => 'Verwelkom bezoekers en scan tickets bij de ingang'], ]; $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'], 'show_in_registration' => $def['show_in_registration'] ?? false, 'registration_description' => $def['registration_description'] ?? null, ]); } } // ── Festival-level time slots (CREW only — operational planning) ── // No VOLUNTEER slots at festival level — cross_event sections use sub-event time slots $fSlots = []; $fSlots['opbouw1'] = TimeSlot::create(['event_id' => $festival->id, 'name' => 'Opbouw vrijdag', '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 zaterdag', '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' => 'Nachtsecurity vr→za', 'person_type' => 'CREW', 'date' => '2026-07-10', 'start_time' => '23:00', 'end_time' => '07:00', 'duration_hours' => 8.00]); $fSlots['nacht_za'] = TimeSlot::create(['event_id' => $festival->id, 'name' => 'Nachtsecurity za→zo', 'person_type' => 'CREW', 'date' => '2026-07-11', 'start_time' => '23:00', 'end_time' => '07:00', 'duration_hours' => 8.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]); // ── Sub-event time slots (program-specific) ── $ts = []; // Dag 1 — Vrijdag $ts['vr_middag'] = TimeSlot::create(['event_id' => $vrijdag->id, 'name' => 'Vrijdag middag — vrijwilliger', 'person_type' => 'VOLUNTEER', 'date' => '2026-07-10', 'start_time' => '12:00', 'end_time' => '18:00', 'duration_hours' => 6.00]); $ts['vr_avond'] = TimeSlot::create(['event_id' => $vrijdag->id, 'name' => 'Vrijdag avond — vrijwilliger', 'person_type' => 'VOLUNTEER', 'date' => '2026-07-10', 'start_time' => '18:00', 'end_time' => '02:00', 'duration_hours' => 8.00]); $ts['vr_crew'] = TimeSlot::create(['event_id' => $vrijdag->id, 'name' => 'Vrijdag avond — crew', 'person_type' => 'CREW', 'date' => '2026-07-10', 'start_time' => '17:00', 'end_time' => '03:00', 'duration_hours' => 10.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]); // Dag 2 — Zaterdag $ts['za_middag'] = TimeSlot::create(['event_id' => $zaterdag->id, 'name' => 'Zaterdag middag — vrijwilliger', 'person_type' => 'VOLUNTEER', 'date' => '2026-07-11', 'start_time' => '12:00', 'end_time' => '18:00', 'duration_hours' => 6.00]); $ts['za_avond'] = TimeSlot::create(['event_id' => $zaterdag->id, 'name' => 'Zaterdag avond — vrijwilliger', '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 avond — crew', 'person_type' => 'CREW', 'date' => '2026-07-11', 'start_time' => '17:00', 'end_time' => '03:00', 'duration_hours' => 10.00]); // Dag 3 — Zondag $ts['zo_middag'] = TimeSlot::create(['event_id' => $zondag->id, 'name' => 'Zondag middag — vrijwilliger', '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 — vrijwilliger', '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 (cross_event): shifts use sub-event time slots (cross_event exception) // + 1 festival-level shift for opbouw duties $s['ehbo_opbouw'] = Shift::create([ 'festival_section_id' => $ehbo->id, 'time_slot_id' => $fSlots['opbouw1']->id, 'title' => 'EHBO Post', 'slots_total' => 2, 'slots_open_for_claiming' => 0, 'status' => 'open', ]); $allShifts[] = $s['ehbo_opbouw']; foreach (['vr_middag', 'vr_avond', 'za_middag', 'za_avond', 'zo_middag'] as $key) { $shift = Shift::create([ 'festival_section_id' => $ehbo->id, 'time_slot_id' => $ts[$key]->id, 'title' => 'EHBO Post', 'slots_total' => $key === 'za_middag' ? 4 : 3, 'slots_open_for_claiming' => $key === 'za_middag' ? 0 : 2, 'status' => 'open', ]); $allShifts[] = $shift; $s["ehbo_{$key}"] = $shift; } // Security (cross_event): festival night shifts + sub-event crew shifts foreach (['nacht_vr', 'nacht_za'] as $key) { $shift = Shift::create([ 'festival_section_id' => $security->id, 'time_slot_id' => $fSlots[$key]->id, 'title' => 'Beveiliger', 'slots_total' => 6, 'slots_open_for_claiming' => 0, 'status' => 'open', ]); $allShifts[] = $shift; $s["security_{$key}"] = $shift; } // Security shifts using sub-event CREW time slots (cross_event exception) foreach (['vr_crew', 'za_crew'] as $key) { $shift = Shift::create([ 'festival_section_id' => $security->id, 'time_slot_id' => $ts[$key]->id, 'title' => 'Beveiliger', 'slots_total' => 4, 'slots_open_for_claiming' => 0, 'status' => 'open', ]); $allShifts[] = $shift; $s["security_{$key}"] = $shift; } // Terreinploeg (standard on festival): shifts use festival time slots only 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 (cross_event): shifts use sub-event time slots foreach (['vr_middag', 'za_middag', 'zo_middag', '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_middag', 'vr_avond'], 'zaterdag' => ['za_middag', 'za_avond'], 'zondag' => ['zo_middag', '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 ([ ['first_name' => 'Jan', 'last_name' => 'de Vries', 'email' => 'jan@gmail.com'], ['first_name' => 'Ahmed', 'last_name' => 'Hassan', 'email' => 'ahmed.h@gmail.com'], ['first_name' => 'Tom', 'last_name' => 'Visser', 'email' => 'tom.visser@gmail.com'], ['first_name' => 'Lotte', 'last_name' => 'de Jong', 'email' => 'lotte@gmail.com'], ['first_name' => 'Pieter', 'last_name' => 'Geluid', 'email' => 'pieter@podiumtechniek.nl'], ] as $u) { $volunteerUsers[$u['email']] = User::create([ 'first_name' => $u['first_name'], 'last_name' => $u['last_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, 'first_name' => 'Jan', 'last_name' => 'de Vries', 'email' => 'jan@gmail.com', 'phone' => '+31612345001', 'status' => 'approved', 'date_of_birth' => '1995-03-15']); $lisaB = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Lisa', 'last_name' => 'Bakker', 'email' => 'lisa.bakker@hotmail.com', 'phone' => '+31612345002', 'status' => 'approved', 'date_of_birth' => '1998-07-22']); $ahmedP = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'user_id' => $volunteerUsers['ahmed.h@gmail.com']->id, 'first_name' => 'Ahmed', 'last_name' => 'Hassan', 'email' => 'ahmed.h@gmail.com', 'phone' => '+31612345003', 'status' => 'approved', 'date_of_birth' => '1992-11-08']); $saraJ = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Sara', 'last_name' => 'Jansen', 'email' => 'sara.j@outlook.com', 'phone' => '+31612345004', 'status' => 'approved', 'date_of_birth' => '2000-01-30']); $tomV = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'user_id' => $volunteerUsers['tom.visser@gmail.com']->id, 'first_name' => 'Tom', 'last_name' => 'Visser', 'email' => 'tom.visser@gmail.com', 'phone' => '+31612345005', 'status' => 'approved', 'date_of_birth' => '1997-06-14']); $fatima = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Fatima', 'last_name' => 'El Amrani', 'email' => 'fatima@gmail.com', 'phone' => '+31612345006', 'status' => 'approved', 'date_of_birth' => '1996-05-20']); $daan = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Daan', 'last_name' => 'Smit', 'email' => 'daan.smit@gmail.com', 'phone' => '+31612345007', 'status' => 'pending', 'date_of_birth' => '1993-08-11']); Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Sophie', 'last_name' => 'Mulder', 'email' => 'sophie.m@hotmail.com', 'phone' => '+31612345008', 'status' => 'pending', 'date_of_birth' => '2001-02-28']); Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Jesse', 'last_name' => 'van Dijk', 'email' => 'jesse@gmail.com', 'phone' => '+31612345009', 'status' => 'applied', 'date_of_birth' => '1999-10-05']); Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Noa', 'last_name' => 'Hendriks', 'email' => 'noa.h@outlook.com', 'phone' => '+31612345010', 'status' => 'applied', 'date_of_birth' => '2000-07-19']); Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Kevin', 'last_name' => 'Bos', 'email' => 'kevin.bos@gmail.com', 'phone' => '+31612345011', 'status' => 'rejected', 'admin_notes' => 'Vorig jaar no-show', 'date_of_birth' => '1994-12-01']); Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Priya', 'last_name' => 'Sharma', 'email' => 'priya@gmail.com', 'phone' => '+31612345012', 'status' => 'invited', 'date_of_birth' => '1998-03-27']); $lotte = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'user_id' => $volunteerUsers['lotte@gmail.com']->id, 'first_name' => 'Lotte', 'last_name' => 'de Jong', 'email' => 'lotte@gmail.com', 'phone' => '+31612345013', 'status' => 'approved', 'date_of_birth' => '1994-09-25']); $robin = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Robin', 'last_name' => 'Peters', 'email' => 'robin.p@hotmail.com', 'phone' => '+31612345014', 'status' => 'approved', 'date_of_birth' => '1991-12-03']); $emma = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Emma', 'last_name' => 'Willems', 'email' => 'emma.w@gmail.com', 'phone' => '+31612345015', 'status' => 'no_show', 'is_blacklisted' => true, 'date_of_birth' => '1999-04-17']); // 6 named crew $klaas = Person::create(['event_id' => $festival->id, 'crowd_type_id' => $crew, 'company_id' => $this->companies['SecureEvent BV']->id, 'first_name' => 'Klaas', 'last_name' => '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, 'first_name' => 'Dennis', 'last_name' => '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, 'first_name' => 'Pieter', 'last_name' => '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, 'first_name' => 'Marco', 'last_name' => '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, 'first_name' => 'Eva', 'last_name' => '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, 'first_name' => 'Maria', 'last_name' => 'van Dijk', 'email' => 'maria@vandijkcatering.nl', 'phone' => '+31612345021', 'status' => 'approved']); // 3 named press Person::create(['event_id' => $festival->id, 'crowd_type_id' => $press, 'first_name' => 'Joris', 'last_name' => 'van Laar', 'email' => 'joris@pers.nl', 'phone' => '+31612345022', 'status' => 'approved']); Person::create(['event_id' => $festival->id, 'crowd_type_id' => $press, 'first_name' => 'Tamara', 'last_name' => 'Smeets', 'email' => 'tamara@pers.nl', 'phone' => '+31612345023', 'status' => 'approved']); Person::create(['event_id' => $festival->id, 'crowd_type_id' => $press, 'first_name' => 'Sven', 'last_name' => 'Pieterse', 'email' => 'sven@pers.nl', 'phone' => '+31612345024', 'status' => 'pending']); // 4 named guests Person::create(['event_id' => $festival->id, 'crowd_type_id' => $guest, 'first_name' => 'Jan', 'last_name' => 'Slagter', 'email' => 'burgemeester@echt.nl', 'phone' => '+31612345025', 'status' => 'approved']); Person::create(['event_id' => $festival->id, 'crowd_type_id' => $guest, 'first_name' => 'Petra', 'last_name' => 'Kamps', 'email' => 'wethouder@echt.nl', 'phone' => '+31612345026', 'status' => 'approved']); Person::create(['event_id' => $festival->id, 'crowd_type_id' => $guest, 'first_name' => 'Jan', 'last_name' => 'Sponsor', 'email' => 'sponsor@bedrijf.nl', 'phone' => '+31612345027', 'status' => 'approved']); Person::create(['event_id' => $festival->id, 'crowd_type_id' => $guest, 'first_name' => 'Gast', 'last_name' => '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, 'first_name' => 'Hans', 'last_name' => '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, 'first_name' => 'Frank', 'last_name' => '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']); // ── Identity match test data ── // Persons whose emails match org member accounts (for email-based identity matching) Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Lisa', 'last_name' => 'van den Berg', 'email' => 'lisa@feestfabriek.nl', 'phone' => '+31612345040', 'status' => 'applied', 'date_of_birth' => '1993-05-12']); Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Sara', 'last_name' => 'de Groot', 'email' => 'sara@feestfabriek.nl', 'phone' => '+31612345041', 'status' => 'pending', 'date_of_birth' => '1991-08-24']); // Person with fuzzy name match to org member "Nina Jansen" (different email) Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Nena', 'last_name' => 'Jansen', 'email' => 'nena.jansen@gmail.com', 'phone' => '+31612345042', 'status' => 'pending', 'date_of_birth' => null]); // Person with fuzzy name match + DOB match to org member "Mark de Boer" (DOB already set in user creation) Person::create(['event_id' => $festival->id, 'crowd_type_id' => $vol, 'first_name' => 'Marc', 'last_name' => 'de Boer', 'email' => 'marc.deboer@gmail.com', 'phone' => '+31612345043', 'status' => 'pending', 'date_of_birth' => '1988-03-17']); // 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']); $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_middag', 'status' => ShiftAssignmentStatus::APPROVED, 'auto' => true], ['person' => $jan, 'shift' => 'hoofdbar_vr_avond', 'status' => ShiftAssignmentStatus::APPROVED, 'auto' => false], ['person' => $jan, 'shift' => 'hoofdbar_za_middag', 'status' => ShiftAssignmentStatus::APPROVED], // Lisa Bakker ['person' => $lisaB, 'shift' => 'hoofdbar_vr_middag', 'status' => ShiftAssignmentStatus::APPROVED, 'auto' => true], ['person' => $lisaB, 'shift' => 'hoofdbar_za_middag', 'status' => ShiftAssignmentStatus::PENDING_APPROVAL], ['person' => $lisaB, 'shift' => 'theaterbar_zo_middag', 'status' => ShiftAssignmentStatus::APPROVED, 'auto' => true], // Ahmed Hassan: 4 EHBO shifts (cross_event section using sub-event time slots) ['person' => $ahmedP, 'shift' => 'ehbo_vr_middag', 'status' => ShiftAssignmentStatus::APPROVED], ['person' => $ahmedP, 'shift' => 'ehbo_vr_avond', 'status' => ShiftAssignmentStatus::APPROVED], ['person' => $ahmedP, 'shift' => 'ehbo_za_middag', '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_middag', 'status' => ShiftAssignmentStatus::APPROVED, 'auto' => true], // Klaas: security, assigned by Tom ['person' => $klaas, 'shift' => 'security_nacht_vr', 'status' => ShiftAssignmentStatus::APPROVED, 'assigned_by' => $tom->id], // Dennis: security ['person' => $dennis, 'shift' => 'security_nacht_za', 'status' => ShiftAssignmentStatus::APPROVED, 'assigned_by' => $tom->id], // Daan: rejected ['person' => $daan, 'shift' => 'hoofdbar_vr_middag', 'status' => ShiftAssignmentStatus::REJECTED, 'reason' => 'Nog niet goedgekeurd'], // Emma: cancelled ['person' => $emma, 'shift' => 'ingang_vr_middag', '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; } // ── Intentional overbooking for UI testing ── $overbookPersons = Person::where('event_id', $festival->id) ->where('status', 'approved') ->whereNotIn('id', array_keys($usedPersonSlots)) ->limit(20) ->get(); // EHBO Vrijdag Middag: slots_total=3, target 4 approved (1 over) // Already has 1 named approved (Ahmed). Add 3 more = 4 total. $ehboOverbookTarget = 3; $ehboShift = $s['ehbo_vr_middag']; foreach ($overbookPersons->splice(0, $ehboOverbookTarget) as $person) { ShiftAssignment::create([ 'shift_id' => $ehboShift->id, 'person_id' => $person->id, 'time_slot_id' => $ehboShift->time_slot_id, 'status' => ShiftAssignmentStatus::APPROVED, 'auto_approved' => false, 'assigned_at' => now(), 'approved_at' => now(), ]); $usedPersonSlots[$person->id][] = $ehboShift->time_slot_id; } // Terreinploeg Opbouw Dag 1: slots_total=15, target 17 approved (2 over) // Already has 1 named approved (Lotte). Add 16 more = 17 total. $terreinOverbookTarget = 16; $terreinShift = $s['terrein_opbouw1']; $terreinOverbookPersons = Person::where('event_id', $festival->id) ->where('status', 'approved') ->whereNotIn('id', collect($usedPersonSlots)->keys()->filter( fn ($pid) => in_array($terreinShift->time_slot_id, $usedPersonSlots[$pid] ?? []), )->values()) ->limit($terreinOverbookTarget) ->get(); foreach ($terreinOverbookPersons as $person) { ShiftAssignment::create([ 'shift_id' => $terreinShift->id, 'person_id' => $person->id, 'time_slot_id' => $terreinShift->time_slot_id, 'status' => ShiftAssignmentStatus::APPROVED, 'auto_approved' => true, 'assigned_at' => now(), 'approved_at' => now(), ]); $usedPersonSlots[$person->id][] = $terreinShift->time_slot_id; } // ── Factory shift assignments (~100) ── // Track filled (approved/completed) counts per shift to respect slots_total $shiftFilledCounts = []; $existingAssignments = ShiftAssignment::whereIn('shift_id', collect($allShifts)->pluck('id')) ->whereIn('status', [ShiftAssignmentStatus::APPROVED, ShiftAssignmentStatus::COMPLETED]) ->get() ->groupBy('shift_id'); foreach ($allShifts as $shift) { $shiftFilledCounts[$shift->id] = ($existingAssignments[$shift->id] ?? collect())->count(); } $approvedPersons = Person::where('event_id', $festival->id) ->where('status', 'approved') ->get(); $openShifts = collect($allShifts)->filter(fn (Shift $shift) => $shift->status === 'open' && $shift->slots_open_for_claiming > 0); $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]); // Skip approved assignments if shift is already at capacity if ($isApproved && ($shiftFilledCounts[$shift->id] ?? 0) >= $shift->slots_total) { continue; } 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, ]); if ($isApproved) { $shiftFilledCounts[$shift->id] = ($shiftFilledCounts[$shift->id] ?? 0) + 1; } $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"); // Auto-correct shift statuses based on actual fill foreach ($allShifts as $shift) { $filled = ShiftAssignment::where('shift_id', $shift->id) ->whereIn('status', [ShiftAssignmentStatus::APPROVED, ShiftAssignmentStatus::COMPLETED]) ->count(); $correctStatus = $filled >= $shift->slots_total ? 'full' : 'open'; if ($shift->status !== $correctStatus) { $shift->update(['status' => $correctStatus]); } } // ── Volunteer availabilities (~70) ── $volSlotKeys = ['vr_middag', 'vr_avond', 'za_middag', 'za_avond', 'zo_middag', '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_middag'], $ts['za_avond'], $ts['zo_middag']]), 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: Braderie Dorpstown 2026 (flat event, registration_open) // ========================================================================= private function seedBraderie(): void { DB::transaction(function (): void { $this->command->info('Seeding Braderie Dorpstown 2026...'); $braderie = Event::create([ 'organisation_id' => $this->org->id, 'name' => 'Braderie Dorpstown 2026', 'slug' => 'braderie-dorpstown-2026', 'start_date' => '2026-06-14', 'end_date' => '2026-06-14', 'timezone' => 'Europe/Amsterdam', 'status' => 'registration_open', 'event_type' => 'event', ]); // ── Time Slots (3) ── $tsOchtend = TimeSlot::create(['event_id' => $braderie->id, 'name' => 'Zondag ochtend — vrijwilliger', 'person_type' => 'VOLUNTEER', 'date' => '2026-06-14', 'start_time' => '07:00', 'end_time' => '13:00', 'duration_hours' => 6.00]); $tsMiddag = TimeSlot::create(['event_id' => $braderie->id, 'name' => 'Zondag middag — vrijwilliger', 'person_type' => 'VOLUNTEER', 'date' => '2026-06-14', 'start_time' => '13:00', 'end_time' => '18:00', 'duration_hours' => 5.00]); $tsCrew = TimeSlot::create(['event_id' => $braderie->id, 'name' => 'Zondag — crew', 'person_type' => 'CREW', 'date' => '2026-06-14', 'start_time' => '06:00', 'end_time' => '20:00', 'duration_hours' => 14.00]); // ── Sections (2) ── $verkeersregelaars = FestivalSection::create([ 'event_id' => $braderie->id, 'name' => 'Verkeersregelaars', 'type' => 'standard', 'category' => 'Veiligheid', 'icon' => 'tabler-traffic-cone', 'sort_order' => 1, 'responder_self_checkin' => true, 'crew_auto_accepts' => false, 'show_in_registration' => true, 'registration_description' => 'Verkeersregelaar bij de braderie', ]); $centraleBar = FestivalSection::create([ 'event_id' => $braderie->id, 'name' => 'Centrale bar', 'type' => 'standard', 'category' => 'Bar', 'icon' => 'tabler-beer', 'sort_order' => 2, 'responder_self_checkin' => true, 'crew_auto_accepts' => true, 'show_in_registration' => true, 'registration_description' => 'Tappen en serveren bij de centrale bar', ]); // ── Shifts (4) ── Shift::create(['festival_section_id' => $verkeersregelaars->id, 'time_slot_id' => $tsOchtend->id, 'title' => 'Verkeersregelaar', 'slots_total' => 4, 'slots_open_for_claiming' => 4, 'status' => 'open']); Shift::create(['festival_section_id' => $verkeersregelaars->id, 'time_slot_id' => $tsMiddag->id, 'title' => 'Verkeersregelaar', 'slots_total' => 4, 'slots_open_for_claiming' => 4, 'status' => 'open']); Shift::create(['festival_section_id' => $centraleBar->id, 'time_slot_id' => $tsOchtend->id, 'title' => 'Tapper', 'slots_total' => 6, 'slots_open_for_claiming' => 6, 'status' => 'open']); Shift::create(['festival_section_id' => $centraleBar->id, 'time_slot_id' => $tsMiddag->id, 'title' => 'Tapper', 'slots_total' => 6, 'slots_open_for_claiming' => 6, 'status' => 'open']); // ── Persons (15) ── $vol = $this->crowdTypes['VOLUNTEER']->id; Person::factory()->count(10)->approved()->create(['event_id' => $braderie->id, 'crowd_type_id' => $vol]); 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->command->info(' Braderie Dorpstown 2026 complete'); }); } // ========================================================================= // Event 3: 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', 'show_in_registration' => true, 'registration_description' => 'Warme dranken en snacks serveren aan schaatsers'], ['name' => 'Schaatsverhuur', 'category' => 'Ontvangst', 'icon' => 'tabler-ticket', 'show_in_registration' => true, 'registration_description' => 'Schaatsen uitgeven en innemen bij de verhuurbalie'], ['name' => 'Terrein', 'category' => 'Productie', 'icon' => 'tabler-shovel', 'show_in_registration' => true, 'registration_description' => 'IJsbaan onderhoud en terreinbeheer'], ]; $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, 'show_in_registration' => $sd['show_in_registration'] ?? false, 'registration_description' => $sd['registration_description'] ?? null, ]); } // 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' && $shift->slots_open_for_claiming > 0); $draftShifts = collect($allShifts)->filter(fn (Shift $shift) => $shift->status === 'draft'); $allApproved = $approvedVol->merge($approvedCrew); // Week 1+2: up to slots_total per open shift foreach ($openShifts as $shift) { $count = min(rand(5, 6), $shift->slots_total); $assigned = $allApproved->shuffle()->take($count); 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, 'show_in_registration' => true, 'registration_description' => 'Podiummedewerker bij de Erasmusbrug']); $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, 'show_in_registration' => true, 'registration_description' => 'Tappen en serveren op het Willemsplein']); $secKids = FestivalSection::create(['event_id' => $koningsdag->id, 'name' => 'Kinderactiviteiten', 'type' => 'standard', 'category' => 'Entertainment', 'icon' => 'tabler-balloon', 'sort_order' => 3, 'responder_self_checkin' => true, 'show_in_registration' => true, 'registration_description' => 'Begeleid kinderactiviteiten en spelletjes']); $secBev = FestivalSection::create(['event_id' => $koningsdag->id, 'name' => 'Beveiliging', 'type' => 'standard', 'category' => 'Veiligheid', 'icon' => 'tabler-shield', 'sort_order' => 4, 'responder_self_checkin' => true, 'show_in_registration' => true, 'registration_description' => 'Beveiliging en crowd management']); $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; } }