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>
This commit is contained in:
@@ -64,7 +64,7 @@ final class AdvanceSection extends Model
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logFillable()
|
||||
->dontSubmitEmptyLogs();
|
||||
->dontLogEmptyChanges();
|
||||
}
|
||||
|
||||
public function engagement(): BelongsTo
|
||||
|
||||
@@ -55,7 +55,7 @@ final class AdvanceSubmission extends Model
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logFillable()
|
||||
->dontSubmitEmptyLogs();
|
||||
->dontLogEmptyChanges();
|
||||
}
|
||||
|
||||
public function section(): BelongsTo
|
||||
|
||||
@@ -57,7 +57,7 @@ final class Artist extends Model
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logFillable()
|
||||
->dontSubmitEmptyLogs();
|
||||
->dontLogEmptyChanges();
|
||||
}
|
||||
|
||||
private function generateUniqueSlug(string $name): string
|
||||
|
||||
@@ -54,7 +54,7 @@ final class ArtistContact extends Model
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logFillable()
|
||||
->dontSubmitEmptyLogs();
|
||||
->dontLogEmptyChanges();
|
||||
}
|
||||
|
||||
public function artist(): BelongsTo
|
||||
|
||||
@@ -88,7 +88,7 @@ final class ArtistEngagement extends Model
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logFillable()
|
||||
->dontSubmitEmptyLogs();
|
||||
->dontLogEmptyChanges();
|
||||
}
|
||||
|
||||
public function organisation(): BelongsTo
|
||||
|
||||
@@ -44,7 +44,7 @@ final class Genre extends Model
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logFillable()
|
||||
->dontSubmitEmptyLogs();
|
||||
->dontLogEmptyChanges();
|
||||
}
|
||||
|
||||
public function organisation(): BelongsTo
|
||||
|
||||
@@ -56,7 +56,7 @@ final class Performance extends Model
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logFillable()
|
||||
->dontSubmitEmptyLogs();
|
||||
->dontLogEmptyChanges();
|
||||
}
|
||||
|
||||
public function engagement(): BelongsTo
|
||||
|
||||
@@ -51,7 +51,7 @@ final class Stage extends Model
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->logFillable()
|
||||
->dontSubmitEmptyLogs();
|
||||
->dontLogEmptyChanges();
|
||||
}
|
||||
|
||||
public function event(): BelongsTo
|
||||
|
||||
46
api/database/factories/AdvanceSectionFactory.php
Normal file
46
api/database/factories/AdvanceSectionFactory.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\Artist\AdvanceSectionSubmissionStatus;
|
||||
use App\Enums\Artist\AdvanceSectionType;
|
||||
use App\Models\AdvanceSection;
|
||||
use App\Models\ArtistEngagement;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/** @extends Factory<AdvanceSection> */
|
||||
final class AdvanceSectionFactory extends Factory
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'engagement_id' => ArtistEngagement::factory(),
|
||||
'name' => fake()->randomElement(['Gastenlijst', 'Contacts', 'Productie', 'Catering']),
|
||||
'type' => fake()->randomElement(AdvanceSectionType::cases()),
|
||||
'is_open' => false,
|
||||
'sort_order' => 0,
|
||||
'submission_status' => AdvanceSectionSubmissionStatus::Open,
|
||||
];
|
||||
}
|
||||
|
||||
public function open(): static
|
||||
{
|
||||
return $this->state(fn () => [
|
||||
'is_open' => true,
|
||||
'open_from' => now()->subDays(7),
|
||||
'open_to' => now()->addDays(14),
|
||||
'submission_status' => AdvanceSectionSubmissionStatus::Open,
|
||||
]);
|
||||
}
|
||||
|
||||
public function approved(): static
|
||||
{
|
||||
return $this->state(fn () => [
|
||||
'submission_status' => AdvanceSectionSubmissionStatus::Approved,
|
||||
'last_submitted_at' => now()->subDay(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
27
api/database/factories/AdvanceSubmissionFactory.php
Normal file
27
api/database/factories/AdvanceSubmissionFactory.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\Artist\AdvanceSubmissionStatus;
|
||||
use App\Models\AdvanceSection;
|
||||
use App\Models\AdvanceSubmission;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/** @extends Factory<AdvanceSubmission> */
|
||||
final class AdvanceSubmissionFactory extends Factory
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'advance_section_id' => AdvanceSection::factory(),
|
||||
'submitted_by_name' => fake()->name(),
|
||||
'submitted_by_email' => fake()->safeEmail(),
|
||||
'submitted_at' => now()->subHours(fake()->numberBetween(1, 72)),
|
||||
'status' => AdvanceSubmissionStatus::Pending,
|
||||
'data' => ['payload' => fake()->sentence()],
|
||||
];
|
||||
}
|
||||
}
|
||||
42
api/database/factories/ArtistContactFactory.php
Normal file
42
api/database/factories/ArtistContactFactory.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Artist;
|
||||
use App\Models\ArtistContact;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/** @extends Factory<ArtistContact> */
|
||||
final class ArtistContactFactory extends Factory
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'artist_id' => Artist::factory(),
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->safeEmail(),
|
||||
'phone' => fake()->phoneNumber(),
|
||||
'role' => fake()->randomElement(['tour_manager', 'agent', 'manager', 'production_manager']),
|
||||
'is_primary' => false,
|
||||
'receives_briefing' => false,
|
||||
'receives_infosheet' => false,
|
||||
];
|
||||
}
|
||||
|
||||
public function primary(): static
|
||||
{
|
||||
return $this->state(fn () => [
|
||||
'is_primary' => true,
|
||||
'receives_briefing' => true,
|
||||
'receives_infosheet' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function tourManager(): static
|
||||
{
|
||||
return $this->state(fn () => ['role' => 'tour_manager']);
|
||||
}
|
||||
}
|
||||
84
api/database/factories/ArtistEngagementFactory.php
Normal file
84
api/database/factories/ArtistEngagementFactory.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\Artist\ArtistEngagementStatus;
|
||||
use App\Models\Artist;
|
||||
use App\Models\ArtistEngagement;
|
||||
use App\Models\Event;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/** @extends Factory<ArtistEngagement> */
|
||||
final class ArtistEngagementFactory extends Factory
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
// organisation_id is set by the observer from the artist;
|
||||
// factory leaves it null and lets the observer denormalise.
|
||||
'artist_id' => Artist::factory(),
|
||||
'event_id' => Event::factory(),
|
||||
'booking_status' => ArtistEngagementStatus::Draft,
|
||||
'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' => 0,
|
||||
'guests_count' => 0,
|
||||
'advancing_completed_count' => 0,
|
||||
'advancing_total_count' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
public function draft(): static
|
||||
{
|
||||
return $this->state(fn () => ['booking_status' => ArtistEngagementStatus::Draft]);
|
||||
}
|
||||
|
||||
public function requested(): static
|
||||
{
|
||||
return $this->state(fn () => [
|
||||
'booking_status' => ArtistEngagementStatus::Requested,
|
||||
'requested_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function option(): static
|
||||
{
|
||||
return $this->state(fn () => [
|
||||
'booking_status' => ArtistEngagementStatus::Option,
|
||||
'option_expires_at' => now()->addDays(14),
|
||||
]);
|
||||
}
|
||||
|
||||
public function offered(): static
|
||||
{
|
||||
return $this->state(fn () => ['booking_status' => ArtistEngagementStatus::Offered]);
|
||||
}
|
||||
|
||||
public function confirmed(): static
|
||||
{
|
||||
return $this->state(fn () => ['booking_status' => ArtistEngagementStatus::Confirmed]);
|
||||
}
|
||||
|
||||
public function contracted(): static
|
||||
{
|
||||
return $this->state(fn () => [
|
||||
'booking_status' => ArtistEngagementStatus::Contracted,
|
||||
'fee_amount' => fake()->randomFloat(2, 500, 25000),
|
||||
'portal_token' => (string) Str::ulid(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function cancelled(): static
|
||||
{
|
||||
return $this->state(fn () => ['booking_status' => ArtistEngagementStatus::Cancelled]);
|
||||
}
|
||||
}
|
||||
57
api/database/factories/ArtistFactory.php
Normal file
57
api/database/factories/ArtistFactory.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Artist;
|
||||
use App\Models\Company;
|
||||
use App\Models\Genre;
|
||||
use App\Models\Organisation;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/** @extends Factory<Artist> */
|
||||
final class ArtistFactory extends Factory
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function definition(): array
|
||||
{
|
||||
$name = fake()->unique()->company().' '.fake()->randomElement(['Live', 'Sound', 'Project', 'Collective', 'DJ Set']);
|
||||
|
||||
return [
|
||||
'organisation_id' => Organisation::factory(),
|
||||
'name' => $name,
|
||||
'slug' => Str::slug($name).'-'.Str::lower(Str::random(4)),
|
||||
'default_genre_id' => null,
|
||||
'default_draw' => fake()->numberBetween(50, 5000),
|
||||
'star_rating' => fake()->numberBetween(1, 5),
|
||||
'home_base_country' => fake()->randomElement(['NL', 'BE', 'DE', 'FR', 'UK']),
|
||||
'agent_company_id' => null,
|
||||
'notes' => null,
|
||||
];
|
||||
}
|
||||
|
||||
public function withGenre(?Genre $genre = null): static
|
||||
{
|
||||
return $this->state(function (array $attrs) use ($genre): array {
|
||||
$resolved = $genre ?? Genre::factory()->create([
|
||||
'organisation_id' => $attrs['organisation_id'],
|
||||
]);
|
||||
|
||||
return ['default_genre_id' => $resolved->id];
|
||||
});
|
||||
}
|
||||
|
||||
public function withAgent(?Company $company = null): static
|
||||
{
|
||||
return $this->state(function (array $attrs) use ($company): array {
|
||||
$resolved = $company ?? Company::factory()->create([
|
||||
'organisation_id' => $attrs['organisation_id'],
|
||||
'type' => 'agency',
|
||||
]);
|
||||
|
||||
return ['agent_company_id' => $resolved->id];
|
||||
});
|
||||
}
|
||||
}
|
||||
25
api/database/factories/GenreFactory.php
Normal file
25
api/database/factories/GenreFactory.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Genre;
|
||||
use App\Models\Organisation;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/** @extends Factory<Genre> */
|
||||
final class GenreFactory extends Factory
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'organisation_id' => Organisation::factory(),
|
||||
'name' => fake()->unique()->words(2, true),
|
||||
'color' => fake()->hexColor(),
|
||||
'sort_order' => 0,
|
||||
'is_active' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
48
api/database/factories/PerformanceFactory.php
Normal file
48
api/database/factories/PerformanceFactory.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\ArtistEngagement;
|
||||
use App\Models\Event;
|
||||
use App\Models\Performance;
|
||||
use App\Models\Stage;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/** @extends Factory<Performance> */
|
||||
final class PerformanceFactory extends Factory
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function definition(): array
|
||||
{
|
||||
$start = CarbonImmutable::now()->addDays(7)->setTime(20, 0);
|
||||
|
||||
return [
|
||||
'engagement_id' => ArtistEngagement::factory(),
|
||||
'event_id' => Event::factory(),
|
||||
'stage_id' => Stage::factory(),
|
||||
'lane' => 0,
|
||||
'start_at' => $start,
|
||||
'end_at' => $start->addMinutes(60),
|
||||
'version' => 0,
|
||||
'notes' => null,
|
||||
];
|
||||
}
|
||||
|
||||
public function parked(): static
|
||||
{
|
||||
return $this->state(fn () => ['stage_id' => null]);
|
||||
}
|
||||
|
||||
public function scheduled(Stage $stage, CarbonImmutable $start, int $minutes): static
|
||||
{
|
||||
return $this->state(fn () => [
|
||||
'stage_id' => $stage->id,
|
||||
'event_id' => $stage->event_id,
|
||||
'start_at' => $start,
|
||||
'end_at' => $start->addMinutes($minutes),
|
||||
]);
|
||||
}
|
||||
}
|
||||
30
api/database/factories/StageFactory.php
Normal file
30
api/database/factories/StageFactory.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\Stage;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/** @extends Factory<Stage> */
|
||||
final class StageFactory extends Factory
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'event_id' => Event::factory(),
|
||||
'name' => fake()->unique()->randomElement(['Mainstage', 'Havana', 'Stairway', 'Socialite', 'Tent', 'Open Air', 'Greenhouse']),
|
||||
'color' => fake()->hexColor(),
|
||||
'capacity' => fake()->numberBetween(200, 5000),
|
||||
'sort_order' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
public function withCapacity(int $capacity): static
|
||||
{
|
||||
return $this->state(fn () => ['capacity' => $capacity]);
|
||||
}
|
||||
}
|
||||
209
api/database/seeders/ArtistTimetableDevSeeder.php
Normal file
209
api/database/seeders/ArtistTimetableDevSeeder.php
Normal file
@@ -0,0 +1,209 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -756,7 +756,7 @@ class DevSeeder extends Seeder
|
||||
|
||||
foreach ($approvedPersons->shuffle() as $person) {
|
||||
$existing = $usedPersonSlots[$person->id] ?? [];
|
||||
$available = $openShifts->filter(fn (Shift $shift) => !in_array($shift->time_slot_id, $existing));
|
||||
$available = $openShifts->filter(fn (Shift $shift) => ! in_array($shift->time_slot_id, $existing));
|
||||
|
||||
if ($available->isEmpty()) {
|
||||
continue;
|
||||
@@ -956,6 +956,15 @@ class DevSeeder extends Seeder
|
||||
FormBuilderDevSeeder::seedEventRegistrationShowcase($this->org, $festival, $this->command);
|
||||
}
|
||||
|
||||
// RFC-TIMETABLE v0.2 — artist + timetable fixture for the
|
||||
// festival (4 stages, 6 artists, 12 engagements, 13 perfs).
|
||||
ArtistTimetableDevSeeder::seedForFestival(
|
||||
$this->org,
|
||||
$festival,
|
||||
[$vrijdag, $zaterdag, $zondag],
|
||||
);
|
||||
$this->command->info(' Artist timetable: 4 stages, 12 stage_days, 6 artists, 12 engagements, 13 performances');
|
||||
|
||||
$this->command->info(' Echt Feesten 2026 complete');
|
||||
});
|
||||
}
|
||||
@@ -1063,7 +1072,7 @@ class DevSeeder extends Seeder
|
||||
'organisation_id' => $this->org->id,
|
||||
'parent_event_id' => $ijsbaan->id,
|
||||
'name' => $wd['name'],
|
||||
'slug' => 'ijsbaan-week-' . ($i + 1),
|
||||
'slug' => 'ijsbaan-week-'.($i + 1),
|
||||
'start_date' => $wd['start'],
|
||||
'end_date' => $wd['end'],
|
||||
'timezone' => 'Europe/Amsterdam',
|
||||
@@ -1192,7 +1201,7 @@ class DevSeeder extends Seeder
|
||||
$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');
|
||||
$this->command->info(" {$personCount} persons, ".count($allShifts).' shifts created');
|
||||
|
||||
$formSchema = FormBuilderDevSeeder::seedEventSchema($ijsbaan);
|
||||
$submissions = FormBuilderDevSeeder::seedSubmissionsForEvent($ijsbaan, $formSchema);
|
||||
|
||||
Reference in New Issue
Block a user