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>
125 lines
5.0 KiB
PHP
125 lines
5.0 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Http\Resources\Api\V1\Artist;
|
||
|
||
use App\Enums\Artist\BumaHandledBy;
|
||
use App\Models\ArtistEngagement;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Http\Resources\Json\JsonResource;
|
||
|
||
/**
|
||
* @mixin ArtistEngagement
|
||
*
|
||
* Buma + VAT formulas (RFC v0.2 D26 — must match Session 5 client-side
|
||
* preview):
|
||
*
|
||
* buma_amount = fee × buma_percentage / 100
|
||
* IFF buma_applicable && buma_handled_by === Organisation
|
||
* ELSE 0
|
||
*
|
||
* vat_grondslag = fee + (buma_amount IF Organisation handles buma ELSE 0)
|
||
*
|
||
* vat_amount = vat_grondslag × vat_percentage / 100 IF vat_applicable
|
||
* ELSE 0
|
||
*
|
||
* total_cost = fee + buma_amount + vat_amount
|
||
* + Σ deal_breakdown[*].amount
|
||
*/
|
||
final class ArtistEngagementResource extends JsonResource
|
||
{
|
||
/**
|
||
* @return array<string, mixed>
|
||
*/
|
||
public function toArray(Request $request): array
|
||
{
|
||
$fee = (float) ($this->fee_amount ?? 0);
|
||
$bumaPercentage = (float) ($this->buma_percentage ?? 0);
|
||
$vatPercentage = (float) ($this->vat_percentage ?? 0);
|
||
|
||
$bumaAmount = ($this->buma_applicable && $this->buma_handled_by === BumaHandledBy::Organisation)
|
||
? round($fee * $bumaPercentage / 100, 2)
|
||
: 0.0;
|
||
|
||
$vatGrondslag = $fee + (
|
||
$this->buma_handled_by === BumaHandledBy::Organisation
|
||
? $bumaAmount
|
||
: 0.0
|
||
);
|
||
|
||
$vatAmount = $this->vat_applicable
|
||
? round($vatGrondslag * $vatPercentage / 100, 2)
|
||
: 0.0;
|
||
|
||
$breakdownTotal = 0.0;
|
||
foreach ((array) $this->deal_breakdown as $line) {
|
||
if (is_array($line) && isset($line['amount'])) {
|
||
$breakdownTotal += (float) $line['amount'];
|
||
}
|
||
}
|
||
|
||
$totalCost = round($fee + $bumaAmount + $vatAmount + $breakdownTotal, 2);
|
||
|
||
return [
|
||
'id' => $this->id,
|
||
'organisation_id' => $this->organisation_id,
|
||
'artist_id' => $this->artist_id,
|
||
'event_id' => $this->event_id,
|
||
'artist' => ArtistResource::make($this->whenLoaded('artist')),
|
||
'project_leader_id' => $this->project_leader_id,
|
||
'project_leader' => $this->whenLoaded('projectLeader', fn () => [
|
||
'id' => $this->projectLeader?->id,
|
||
'name' => trim(($this->projectLeader?->first_name ?? '').' '.($this->projectLeader?->last_name ?? '')),
|
||
'email' => $this->projectLeader?->email,
|
||
]),
|
||
'booking_status' => [
|
||
'value' => $this->booking_status?->value,
|
||
'label' => $this->booking_status?->label(),
|
||
],
|
||
'fee_amount' => $this->fee_amount,
|
||
'fee_currency' => $this->fee_currency,
|
||
'fee_type' => [
|
||
'value' => $this->fee_type?->value,
|
||
'label' => $this->fee_type?->label(),
|
||
],
|
||
'buma_applicable' => (bool) $this->buma_applicable,
|
||
'buma_percentage' => $this->buma_percentage,
|
||
'buma_handled_by' => [
|
||
'value' => $this->buma_handled_by?->value,
|
||
'label' => $this->buma_handled_by?->label(),
|
||
],
|
||
'vat_applicable' => (bool) $this->vat_applicable,
|
||
'vat_percentage' => $this->vat_percentage,
|
||
'deal_breakdown' => $this->deal_breakdown,
|
||
'deposit_percentage' => $this->deposit_percentage,
|
||
'deposit_due_date' => optional($this->deposit_due_date)->toIso8601String(),
|
||
'balance_due_date' => optional($this->balance_due_date)->toIso8601String(),
|
||
'payment_status' => [
|
||
'value' => $this->payment_status?->value,
|
||
'label' => $this->payment_status?->label(),
|
||
],
|
||
'crew_count' => $this->crew_count,
|
||
'guests_count' => $this->guests_count,
|
||
'requested_at' => optional($this->requested_at)->toIso8601String(),
|
||
'option_expires_at' => optional($this->option_expires_at)->toIso8601String(),
|
||
'advance_open_from' => optional($this->advance_open_from)->toIso8601String(),
|
||
'advance_open_to' => optional($this->advance_open_to)->toIso8601String(),
|
||
'advancing_completed_count' => $this->advancing_completed_count,
|
||
'advancing_total_count' => $this->advancing_total_count,
|
||
'notes' => $this->notes,
|
||
'computed' => [
|
||
'buma_amount' => $bumaAmount,
|
||
'vat_grondslag' => $vatGrondslag,
|
||
'vat_amount' => $vatAmount,
|
||
'breakdown_total' => $breakdownTotal,
|
||
'total_cost' => $totalCost,
|
||
],
|
||
'performances' => PerformanceResource::collection($this->whenLoaded('performances')),
|
||
'created_at' => optional($this->created_at)->toIso8601String(),
|
||
'updated_at' => optional($this->updated_at)->toIso8601String(),
|
||
'deleted_at' => optional($this->deleted_at)->toIso8601String(),
|
||
];
|
||
}
|
||
}
|