feat(timetable): four custom validation rules for artist domain
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>
This commit is contained in:
51
api/app/Rules/Artist/WithinEventBounds.php
Normal file
51
api/app/Rules/Artist/WithinEventBounds.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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'),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user