feat(timetable): API resources + LaneResolver helper
Six resources under app/Http/Resources/Api/V1/Artist/ matching
FormSubmissionResource conventions (final class, @mixin model,
optional()->toIso8601String, whenLoaded relationships).
GenreResource — id, name, color, sort_order, is_active
ArtistResource — master + lifetime/upcoming engagement counts
computed lazily from the engagements relation
ArtistContactResource — paired with ArtistResource.contacts
ArtistEngagementResource — full deal block with the RFC D26 Buma/VAT
formulas computed live in `computed.*`:
buma_amount = fee × buma_pct/100
IFF Organisation handles BUMA
vat_grondslag = fee + (buma when Organisation)
vat_amount = vat_grondslag × vat_pct/100
when vat_applicable
total_cost = fee + buma + vat + Σ breakdown
Frontend (Session 5) ports the same formula.
StageResource — adds stage_days as a flat array of event_ids
(not nested Event resources, to keep payload
light)
PerformanceResource — `lane` (raw, persisted), `lane_resolved`
(computed per D19), `warnings` (overlap +
B2B at minimum; capacity-warn refined later)
LaneResolver under app/Services/Artist/ is the pure-logic helper that
PerformanceResource calls. Greedy lowest-non-conflicting lane
assignment over the (stage_id, event_id) cohort sorted by start_at
then by raw lane (so cascade-bumped rows stay where they were
visually). Frontend port lands in Session 4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
69
api/app/Http/Resources/Api/V1/Artist/ArtistResource.php
Normal file
69
api/app/Http/Resources/Api/V1/Artist/ArtistResource.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\Api\V1\Artist;
|
||||
|
||||
use App\Enums\Artist\ArtistEngagementStatus;
|
||||
use App\Models\Artist;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @mixin Artist
|
||||
*/
|
||||
final class ArtistResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$lifetime = $this->engagements()
|
||||
->whereNotIn('booking_status', [
|
||||
ArtistEngagementStatus::Cancelled->value,
|
||||
ArtistEngagementStatus::Rejected->value,
|
||||
ArtistEngagementStatus::Declined->value,
|
||||
])
|
||||
->count();
|
||||
|
||||
$upcoming = $this->engagements()
|
||||
->whereNotIn('booking_status', [
|
||||
ArtistEngagementStatus::Cancelled->value,
|
||||
ArtistEngagementStatus::Rejected->value,
|
||||
ArtistEngagementStatus::Declined->value,
|
||||
])
|
||||
->whereHas('event', fn ($q) => $q->where('end_at', '>=', now()))
|
||||
->count();
|
||||
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'organisation_id' => $this->organisation_id,
|
||||
'name' => $this->name,
|
||||
'slug' => $this->slug,
|
||||
'default_genre_id' => $this->default_genre_id,
|
||||
'default_genre' => GenreResource::make($this->whenLoaded('defaultGenre')),
|
||||
'default_draw' => $this->default_draw,
|
||||
'star_rating' => $this->star_rating,
|
||||
'home_base_country' => $this->home_base_country,
|
||||
'agent_company_id' => $this->agent_company_id,
|
||||
'agent_company' => $this->whenLoaded(
|
||||
'agentCompany',
|
||||
fn () => [
|
||||
'id' => $this->agentCompany?->id,
|
||||
'name' => $this->agentCompany?->name,
|
||||
'handles_buma' => (bool) ($this->agentCompany?->handles_buma ?? false),
|
||||
],
|
||||
),
|
||||
'notes' => $this->notes,
|
||||
'contacts' => ArtistContactResource::collection($this->whenLoaded('contacts')),
|
||||
'engagements_summary' => [
|
||||
'lifetime_count' => $lifetime,
|
||||
'upcoming_count' => $upcoming,
|
||||
],
|
||||
'created_at' => optional($this->created_at)->toIso8601String(),
|
||||
'updated_at' => optional($this->updated_at)->toIso8601String(),
|
||||
'deleted_at' => optional($this->deleted_at)->toIso8601String(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user