Files
crewli/api/database/seeders/DevSeeder.php
bert.hausmans 853271f0c2 feat: comprehensive DevSeeder with 4 events and 310+ persons
Replace minimal DevSeeder with full-scale test data simulating
Stichting Feestfabriek managing 4 diverse events:
- Echt Feesten 2026: festival, 150 persons, ~45 shifts, ~275 assignments
- IJsbaan Winterpark: series, 60 persons, 24 shifts
- Koningsdag Rotterdam: flat closed event, 100 persons, 12 completed shifts
- Nacht van de Kaap: empty draft event

Includes 8 users, 6 companies, 7 crowd types, 10 person tags,
crowd lists, volunteer availabilities, event person activations,
and user organisation tags with proficiency levels.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 17:28:25 +02:00

1185 lines
69 KiB
PHP

<?php
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<string, User> */
private array $users = [];
/** @var array<string, CrowdType> */
private array $crowdTypes = [];
/** @var array<string, Company> */
private array $companies = [];
/** @var array<string, \App\Models\PersonTag> */
private array $personTags = [];
public function run(): void
{
$this->call(RoleSeeder::class);
$this->seedOrganisation();
$this->seedEchtFeesten();
$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', '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'],
];
foreach ($usersData as $data) {
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'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_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'],
];
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;
}
$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',
'status' => 'registration_open',
'event_type' => 'festival',
'event_type_label' => 'Festival',
'sub_event_label' => 'Programmaonderdeel',
]);
$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',
]);
$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',
]);
$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' => '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,
]);
$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,
]);
$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,
]);
$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 sections (5 per sub-event) ──
$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],
];
$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 time slots (5) ──
$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]);
// ── Sub-event time slots ──
$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;
}
}