feat(timetable): 13 form requests for artist domain endpoints
Created under app/Http/Requests/Api/V1/Artist/, mirroring the
existing FormRequest pattern (final class, authorize() returns true,
controller-level Gate::authorize). One request per CRUD shape plus the
two domain-specific endpoints:
artists create / update
genres create / update (with org-scoped unique)
stages create / update (with event-scoped unique)
stages/order ReorderStagesRequest — permutation check
engagements create / update — per RFC §10.3, with
ContractRequiresFee + OptionExpiresInFuture
conditional rules wired
performances create / update — per §10.2; cross-FK
engagement.event_id ↔ event_id chain
enforced via withValidator closure;
update is non-placement only (placement
edits go through /timetable/move)
timetable/move per §10.4; resolves target_event_id from
target_stage_id + target_start_at via
stage_days, then reuses StageActiveOnEvent
+ WithinEventBounds for downstream rules
stages/{stage}/days §10.5 matrix replace; each event_id must
equal stage.event_id (flat) or be sub-event
(festival)
Custom error messages in Dutch where user-facing. Cross-FK rules that
span request inputs (engagement vs event-id chain, day matrix sub-event
membership) live in withValidator after-closures so the rule cache is
stable per request.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Enums\Artist\ArtistEngagementStatus;
|
||||
use App\Enums\Artist\BumaHandledBy;
|
||||
use App\Enums\Artist\FeeType;
|
||||
use App\Enums\Artist\PaymentStatus;
|
||||
use App\Models\Event;
|
||||
use App\Rules\Artist\ContractRequiresFee;
|
||||
use App\Rules\Artist\OptionExpiresInFuture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* RFC v0.2 §10.3.
|
||||
*/
|
||||
final class CreateArtistEngagementRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$event = $this->route('event');
|
||||
$organisationId = $event instanceof Event ? $event->organisation_id : null;
|
||||
$bookingStatus = $this->input('booking_status');
|
||||
|
||||
return [
|
||||
'artist_id' => [
|
||||
'required', 'string', 'max:30',
|
||||
Rule::exists('artists', 'id')->where('organisation_id', $organisationId),
|
||||
],
|
||||
'booking_status' => ['required', Rule::enum(ArtistEngagementStatus::class)],
|
||||
'project_leader_id' => ['nullable', 'string', 'max:30', 'exists:users,id'],
|
||||
'fee_amount' => ['nullable', 'numeric', 'min:0', 'max:9999999.99', new ContractRequiresFee($bookingStatus)],
|
||||
'fee_currency' => ['nullable', 'string', 'size:3', Rule::in(['EUR', 'USD', 'GBP'])],
|
||||
'fee_type' => ['nullable', Rule::enum(FeeType::class)],
|
||||
'buma_applicable' => ['nullable', 'boolean'],
|
||||
'buma_percentage' => ['nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'buma_handled_by' => ['nullable', Rule::enum(BumaHandledBy::class)],
|
||||
'vat_applicable' => ['nullable', 'boolean'],
|
||||
'vat_percentage' => ['nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'deal_breakdown' => ['nullable', 'array'],
|
||||
'deposit_percentage' => ['nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'deposit_due_date' => ['nullable', 'date'],
|
||||
'balance_due_date' => ['nullable', 'date'],
|
||||
'payment_status' => ['nullable', Rule::enum(PaymentStatus::class)],
|
||||
'crew_count' => ['nullable', 'integer', 'min:0', 'max:200'],
|
||||
'guests_count' => ['nullable', 'integer', 'min:0', 'max:1000'],
|
||||
'requested_at' => ['nullable', 'date'],
|
||||
'option_expires_at' => ['nullable', 'date', new OptionExpiresInFuture($bookingStatus)],
|
||||
'advance_open_from' => ['nullable', 'date'],
|
||||
'advance_open_to' => ['nullable', 'date', 'after_or_equal:advance_open_from'],
|
||||
'notes' => ['nullable', 'string', 'max:2000'],
|
||||
];
|
||||
}
|
||||
}
|
||||
49
api/app/Http/Requests/Api/V1/Artist/CreateArtistRequest.php
Normal file
49
api/app/Http/Requests/Api/V1/Artist/CreateArtistRequest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Models\Organisation;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* RFC v0.2 §5.3 (artists table) + §10.3 derived shape.
|
||||
*
|
||||
* Authorization is handled in the controller via Gate::authorize per
|
||||
* the codebase convention; this request returns true.
|
||||
*/
|
||||
final class CreateArtistRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$organisationId = $this->route('organisation') instanceof Organisation
|
||||
? $this->route('organisation')->id
|
||||
: (string) $this->route('organisation');
|
||||
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:120'],
|
||||
'default_genre_id' => [
|
||||
'nullable', 'string', 'max:30',
|
||||
Rule::exists('genres', 'id')->where('organisation_id', $organisationId),
|
||||
],
|
||||
'default_draw' => ['nullable', 'integer', 'min:0'],
|
||||
'star_rating' => ['nullable', 'integer', 'between:1,5'],
|
||||
'home_base_country' => ['nullable', 'string', 'size:2', 'alpha'],
|
||||
'agent_company_id' => [
|
||||
'nullable', 'string', 'max:30',
|
||||
Rule::exists('companies', 'id')->where('organisation_id', $organisationId),
|
||||
],
|
||||
'notes' => ['nullable', 'string', 'max:2000'],
|
||||
];
|
||||
}
|
||||
}
|
||||
37
api/app/Http/Requests/Api/V1/Artist/CreateGenreRequest.php
Normal file
37
api/app/Http/Requests/Api/V1/Artist/CreateGenreRequest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Models\Organisation;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
final class CreateGenreRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$organisationId = $this->route('organisation') instanceof Organisation
|
||||
? $this->route('organisation')->id
|
||||
: (string) $this->route('organisation');
|
||||
|
||||
return [
|
||||
'name' => [
|
||||
'required', 'string', 'max:40',
|
||||
Rule::unique('genres', 'name')->where('organisation_id', $organisationId),
|
||||
],
|
||||
'color' => ['nullable', 'string', 'size:7', 'regex:/^#[0-9A-Fa-f]{6}$/'],
|
||||
'sort_order' => ['nullable', 'integer', 'min:0'],
|
||||
'is_active' => ['nullable', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Models\ArtistEngagement;
|
||||
use App\Models\Event;
|
||||
use App\Rules\Artist\StageActiveOnEvent;
|
||||
use App\Rules\Artist\WithinEventBounds;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* RFC v0.2 §10.2.
|
||||
*/
|
||||
final class CreatePerformanceRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$event = $this->route('event');
|
||||
$organisationId = $event instanceof Event ? $event->organisation_id : null;
|
||||
$eventIdInput = (string) $this->input('event_id', '');
|
||||
|
||||
return [
|
||||
'engagement_id' => [
|
||||
'required', 'string', 'max:30',
|
||||
Rule::exists('artist_engagements', 'id')->where('organisation_id', $organisationId),
|
||||
],
|
||||
'event_id' => [
|
||||
'required', 'string', 'max:30',
|
||||
Rule::exists('events', 'id')->where('organisation_id', $organisationId),
|
||||
],
|
||||
'stage_id' => [
|
||||
'nullable', 'string', 'max:30',
|
||||
Rule::exists('stages', 'id'),
|
||||
new StageActiveOnEvent($eventIdInput),
|
||||
],
|
||||
'start_at' => ['required', 'date_format:Y-m-d H:i:s', new WithinEventBounds($eventIdInput)],
|
||||
'end_at' => ['required', 'date_format:Y-m-d H:i:s', 'after:start_at', new WithinEventBounds($eventIdInput)],
|
||||
'lane' => ['nullable', 'integer', 'min:0', 'max:9'],
|
||||
'notes' => ['nullable', 'string', 'max:1000'],
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(function (Validator $validator): void {
|
||||
$engagementId = $this->input('engagement_id');
|
||||
$eventId = $this->input('event_id');
|
||||
if (! is_string($engagementId) || ! is_string($eventId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$engagement = ArtistEngagement::query()->find($engagementId);
|
||||
if ($engagement === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event = Event::withoutGlobalScopes()->find($eventId);
|
||||
if ($event === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// event_id must equal engagement.event_id (flat case) OR be a
|
||||
// sub-event of engagement.event_id (festival case).
|
||||
if (
|
||||
$eventId !== $engagement->event_id
|
||||
&& $event->parent_event_id !== $engagement->event_id
|
||||
) {
|
||||
$validator->errors()->add(
|
||||
'event_id',
|
||||
'event_id moet gelijk zijn aan de engagement.event_id of een sub-event daarvan.',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
37
api/app/Http/Requests/Api/V1/Artist/CreateStageRequest.php
Normal file
37
api/app/Http/Requests/Api/V1/Artist/CreateStageRequest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Models\Event;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
final class CreateStageRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$eventId = $this->route('event') instanceof Event
|
||||
? $this->route('event')->id
|
||||
: (string) $this->route('event');
|
||||
|
||||
return [
|
||||
'name' => [
|
||||
'required', 'string', 'max:120',
|
||||
Rule::unique('stages', 'name')->where('event_id', $eventId),
|
||||
],
|
||||
'color' => ['required', 'string', 'size:7', 'regex:/^#[0-9A-Fa-f]{6}$/'],
|
||||
'capacity' => ['nullable', 'integer', 'min:0'],
|
||||
'sort_order' => ['nullable', 'integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\Stage;
|
||||
use App\Models\StageDay;
|
||||
use App\Rules\Artist\StageActiveOnEvent;
|
||||
use App\Rules\Artist\WithinEventBounds;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* RFC v0.2 §10.4 — D18 transactional move endpoint.
|
||||
*/
|
||||
final class MoveTimetablePerformanceRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$event = $this->route('event');
|
||||
$organisationId = $event instanceof Event ? $event->organisation_id : null;
|
||||
$resolvedEventId = $this->resolveTargetEventId();
|
||||
|
||||
return [
|
||||
'performance_id' => [
|
||||
'required', 'string', 'max:30',
|
||||
Rule::exists('performances', 'id')->where('organisation_id', $organisationId),
|
||||
],
|
||||
'target_stage_id' => [
|
||||
'nullable', 'string', 'max:30',
|
||||
Rule::exists('stages', 'id'),
|
||||
new StageActiveOnEvent($resolvedEventId),
|
||||
],
|
||||
'target_start_at' => [
|
||||
'nullable', 'date_format:Y-m-d H:i:s',
|
||||
'required_unless:target_stage_id,null',
|
||||
new WithinEventBounds($resolvedEventId),
|
||||
],
|
||||
'target_end_at' => [
|
||||
'nullable', 'date_format:Y-m-d H:i:s',
|
||||
'required_unless:target_stage_id,null',
|
||||
'after:target_start_at',
|
||||
new WithinEventBounds($resolvedEventId),
|
||||
],
|
||||
'target_lane' => ['nullable', 'integer', 'min:0', 'max:9'],
|
||||
'version' => ['required', 'integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(function (Validator $validator): void {
|
||||
// When target_stage_id is non-null, target_lane must be set
|
||||
// (the move algorithm requires a definite lane).
|
||||
if ($this->input('target_stage_id') !== null && $this->input('target_lane') === null) {
|
||||
$validator->errors()->add('target_lane', 'target_lane is verplicht bij een niet-leeg target_stage_id.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the event_id the candidate move lands on so the
|
||||
* StageActiveOnEvent and WithinEventBounds rules can validate
|
||||
* against a concrete event window.
|
||||
*
|
||||
* For flat events: stage.event_id is the answer.
|
||||
* For festivals: walk stage_days for target_stage_id and find the
|
||||
* sub-event whose [start, end] contains target_start_at.
|
||||
*/
|
||||
private function resolveTargetEventId(): ?string
|
||||
{
|
||||
$stageId = $this->input('target_stage_id');
|
||||
$startAt = $this->input('target_start_at');
|
||||
if (! is_string($stageId) || ! is_string($startAt)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$start = CarbonImmutable::parse($startAt);
|
||||
$stage = Stage::query()->find($stageId);
|
||||
if ($stage === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$match = StageDay::query()
|
||||
->where('stage_id', $stage->id)
|
||||
->join('events', 'events.id', '=', 'stage_days.event_id')
|
||||
->where('events.start_at', '<=', $start)
|
||||
->where('events.end_at', '>=', $start)
|
||||
->orderBy('events.start_at', 'desc')
|
||||
->limit(1)
|
||||
->value('stage_days.event_id');
|
||||
|
||||
return is_string($match) ? $match : $stage->event_id;
|
||||
}
|
||||
}
|
||||
55
api/app/Http/Requests/Api/V1/Artist/ReorderStagesRequest.php
Normal file
55
api/app/Http/Requests/Api/V1/Artist/ReorderStagesRequest.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\Stage;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class ReorderStagesRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'stage_ids' => ['required', 'array', 'min:1'],
|
||||
'stage_ids.*' => ['string', 'max:30'],
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(function (Validator $validator): void {
|
||||
$event = $this->route('event');
|
||||
if (! $event instanceof Event) {
|
||||
return;
|
||||
}
|
||||
|
||||
$submitted = (array) $this->input('stage_ids', []);
|
||||
$existing = Stage::query()
|
||||
->where('event_id', $event->id)
|
||||
->pluck('id')
|
||||
->all();
|
||||
|
||||
$missing = array_diff($existing, $submitted);
|
||||
$extra = array_diff($submitted, $existing);
|
||||
|
||||
if ($missing !== [] || $extra !== []) {
|
||||
$validator->errors()->add(
|
||||
'stage_ids',
|
||||
'stage_ids moet een permutatie zijn van alle stages op dit evenement.',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\Stage;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* RFC v0.2 §10.5 — atomic stage_days matrix replace.
|
||||
*/
|
||||
final class ReplaceStageDaysRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$event = $this->route('event');
|
||||
$organisationId = $event instanceof Event ? $event->organisation_id : null;
|
||||
|
||||
return [
|
||||
'event_ids' => ['required', 'array', 'min:1'],
|
||||
'event_ids.*' => [
|
||||
'string', 'max:30',
|
||||
Rule::exists('events', 'id')->where('organisation_id', $organisationId),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(function (Validator $validator): void {
|
||||
$stage = $this->route('stage');
|
||||
if (! $stage instanceof Stage) {
|
||||
return;
|
||||
}
|
||||
|
||||
$eventIds = (array) $this->input('event_ids', []);
|
||||
$events = Event::withoutGlobalScopes()
|
||||
->whereIn('id', $eventIds)
|
||||
->get(['id', 'parent_event_id']);
|
||||
|
||||
foreach ($events as $event) {
|
||||
$isFlatMatch = $event->id === $stage->event_id;
|
||||
$isSubEventMatch = $event->parent_event_id === $stage->event_id;
|
||||
|
||||
if (! $isFlatMatch && ! $isSubEventMatch) {
|
||||
$validator->errors()->add(
|
||||
'event_ids',
|
||||
sprintf(
|
||||
'event_id %s is geen sub-event van of gelijk aan stage.event_id (%s).',
|
||||
$event->id,
|
||||
$stage->event_id,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Enums\Artist\ArtistEngagementStatus;
|
||||
use App\Enums\Artist\BumaHandledBy;
|
||||
use App\Enums\Artist\FeeType;
|
||||
use App\Enums\Artist\PaymentStatus;
|
||||
use App\Models\ArtistEngagement;
|
||||
use App\Rules\Artist\ContractRequiresFee;
|
||||
use App\Rules\Artist\OptionExpiresInFuture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
final class UpdateArtistEngagementRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$engagement = $this->route('engagement');
|
||||
$effectiveStatus = $this->input(
|
||||
'booking_status',
|
||||
$engagement instanceof ArtistEngagement
|
||||
? ($engagement->booking_status?->value ?? null)
|
||||
: null,
|
||||
);
|
||||
|
||||
return [
|
||||
'booking_status' => ['sometimes', Rule::enum(ArtistEngagementStatus::class)],
|
||||
'project_leader_id' => ['sometimes', 'nullable', 'string', 'max:30', 'exists:users,id'],
|
||||
'fee_amount' => ['sometimes', 'nullable', 'numeric', 'min:0', 'max:9999999.99', new ContractRequiresFee($effectiveStatus)],
|
||||
'fee_currency' => ['sometimes', 'nullable', 'string', 'size:3', Rule::in(['EUR', 'USD', 'GBP'])],
|
||||
'fee_type' => ['sometimes', 'nullable', Rule::enum(FeeType::class)],
|
||||
'buma_applicable' => ['sometimes', 'boolean'],
|
||||
'buma_percentage' => ['sometimes', 'nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'buma_handled_by' => ['sometimes', 'nullable', Rule::enum(BumaHandledBy::class)],
|
||||
'vat_applicable' => ['sometimes', 'boolean'],
|
||||
'vat_percentage' => ['sometimes', 'nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'deal_breakdown' => ['sometimes', 'nullable', 'array'],
|
||||
'deposit_percentage' => ['sometimes', 'nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'deposit_due_date' => ['sometimes', 'nullable', 'date'],
|
||||
'balance_due_date' => ['sometimes', 'nullable', 'date'],
|
||||
'payment_status' => ['sometimes', 'nullable', Rule::enum(PaymentStatus::class)],
|
||||
'crew_count' => ['sometimes', 'nullable', 'integer', 'min:0', 'max:200'],
|
||||
'guests_count' => ['sometimes', 'nullable', 'integer', 'min:0', 'max:1000'],
|
||||
'requested_at' => ['sometimes', 'nullable', 'date'],
|
||||
'option_expires_at' => ['sometimes', 'nullable', 'date', new OptionExpiresInFuture($effectiveStatus)],
|
||||
'advance_open_from' => ['sometimes', 'nullable', 'date'],
|
||||
'advance_open_to' => ['sometimes', 'nullable', 'date', 'after_or_equal:advance_open_from'],
|
||||
'notes' => ['sometimes', 'nullable', 'string', 'max:2000'],
|
||||
];
|
||||
}
|
||||
}
|
||||
44
api/app/Http/Requests/Api/V1/Artist/UpdateArtistRequest.php
Normal file
44
api/app/Http/Requests/Api/V1/Artist/UpdateArtistRequest.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Models\Artist;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
final class UpdateArtistRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$artist = $this->route('artist');
|
||||
$organisationId = $artist instanceof Artist
|
||||
? $artist->organisation_id
|
||||
: ($this->route('organisation')?->id ?? null);
|
||||
|
||||
return [
|
||||
'name' => ['sometimes', 'required', 'string', 'max:120'],
|
||||
'default_genre_id' => [
|
||||
'sometimes', 'nullable', 'string', 'max:30',
|
||||
Rule::exists('genres', 'id')->where('organisation_id', $organisationId),
|
||||
],
|
||||
'default_draw' => ['sometimes', 'nullable', 'integer', 'min:0'],
|
||||
'star_rating' => ['sometimes', 'nullable', 'integer', 'between:1,5'],
|
||||
'home_base_country' => ['sometimes', 'nullable', 'string', 'size:2', 'alpha'],
|
||||
'agent_company_id' => [
|
||||
'sometimes', 'nullable', 'string', 'max:30',
|
||||
Rule::exists('companies', 'id')->where('organisation_id', $organisationId),
|
||||
],
|
||||
'notes' => ['sometimes', 'nullable', 'string', 'max:2000'],
|
||||
];
|
||||
}
|
||||
}
|
||||
41
api/app/Http/Requests/Api/V1/Artist/UpdateGenreRequest.php
Normal file
41
api/app/Http/Requests/Api/V1/Artist/UpdateGenreRequest.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Models\Genre;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
final class UpdateGenreRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$genre = $this->route('genre');
|
||||
$organisationId = $genre instanceof Genre
|
||||
? $genre->organisation_id
|
||||
: ($this->route('organisation')?->id ?? null);
|
||||
$genreId = $genre instanceof Genre ? $genre->id : null;
|
||||
|
||||
return [
|
||||
'name' => [
|
||||
'sometimes', 'required', 'string', 'max:40',
|
||||
Rule::unique('genres', 'name')
|
||||
->where('organisation_id', $organisationId)
|
||||
->ignore($genreId),
|
||||
],
|
||||
'color' => ['sometimes', 'nullable', 'string', 'size:7', 'regex:/^#[0-9A-Fa-f]{6}$/'],
|
||||
'sort_order' => ['sometimes', 'nullable', 'integer', 'min:0'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* RFC v0.2 §10.2 — non-placement edits only. Placement (start_at,
|
||||
* end_at, stage_id, lane) is NOT updateable here; placement changes
|
||||
* route through POST /timetable/move so the cascade-bump and
|
||||
* optimistic-lock contract is honoured.
|
||||
*/
|
||||
final class UpdatePerformanceRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'notes' => ['sometimes', 'nullable', 'string', 'max:1000'],
|
||||
];
|
||||
}
|
||||
}
|
||||
39
api/app/Http/Requests/Api/V1/Artist/UpdateStageRequest.php
Normal file
39
api/app/Http/Requests/Api/V1/Artist/UpdateStageRequest.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Artist;
|
||||
|
||||
use App\Models\Stage;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
final class UpdateStageRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$stage = $this->route('stage');
|
||||
$eventId = $stage instanceof Stage
|
||||
? $stage->event_id
|
||||
: ($this->route('event')?->id ?? null);
|
||||
$stageId = $stage instanceof Stage ? $stage->id : null;
|
||||
|
||||
return [
|
||||
'name' => [
|
||||
'sometimes', 'required', 'string', 'max:120',
|
||||
Rule::unique('stages', 'name')->where('event_id', $eventId)->ignore($stageId),
|
||||
],
|
||||
'color' => ['sometimes', 'required', 'string', 'size:7', 'regex:/^#[0-9A-Fa-f]{6}$/'],
|
||||
'capacity' => ['sometimes', 'nullable', 'integer', 'min:0'],
|
||||
'sort_order' => ['sometimes', 'nullable', 'integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user