Files
crewli/api/app/Http/Resources/Api/V1/Artist/ArtistEngagementResource.php
bert.hausmans 9e94ab78d8 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>
2026-05-08 20:53:43 +02:00

125 lines
5.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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(),
];
}
}