Nine test files under tests/Feature/Artist/ exercising:
ArtistEngagementStateMachineTest 8 tests — terminal blocks, conditional
gates (Option/Contracted), full happy
path, cancel cascade
LaneCascadeServiceTest 5 tests — simple move, cascade-bump,
version mismatch, park, unpark
BumaVatCalculationTest 6 tests — D26 formula coverage:
Organisation/BookingAgency/NotApplicable,
VAT off, breakdown sum, zero fee
DemoteExpiredOptionsTest 4 tests — expired demote, future
untouched, non-Option untouched, run
twice → single option_expired entry
IdempotencyKey60sRedisTest 4 tests — missing header 400, first
cache, replay header, failed not cached
ArtistControllerTest 8 tests — index/create/destroy + cross-
tenant + duplicate detection + restore
StageControllerTest 7 tests — create + uniqueness, destroy
cascade-park, reorder permutation,
replaceDays orphan 409 + force_orphan
ArtistEngagementControllerTest 5 tests — index/create/update/destroy +
422 on invalid status transition
TimetableMoveControllerTest 3 tests — happy path with idempotency
header, missing header → 400, version
mismatch → 409
ArtistPolicyTest 6 tests — role checks, cross-tenant
denial, super_admin bypass, D27 active-
engagement gate
ActivityLogShapeTest 4 tests — performance.moved cascade
props, status_changed vs cancelled,
stage.day_added subject + props,
stage.reordered on Event subject
Bug fixes surfaced by Phase C:
Schema reality: events table uses `start_date`/`end_date` (date), not
`start_at`/`end_at`. Updated WithinEventBounds rule and the two stage_day
resolvers (LaneCascadeService + MoveTimetablePerformanceRequest) to
query the actual columns. ArtistResource.engagements_summary upcoming
filter likewise.
performances table has no organisation_id column (FK-chain via
engagement_id). Removed the org-id filter from the Rule::exists in
MoveTimetablePerformanceRequest; cross-tenant is caught by the policy
in TimetableMoveController.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
70 lines
2.5 KiB
PHP
70 lines
2.5 KiB
PHP
<?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_date', '>=', now()->toDateString()))
|
|
->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(),
|
|
];
|
|
}
|
|
}
|