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:
@@ -0,0 +1,124 @@
|
||||
<?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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user