Files
crewli/api/database/seeders/ArtistTimetableDevSeeder.php
bert.hausmans 3e3636dc53 feat(timetable): factories + ArtistTimetableDevSeeder
Eight factories with named states (Genre, Artist, ArtistContact, Stage,
ArtistEngagement, Performance, AdvanceSection, AdvanceSubmission).

ArtistTimetableDevSeeder hooked into DevSeeder::seedEchtFeesten after
the form-builder showcase. Produces:
- 4 stages (Mainstage, Havana, Stairway, Socialite) with prototype-style
  hex colours
- 4 stages × 3 sub-events = 12 stage_days rows
- 4 genres (Hardstyle, Techno, Indie, Live band)
- 6 master artists, each with one tour-manager ArtistContact
- 12 engagements with status mix (1 Draft, 2 Requested, 3 Option,
  2 Confirmed, 3 Contracted, 1 Cancelled). Two artists have two
  engagements each (different sub-events) — exercises D17 multi-
  engagement-per-artist.
- 13 performances, including one parked (stage_id=null = wachtrij)
  and one B2B pair within 3 minutes on Mainstage Saturday to seed
  the Session 4 frontend B2B detector.

Also fix LogOptions method name across 8 models: dontSubmitEmptyLogs()
→ dontLogEmptyChanges() (Spatie's actual API; surfaced when DevSeeder
ran).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 18:08:16 +02:00

210 lines
9.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace Database\Seeders;
use App\Enums\Artist\ArtistEngagementStatus;
use App\Models\Artist;
use App\Models\ArtistContact;
use App\Models\ArtistEngagement;
use App\Models\Event;
use App\Models\Genre;
use App\Models\Organisation;
use App\Models\Performance;
use App\Models\Stage;
use App\Models\StageDay;
use Carbon\CarbonImmutable;
/**
* Seeds the Artist & Timetable fixture for an existing festival with
* three sub-events (e.g. Echt Feesten 2026 → Vrijdag/Zaterdag/Zondag).
*
* Reproduces the prototype audit fixture as closely as possible:
* - 4 stages on the festival
* - 4 stages × 3 sub-events = 12 stage_days rows
* - 4 genres, 6 master artists with default genre + draw
* - 12 engagements with status mix (Draft 1, Requested 2, Option 3,
* Confirmed 2, Contracted 3, Cancelled 1). Two artists each have
* two engagements (one per day) to exercise the multi-engagement
* case (D17).
* - 13 performances; one parked (stage_id=null); a B2B pair within
* 3 minutes on the same stage/lane to seed Session 4 frontend dev.
* - One ArtistContact per artist (tour-manager role).
*
* advance_sections seeding lands in Session 3 with the form-builder
* integration.
*/
final class ArtistTimetableDevSeeder
{
/** @param array<int, Event> $subEvents Sub-events keyed 0..n in date order */
public static function seedForFestival(Organisation $org, Event $festival, array $subEvents): void
{
if (count($subEvents) < 3) {
return;
}
// Genres
$genres = collect([
['name' => 'Hardstyle', 'color' => '#e85d75'],
['name' => 'Techno', 'color' => '#1f6feb'],
['name' => 'Indie', 'color' => '#7c3aed'],
['name' => 'Live band', 'color' => '#0ea5e9'],
])->mapWithKeys(fn (array $g) => [
$g['name'] => Genre::create([
'organisation_id' => $org->id,
'name' => $g['name'],
'color' => $g['color'],
'sort_order' => 0,
'is_active' => true,
]),
]);
// Stages on the festival
$stages = collect([
['name' => 'Mainstage', 'color' => '#e85d75', 'capacity' => 4500, 'sort_order' => 1],
['name' => 'Havana', 'color' => '#0ea5e9', 'capacity' => 1200, 'sort_order' => 2],
['name' => 'Stairway', 'color' => '#7c3aed', 'capacity' => 800, 'sort_order' => 3],
['name' => 'Socialite', 'color' => '#22c55e', 'capacity' => 600, 'sort_order' => 4],
])->mapWithKeys(fn (array $s) => [
$s['name'] => Stage::create(['event_id' => $festival->id, ...$s]),
]);
// stage_days — every stage active every sub-event day
foreach ($stages as $stage) {
foreach ($subEvents as $subEvent) {
StageDay::create([
'stage_id' => $stage->id,
'event_id' => $subEvent->id,
]);
}
}
// Master artists
$artistsData = [
['name' => 'Donker & Licht', 'genre' => 'Hardstyle', 'draw' => 3500, 'star' => 5],
['name' => 'Voltage Collective', 'genre' => 'Techno', 'draw' => 1800, 'star' => 4],
['name' => 'Roos & de Wolf', 'genre' => 'Indie', 'draw' => 700, 'star' => 3],
['name' => 'Rotterdam Brass', 'genre' => 'Live band', 'draw' => 900, 'star' => 4],
['name' => 'Nachtwacht DJs', 'genre' => 'Techno', 'draw' => 1100, 'star' => 3],
['name' => 'De Lichtbrigade', 'genre' => 'Live band', 'draw' => 500, 'star' => 3],
];
$artists = [];
foreach ($artistsData as $data) {
/** @var Artist $artist */
$artist = Artist::create([
'organisation_id' => $org->id,
'name' => $data['name'],
'default_genre_id' => $genres[$data['genre']]->id,
'default_draw' => $data['draw'],
'star_rating' => $data['star'],
'home_base_country' => 'NL',
]);
$artists[$data['name']] = $artist;
ArtistContact::create([
'artist_id' => $artist->id,
'name' => 'Tour Manager '.$artist->name,
'email' => 'tm-'.$artist->slug.'@example.test',
'phone' => '+31612340000',
'role' => 'tour_manager',
'is_primary' => true,
'receives_briefing' => true,
'receives_infosheet' => true,
]);
}
// Engagements (12) — status mix per RFC §5.3 Session 1 prompt.
// Two artists get two engagements (different days) to exercise D17.
$statusPlan = [
['artist' => 'Donker & Licht', 'sub' => 0, 'status' => ArtistEngagementStatus::Contracted],
['artist' => 'Donker & Licht', 'sub' => 1, 'status' => ArtistEngagementStatus::Contracted],
['artist' => 'Voltage Collective', 'sub' => 0, 'status' => ArtistEngagementStatus::Confirmed],
['artist' => 'Voltage Collective', 'sub' => 1, 'status' => ArtistEngagementStatus::Option],
['artist' => 'Roos & de Wolf', 'sub' => 1, 'status' => ArtistEngagementStatus::Contracted],
['artist' => 'Rotterdam Brass', 'sub' => 0, 'status' => ArtistEngagementStatus::Confirmed],
['artist' => 'Rotterdam Brass', 'sub' => 2, 'status' => ArtistEngagementStatus::Requested],
['artist' => 'Nachtwacht DJs', 'sub' => 1, 'status' => ArtistEngagementStatus::Option],
['artist' => 'Nachtwacht DJs', 'sub' => 2, 'status' => ArtistEngagementStatus::Cancelled],
['artist' => 'De Lichtbrigade', 'sub' => 0, 'status' => ArtistEngagementStatus::Draft],
['artist' => 'De Lichtbrigade', 'sub' => 1, 'status' => ArtistEngagementStatus::Requested],
['artist' => 'De Lichtbrigade', 'sub' => 2, 'status' => ArtistEngagementStatus::Option],
];
$engagements = [];
foreach ($statusPlan as $idx => $plan) {
/** @var Event $subEvent */
$subEvent = $subEvents[$plan['sub']];
$artist = $artists[$plan['artist']];
$attrs = [
'artist_id' => $artist->id,
'event_id' => $subEvent->id,
'booking_status' => $plan['status'],
'fee_currency' => 'EUR',
'buma_applicable' => true,
'buma_percentage' => 7.00,
'buma_handled_by' => 'organisation',
'vat_applicable' => true,
'vat_percentage' => 21.00,
'payment_status' => 'none',
'crew_count' => 2,
'guests_count' => 4,
'advancing_completed_count' => 0,
'advancing_total_count' => 0,
];
if ($plan['status'] === ArtistEngagementStatus::Option) {
$attrs['option_expires_at'] = CarbonImmutable::now()->addDays(14);
}
if ($plan['status'] === ArtistEngagementStatus::Requested) {
$attrs['requested_at'] = CarbonImmutable::now()->subDays(3);
}
if ($plan['status'] === ArtistEngagementStatus::Contracted) {
$attrs['fee_amount'] = 7500.00;
}
$engagements[$idx] = ArtistEngagement::create($attrs);
}
// Performances (13) — most engagements get 1 perf, a couple
// get 2 (D17). One parked. One B2B pair on Mainstage Saturday.
$perfPlan = [
// [engagementIdx, stageName|null, subIdx, hour, minute, duration]
[0, 'Mainstage', 0, 22, 0, 75], // Donker & Licht — Vrijdag mainstage
[1, 'Mainstage', 1, 23, 30, 60], // Donker & Licht — Zaterdag mainstage (B2B partner-A)
[1, 'Mainstage', 1, 21, 0, 60], // …extra perf same engagement (D17 multi-perf)
[2, 'Havana', 0, 21, 0, 60],
[3, 'Havana', 1, 22, 30, 60],
[4, 'Stairway', 1, 21, 0, 75],
[5, 'Socialite', 0, 19, 0, 60],
[6, 'Socialite', 2, 17, 0, 45],
[7, 'Mainstage', 1, 23, 33, 60], // B2B partner-B (3-min offset → seeds B2B detector)
[9, null, 0, 0, 0, 60], // De Lichtbrigade Vrijdag = parked / wachtrij
[10, 'Stairway', 1, 19, 0, 60],
[11, 'Havana', 2, 20, 30, 60],
[4, 'Stairway', 1, 22, 30, 60], // multi-perf on same engagement (D17)
];
foreach ($perfPlan as $row) {
[$engagementIdx, $stageName, $subIdx, $hour, $minute, $minutes] = $row;
$engagement = $engagements[$engagementIdx];
/** @var Event $subEvent */
$subEvent = $subEvents[$subIdx];
$start = CarbonImmutable::parse($subEvent->start_date)->setTime($hour, $minute);
Performance::create([
'engagement_id' => $engagement->id,
'event_id' => $subEvent->id,
'stage_id' => $stageName === null ? null : $stages[$stageName]->id,
'lane' => 0,
'start_at' => $start,
'end_at' => $start->addMinutes($minutes),
'version' => 0,
]);
}
}
}