StageActiveOnEvent — checks the candidate stage_id is linked to the given event_id via stage_days. Covers performance create/update (perf.event_id ↔ stage) and the timetable move endpoint (target_stage_id ↔ resolved target event). WithinEventBounds — checks a candidate datetime is inside the event's [start_at, end_at] window. Used for performance start/end dates and move-target dates against the relevant sub-event for festivals. OptionExpiresInFuture — conditional rule fired only when booking_status === 'option'. Asserts option_expires_at is set and in the future. Implementation of RFC §10.1 transition gate at the request layer (the service layer enforces the same invariant). ContractRequiresFee — conditional rule fired only when booking_status === 'contracted'. Asserts fee_amount is set and > 0. Same dual-layer enforcement as OptionExpiresInFuture. All four pass silently when the validated field is null or the context is irrelevant — the FormRequest still owns the surrounding required/nullable/exists rules. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
52 lines
1.5 KiB
PHP
52 lines
1.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Rules\Artist;
|
|
|
|
use App\Models\Event;
|
|
use Carbon\CarbonImmutable;
|
|
use Closure;
|
|
use Illuminate\Contracts\Validation\ValidationRule;
|
|
|
|
/**
|
|
* Validates that a candidate datetime sits inside the event's
|
|
* [start_at, end_at] window. Works for flat events (single window)
|
|
* and for sub-events (sub-event window); festival-level events are
|
|
* usually not the target — the FormRequest passes the *sub-event* id
|
|
* for performances and the *containing* event for the stage's day.
|
|
*
|
|
* Rule passes silently when value is null or when the event cannot
|
|
* be found (the FormRequest owns the `required`/`exists` decision).
|
|
*/
|
|
final class WithinEventBounds implements ValidationRule
|
|
{
|
|
public function __construct(
|
|
private readonly ?string $eventId,
|
|
) {}
|
|
|
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
|
{
|
|
if ($value === null || $this->eventId === null) {
|
|
return;
|
|
}
|
|
|
|
$event = Event::withoutGlobalScopes()->find($this->eventId);
|
|
if ($event === null) {
|
|
return;
|
|
}
|
|
|
|
$candidate = CarbonImmutable::parse((string) $value);
|
|
$start = CarbonImmutable::instance($event->start_at);
|
|
$end = CarbonImmutable::instance($event->end_at);
|
|
|
|
if ($candidate->lt($start) || $candidate->gt($end)) {
|
|
$fail(sprintf(
|
|
'De datum moet binnen het evenement (%s — %s) vallen.',
|
|
$start->format('Y-m-d H:i'),
|
|
$end->format('Y-m-d H:i'),
|
|
));
|
|
}
|
|
}
|
|
}
|