feat(timetable): Artist domain — 7 enums + 9 Eloquent models
Enums under App\Enums\Artist\ (PascalCase per FormBuilder convention, snake_case wire values per RFC): - ArtistEngagementStatus (D9, 9 states + Dutch labels) - BumaHandledBy (D26) - FeeType, PaymentStatus - AdvanceSectionType, AdvanceSectionSubmissionStatus, AdvanceSubmissionStatus Models: - Artist (org-scoped, slug-unique-per-org via creating boot hook) - ArtistEngagement (per-event booking, denorm organisation_id) - Genre, Stage (event-scoped, ordered scope), StageDay (Pivot, int PK) - Performance (engagement-scoped, isParked() helper) - AdvanceSection, AdvanceSubmission, ArtistContact (primary scope) OrganisationScope wired: - Direct organisation_id: Artist, Genre, ArtistEngagement - FK-chain via tenantScopeStrategy(): Stage→Event, Performance→Engagement, AdvanceSection→Engagement, AdvanceSubmission→Section→Engagement, ArtistContact→Artist, StageDay→Stage→Event Soft-deletes: Artist, ArtistEngagement, Performance (per RFC §5.4). LogsActivity baseline (logFillable+dontSubmitEmptyLogs) on all business models — actual mutation surfaces wire LogOptions in Session 2+. Inverse relations added on Organisation, Event, Company. companies.handles_buma cast added. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
20
api/app/Enums/Artist/AdvanceSectionSubmissionStatus.php
Normal file
20
api/app/Enums/Artist/AdvanceSectionSubmissionStatus.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums\Artist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submission lifecycle status for an AdvanceSection.
|
||||||
|
*
|
||||||
|
* Per RFC-TIMETABLE v0.2 §5.3 (`advance_sections.submission_status`
|
||||||
|
* column).
|
||||||
|
*/
|
||||||
|
enum AdvanceSectionSubmissionStatus: string
|
||||||
|
{
|
||||||
|
case Open = 'open';
|
||||||
|
case Pending = 'pending';
|
||||||
|
case Submitted = 'submitted';
|
||||||
|
case Approved = 'approved';
|
||||||
|
case Declined = 'declined';
|
||||||
|
}
|
||||||
20
api/app/Enums/Artist/AdvanceSectionType.php
Normal file
20
api/app/Enums/Artist/AdvanceSectionType.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums\Artist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-categorisation for an AdvanceSection.
|
||||||
|
*
|
||||||
|
* Per RFC-TIMETABLE v0.2 §5.3 (`advance_sections.type` column).
|
||||||
|
* Section labels live in the `name` column; this enum classifies
|
||||||
|
* the section for downstream behaviour (rendering, defaults).
|
||||||
|
*/
|
||||||
|
enum AdvanceSectionType: string
|
||||||
|
{
|
||||||
|
case GuestList = 'guest_list';
|
||||||
|
case Contacts = 'contacts';
|
||||||
|
case Production = 'production';
|
||||||
|
case Custom = 'custom';
|
||||||
|
}
|
||||||
17
api/app/Enums/Artist/AdvanceSubmissionStatus.php
Normal file
17
api/app/Enums/Artist/AdvanceSubmissionStatus.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums\Artist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Review status for an individual AdvanceSubmission row.
|
||||||
|
*
|
||||||
|
* Per RFC-TIMETABLE v0.2 §5.3 (`advance_submissions.status`).
|
||||||
|
*/
|
||||||
|
enum AdvanceSubmissionStatus: string
|
||||||
|
{
|
||||||
|
case Pending = 'pending';
|
||||||
|
case Accepted = 'accepted';
|
||||||
|
case Declined = 'declined';
|
||||||
|
}
|
||||||
39
api/app/Enums/Artist/ArtistEngagementStatus.php
Normal file
39
api/app/Enums/Artist/ArtistEngagementStatus.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums\Artist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Booking status for an ArtistEngagement (per-event booking).
|
||||||
|
*
|
||||||
|
* Per RFC-TIMETABLE v0.2 D9 — 9 states. `Cancelled`, `Rejected`,
|
||||||
|
* `Declined` are three distinct end-states for reporting.
|
||||||
|
*/
|
||||||
|
enum ArtistEngagementStatus: string
|
||||||
|
{
|
||||||
|
case Draft = 'draft';
|
||||||
|
case Requested = 'requested';
|
||||||
|
case Option = 'option';
|
||||||
|
case Offered = 'offered';
|
||||||
|
case Confirmed = 'confirmed';
|
||||||
|
case Contracted = 'contracted';
|
||||||
|
case Cancelled = 'cancelled';
|
||||||
|
case Rejected = 'rejected';
|
||||||
|
case Declined = 'declined';
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Draft => 'Concept',
|
||||||
|
self::Requested => 'Aangevraagd',
|
||||||
|
self::Option => 'Optie',
|
||||||
|
self::Offered => 'Aanbod uit',
|
||||||
|
self::Confirmed => 'Bevestigd',
|
||||||
|
self::Contracted => 'Gecontracteerd',
|
||||||
|
self::Cancelled => 'Geannuleerd',
|
||||||
|
self::Rejected => 'Afgewezen',
|
||||||
|
self::Declined => 'Bedankt',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
26
api/app/Enums/Artist/BumaHandledBy.php
Normal file
26
api/app/Enums/Artist/BumaHandledBy.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums\Artist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Who handles BUMA reporting/payment for a given engagement.
|
||||||
|
*
|
||||||
|
* Per RFC-TIMETABLE v0.2 D26.
|
||||||
|
*/
|
||||||
|
enum BumaHandledBy: string
|
||||||
|
{
|
||||||
|
case Organisation = 'organisation';
|
||||||
|
case BookingAgency = 'booking_agency';
|
||||||
|
case NotApplicable = 'not_applicable';
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Organisation => 'Organisatie',
|
||||||
|
self::BookingAgency => 'Boekingsagent',
|
||||||
|
self::NotApplicable => 'Niet van toepassing',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
26
api/app/Enums/Artist/FeeType.php
Normal file
26
api/app/Enums/Artist/FeeType.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums\Artist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deal-fee structure for an ArtistEngagement.
|
||||||
|
*
|
||||||
|
* Per RFC-TIMETABLE v0.2 §5.3 (`fee_type` column).
|
||||||
|
*/
|
||||||
|
enum FeeType: string
|
||||||
|
{
|
||||||
|
case Flat = 'flat';
|
||||||
|
case DoorSplit = 'door_split';
|
||||||
|
case GuaranteePlusSplit = 'guarantee_plus_split';
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Flat => 'Vaste fee',
|
||||||
|
self::DoorSplit => 'Door split',
|
||||||
|
self::GuaranteePlusSplit => 'Garantie + split',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
26
api/app/Enums/Artist/PaymentStatus.php
Normal file
26
api/app/Enums/Artist/PaymentStatus.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums\Artist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment progress for an ArtistEngagement.
|
||||||
|
*
|
||||||
|
* Per RFC-TIMETABLE v0.2 §5.3 (`payment_status` column).
|
||||||
|
*/
|
||||||
|
enum PaymentStatus: string
|
||||||
|
{
|
||||||
|
case None = 'none';
|
||||||
|
case DepositPaid = 'deposit_paid';
|
||||||
|
case PaidInFull = 'paid_in_full';
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::None => 'Geen betaling',
|
||||||
|
self::DepositPaid => 'Aanbetaling voldaan',
|
||||||
|
self::PaidInFull => 'Volledig voldaan',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
79
api/app/Models/AdvanceSection.php
Normal file
79
api/app/Models/AdvanceSection.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\Artist\AdvanceSectionSubmissionStatus;
|
||||||
|
use App\Enums\Artist\AdvanceSectionType;
|
||||||
|
use App\Models\Scopes\OrganisationScope;
|
||||||
|
use Illuminate\Database\Eloquent\Concerns\HasUlids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||||
|
use Spatie\Activitylog\Support\LogOptions;
|
||||||
|
|
||||||
|
final class AdvanceSection extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use HasUlids;
|
||||||
|
use LogsActivity;
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
self::addGlobalScope(new OrganisationScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array{via: class-string, fk: string} */
|
||||||
|
public static function tenantScopeStrategy(): array
|
||||||
|
{
|
||||||
|
return ['via' => ArtistEngagement::class, 'fk' => 'engagement_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'engagement_id',
|
||||||
|
'name',
|
||||||
|
'type',
|
||||||
|
'is_open',
|
||||||
|
'open_from',
|
||||||
|
'open_to',
|
||||||
|
'sort_order',
|
||||||
|
'submission_status',
|
||||||
|
'last_submitted_at',
|
||||||
|
'last_submitted_by',
|
||||||
|
'submission_diff',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => AdvanceSectionType::class,
|
||||||
|
'submission_status' => AdvanceSectionSubmissionStatus::class,
|
||||||
|
'is_open' => 'boolean',
|
||||||
|
'open_from' => 'datetime',
|
||||||
|
'open_to' => 'datetime',
|
||||||
|
'sort_order' => 'integer',
|
||||||
|
'last_submitted_at' => 'datetime',
|
||||||
|
'submission_diff' => 'array',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActivitylogOptions(): LogOptions
|
||||||
|
{
|
||||||
|
return LogOptions::defaults()
|
||||||
|
->logFillable()
|
||||||
|
->dontSubmitEmptyLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function engagement(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ArtistEngagement::class, 'engagement_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submissions(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(AdvanceSubmission::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
70
api/app/Models/AdvanceSubmission.php
Normal file
70
api/app/Models/AdvanceSubmission.php
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\Artist\AdvanceSubmissionStatus;
|
||||||
|
use App\Models\Scopes\OrganisationScope;
|
||||||
|
use Illuminate\Database\Eloquent\Concerns\HasUlids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||||
|
use Spatie\Activitylog\Support\LogOptions;
|
||||||
|
|
||||||
|
final class AdvanceSubmission extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use HasUlids;
|
||||||
|
use LogsActivity;
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
self::addGlobalScope(new OrganisationScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array{via: class-string, fk: string} */
|
||||||
|
public static function tenantScopeStrategy(): array
|
||||||
|
{
|
||||||
|
return ['via' => AdvanceSection::class, 'fk' => 'advance_section_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'advance_section_id',
|
||||||
|
'submitted_by_name',
|
||||||
|
'submitted_by_email',
|
||||||
|
'submitted_at',
|
||||||
|
'status',
|
||||||
|
'reviewed_by',
|
||||||
|
'reviewed_at',
|
||||||
|
'data',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'status' => AdvanceSubmissionStatus::class,
|
||||||
|
'submitted_at' => 'datetime',
|
||||||
|
'reviewed_at' => 'datetime',
|
||||||
|
'data' => 'array',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActivitylogOptions(): LogOptions
|
||||||
|
{
|
||||||
|
return LogOptions::defaults()
|
||||||
|
->logFillable()
|
||||||
|
->dontSubmitEmptyLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function section(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(AdvanceSection::class, 'advance_section_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reviewer(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'reviewed_by');
|
||||||
|
}
|
||||||
|
}
|
||||||
107
api/app/Models/Artist.php
Normal file
107
api/app/Models/Artist.php
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\Scopes\OrganisationScope;
|
||||||
|
use Illuminate\Database\Eloquent\Concerns\HasUlids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||||
|
use Spatie\Activitylog\Support\LogOptions;
|
||||||
|
|
||||||
|
final class Artist extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use HasUlids;
|
||||||
|
use LogsActivity;
|
||||||
|
use SoftDeletes;
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
self::addGlobalScope(new OrganisationScope);
|
||||||
|
|
||||||
|
self::creating(function (Artist $artist): void {
|
||||||
|
if (empty($artist->slug)) {
|
||||||
|
$artist->slug = $artist->generateUniqueSlug($artist->name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'organisation_id',
|
||||||
|
'name',
|
||||||
|
'slug',
|
||||||
|
'default_genre_id',
|
||||||
|
'default_draw',
|
||||||
|
'star_rating',
|
||||||
|
'home_base_country',
|
||||||
|
'agent_company_id',
|
||||||
|
'notes',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'default_draw' => 'integer',
|
||||||
|
'star_rating' => 'integer',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActivitylogOptions(): LogOptions
|
||||||
|
{
|
||||||
|
return LogOptions::defaults()
|
||||||
|
->logFillable()
|
||||||
|
->dontSubmitEmptyLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateUniqueSlug(string $name): string
|
||||||
|
{
|
||||||
|
$base = Str::slug($name);
|
||||||
|
$slug = $base;
|
||||||
|
$suffix = 2;
|
||||||
|
|
||||||
|
while (
|
||||||
|
self::withoutGlobalScope(OrganisationScope::class)
|
||||||
|
->withTrashed()
|
||||||
|
->where('organisation_id', $this->organisation_id)
|
||||||
|
->where('slug', $slug)
|
||||||
|
->exists()
|
||||||
|
) {
|
||||||
|
$slug = "{$base}-{$suffix}";
|
||||||
|
$suffix++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function organisation(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Organisation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function defaultGenre(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Genre::class, 'default_genre_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function agentCompany(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Company::class, 'agent_company_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contacts(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(ArtistContact::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function engagements(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(ArtistEngagement::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
69
api/app/Models/ArtistContact.php
Normal file
69
api/app/Models/ArtistContact.php
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\Scopes\OrganisationScope;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Concerns\HasUlids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||||
|
use Spatie\Activitylog\Support\LogOptions;
|
||||||
|
|
||||||
|
final class ArtistContact extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use HasUlids;
|
||||||
|
use LogsActivity;
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
self::addGlobalScope(new OrganisationScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array{via: class-string, fk: string} */
|
||||||
|
public static function tenantScopeStrategy(): array
|
||||||
|
{
|
||||||
|
return ['via' => Artist::class, 'fk' => 'artist_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'artist_id',
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'phone',
|
||||||
|
'role',
|
||||||
|
'is_primary',
|
||||||
|
'receives_briefing',
|
||||||
|
'receives_infosheet',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'is_primary' => 'boolean',
|
||||||
|
'receives_briefing' => 'boolean',
|
||||||
|
'receives_infosheet' => 'boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActivitylogOptions(): LogOptions
|
||||||
|
{
|
||||||
|
return LogOptions::defaults()
|
||||||
|
->logFillable()
|
||||||
|
->dontSubmitEmptyLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function artist(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Artist::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopePrimary(Builder $query): Builder
|
||||||
|
{
|
||||||
|
return $query->where('is_primary', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
123
api/app/Models/ArtistEngagement.php
Normal file
123
api/app/Models/ArtistEngagement.php
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\Artist\ArtistEngagementStatus;
|
||||||
|
use App\Enums\Artist\BumaHandledBy;
|
||||||
|
use App\Enums\Artist\FeeType;
|
||||||
|
use App\Enums\Artist\PaymentStatus;
|
||||||
|
use App\Models\Scopes\OrganisationScope;
|
||||||
|
use Illuminate\Database\Eloquent\Concerns\HasUlids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||||
|
use Spatie\Activitylog\Support\LogOptions;
|
||||||
|
|
||||||
|
final class ArtistEngagement extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use HasUlids;
|
||||||
|
use LogsActivity;
|
||||||
|
use SoftDeletes;
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
self::addGlobalScope(new OrganisationScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'organisation_id',
|
||||||
|
'artist_id',
|
||||||
|
'event_id',
|
||||||
|
'booking_status',
|
||||||
|
'project_leader_id',
|
||||||
|
'fee_amount',
|
||||||
|
'fee_currency',
|
||||||
|
'fee_type',
|
||||||
|
'buma_applicable',
|
||||||
|
'buma_percentage',
|
||||||
|
'buma_handled_by',
|
||||||
|
'vat_applicable',
|
||||||
|
'vat_percentage',
|
||||||
|
'deal_breakdown',
|
||||||
|
'deposit_percentage',
|
||||||
|
'deposit_due_date',
|
||||||
|
'balance_due_date',
|
||||||
|
'payment_status',
|
||||||
|
'crew_count',
|
||||||
|
'guests_count',
|
||||||
|
'requested_at',
|
||||||
|
'option_expires_at',
|
||||||
|
'advance_open_from',
|
||||||
|
'advance_open_to',
|
||||||
|
'portal_token',
|
||||||
|
'advancing_completed_count',
|
||||||
|
'advancing_total_count',
|
||||||
|
'notes',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'booking_status' => ArtistEngagementStatus::class,
|
||||||
|
'fee_type' => FeeType::class,
|
||||||
|
'buma_handled_by' => BumaHandledBy::class,
|
||||||
|
'payment_status' => PaymentStatus::class,
|
||||||
|
'buma_applicable' => 'boolean',
|
||||||
|
'vat_applicable' => 'boolean',
|
||||||
|
'deal_breakdown' => 'array',
|
||||||
|
'deposit_due_date' => 'date',
|
||||||
|
'balance_due_date' => 'date',
|
||||||
|
'crew_count' => 'integer',
|
||||||
|
'guests_count' => 'integer',
|
||||||
|
'requested_at' => 'datetime',
|
||||||
|
'option_expires_at' => 'datetime',
|
||||||
|
'advance_open_from' => 'datetime',
|
||||||
|
'advance_open_to' => 'datetime',
|
||||||
|
'advancing_completed_count' => 'integer',
|
||||||
|
'advancing_total_count' => 'integer',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActivitylogOptions(): LogOptions
|
||||||
|
{
|
||||||
|
return LogOptions::defaults()
|
||||||
|
->logFillable()
|
||||||
|
->dontSubmitEmptyLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function organisation(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Organisation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function artist(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Artist::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function event(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Event::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function projectLeader(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'project_leader_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function performances(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Performance::class, 'engagement_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function advanceSections(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(AdvanceSection::class, 'engagement_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ final class Company extends Model
|
|||||||
'organisation_id',
|
'organisation_id',
|
||||||
'name',
|
'name',
|
||||||
'type',
|
'type',
|
||||||
|
'handles_buma',
|
||||||
'kvk_number',
|
'kvk_number',
|
||||||
'contact_first_name',
|
'contact_first_name',
|
||||||
'contact_last_name',
|
'contact_last_name',
|
||||||
@@ -35,6 +36,13 @@ final class Company extends Model
|
|||||||
'contact_phone',
|
'contact_phone',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'handles_buma' => 'boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function getContactFullNameAttribute(): ?string
|
public function getContactFullNameAttribute(): ?string
|
||||||
{
|
{
|
||||||
if (! $this->contact_first_name) {
|
if (! $this->contact_first_name) {
|
||||||
@@ -54,6 +62,11 @@ final class Company extends Model
|
|||||||
return $this->hasMany(Person::class);
|
return $this->hasMany(Person::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function artistsAsAgent(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Artist::class, 'agent_company_id');
|
||||||
|
}
|
||||||
|
|
||||||
/** @param Builder<self> $query */
|
/** @param Builder<self> $query */
|
||||||
public function scopeOrdered(Builder $query): Builder
|
public function scopeOrdered(Builder $query): Builder
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ final class Event extends Model
|
|||||||
|
|
||||||
protected static function booted(): void
|
protected static function booted(): void
|
||||||
{
|
{
|
||||||
static::addGlobalScope(new OrganisationScope());
|
self::addGlobalScope(new OrganisationScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var array<string, list<string>> Allowed status transitions */
|
/** @var array<string, list<string>> Allowed status transitions */
|
||||||
@@ -153,6 +153,21 @@ final class Event extends Model
|
|||||||
->orderBy('name');
|
->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function stages(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Stage::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function artistEngagements(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(ArtistEngagement::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function performances(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Performance::class);
|
||||||
|
}
|
||||||
|
|
||||||
// ----- Status State Machine -----
|
// ----- Status State Machine -----
|
||||||
|
|
||||||
public function canTransitionTo(string $newStatus): bool
|
public function canTransitionTo(string $newStatus): bool
|
||||||
|
|||||||
59
api/app/Models/Genre.php
Normal file
59
api/app/Models/Genre.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\Scopes\OrganisationScope;
|
||||||
|
use Illuminate\Database\Eloquent\Concerns\HasUlids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||||
|
use Spatie\Activitylog\Support\LogOptions;
|
||||||
|
|
||||||
|
final class Genre extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use HasUlids;
|
||||||
|
use LogsActivity;
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
self::addGlobalScope(new OrganisationScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'organisation_id',
|
||||||
|
'name',
|
||||||
|
'color',
|
||||||
|
'sort_order',
|
||||||
|
'is_active',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'sort_order' => 'integer',
|
||||||
|
'is_active' => 'boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActivitylogOptions(): LogOptions
|
||||||
|
{
|
||||||
|
return LogOptions::defaults()
|
||||||
|
->logFillable()
|
||||||
|
->dontSubmitEmptyLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function organisation(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Organisation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function artists(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Artist::class, 'default_genre_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,4 +100,19 @@ final class Organisation extends Model
|
|||||||
{
|
{
|
||||||
return $this->hasMany(EmailLog::class);
|
return $this->hasMany(EmailLog::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function artists(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Artist::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function genres(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Genre::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function artistEngagements(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(ArtistEngagement::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
81
api/app/Models/Performance.php
Normal file
81
api/app/Models/Performance.php
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\Scopes\OrganisationScope;
|
||||||
|
use Illuminate\Database\Eloquent\Concerns\HasUlids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||||
|
use Spatie\Activitylog\Support\LogOptions;
|
||||||
|
|
||||||
|
final class Performance extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use HasUlids;
|
||||||
|
use LogsActivity;
|
||||||
|
use SoftDeletes;
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
self::addGlobalScope(new OrganisationScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array{via: class-string, fk: string} */
|
||||||
|
public static function tenantScopeStrategy(): array
|
||||||
|
{
|
||||||
|
return ['via' => ArtistEngagement::class, 'fk' => 'engagement_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'engagement_id',
|
||||||
|
'event_id',
|
||||||
|
'stage_id',
|
||||||
|
'lane',
|
||||||
|
'start_at',
|
||||||
|
'end_at',
|
||||||
|
'version',
|
||||||
|
'notes',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'lane' => 'integer',
|
||||||
|
'start_at' => 'datetime',
|
||||||
|
'end_at' => 'datetime',
|
||||||
|
'version' => 'integer',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActivitylogOptions(): LogOptions
|
||||||
|
{
|
||||||
|
return LogOptions::defaults()
|
||||||
|
->logFillable()
|
||||||
|
->dontSubmitEmptyLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function engagement(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ArtistEngagement::class, 'engagement_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function event(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Event::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stage(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Stage::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isParked(): bool
|
||||||
|
{
|
||||||
|
return $this->stage_id === null;
|
||||||
|
}
|
||||||
|
}
|
||||||
76
api/app/Models/Stage.php
Normal file
76
api/app/Models/Stage.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\Scopes\OrganisationScope;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Concerns\HasUlids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||||
|
use Spatie\Activitylog\Support\LogOptions;
|
||||||
|
|
||||||
|
final class Stage extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use HasUlids;
|
||||||
|
use LogsActivity;
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
self::addGlobalScope(new OrganisationScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array{via: class-string, fk: string} */
|
||||||
|
public static function tenantScopeStrategy(): array
|
||||||
|
{
|
||||||
|
return ['via' => Event::class, 'fk' => 'event_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'event_id',
|
||||||
|
'name',
|
||||||
|
'color',
|
||||||
|
'capacity',
|
||||||
|
'sort_order',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'capacity' => 'integer',
|
||||||
|
'sort_order' => 'integer',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActivitylogOptions(): LogOptions
|
||||||
|
{
|
||||||
|
return LogOptions::defaults()
|
||||||
|
->logFillable()
|
||||||
|
->dontSubmitEmptyLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function event(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Event::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stageDays(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(StageDay::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function performances(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Performance::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeOrdered(Builder $query): Builder
|
||||||
|
{
|
||||||
|
return $query->orderBy('sort_order')->orderBy('created_at');
|
||||||
|
}
|
||||||
|
}
|
||||||
51
api/app/Models/StageDay.php
Normal file
51
api/app/Models/StageDay.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\Scopes\OrganisationScope;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pivot stage↔event. Integer auto-increment PK; pure pivot, no
|
||||||
|
* timestamps, no soft deletes. Multi-tenancy inherited via `stage_id`.
|
||||||
|
*/
|
||||||
|
final class StageDay extends Pivot
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
public $incrementing = true;
|
||||||
|
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
|
protected $table = 'stage_days';
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
self::addGlobalScope(new OrganisationScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array{via: class-string, fk: string} */
|
||||||
|
public static function tenantScopeStrategy(): array
|
||||||
|
{
|
||||||
|
return ['via' => Stage::class, 'fk' => 'stage_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'stage_id',
|
||||||
|
'event_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function stage(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Stage::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function event(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Event::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user