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:
2026-05-08 20:50:12 +02:00
parent f7ed03237c
commit 378b6fe970
4 changed files with 176 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\Rules\Artist;
use App\Enums\Artist\ArtistEngagementStatus;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
/**
* Conditional rule: when `booking_status === 'contracted'`, the
* `fee_amount` field must be set and greater than zero.
*
* Bound from the engagement FormRequest, taking the inbound
* booking_status value at construction time.
*/
final class ContractRequiresFee implements ValidationRule
{
public function __construct(
private readonly ?string $bookingStatus,
) {}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if ($this->bookingStatus !== ArtistEngagementStatus::Contracted->value) {
return;
}
if ($value === null || $value === '') {
$fail('Bij status "Gecontracteerd" is een fee verplicht.');
return;
}
if ((float) $value <= 0) {
$fail('De fee moet groter dan 0 zijn.');
}
}
}