['required', 'string', 'max:255'], 'type' => ['required', Rule::in([Stream::TYPE_INCOME, Stream::TYPE_EXPENSE])], 'amount' => ['required', 'numeric', 'min:0.01', 'max:999999999.99'], 'frequency' => [ 'required', Rule::in([ Stream::FREQUENCY_ONCE, Stream::FREQUENCY_WEEKLY, Stream::FREQUENCY_BIWEEKLY, Stream::FREQUENCY_MONTHLY, Stream::FREQUENCY_QUARTERLY, Stream::FREQUENCY_YEARLY, ]), ], 'start_date' => ['required', 'date', 'date_format:Y-m-d'], 'end_date' => ['nullable', 'date', 'date_format:Y-m-d', 'after_or_equal:start_date'], 'bucket_id' => ['nullable', 'exists:buckets,uuid'], 'description' => ['nullable', 'string', 'max:1000'], 'is_active' => ['boolean'], ]; } public function messages(): array { return [ 'type.in' => 'The type must be either income or expense.', 'frequency.in' => 'Invalid frequency selected.', 'amount.min' => 'The amount must be greater than zero.', 'amount.max' => 'The amount is too large.', 'end_date.after_or_equal' => 'The end date must be after or equal to the start date.', 'bucket_id.exists' => 'The selected bucket does not exist.', ]; } public function withValidator($validator): void { $validator->after(function ($validator) { /** @var Stream $stream */ $stream = $this->route('stream'); // Validate that the bucket belongs to the stream's scenario if ($this->bucket_id) { $bucketBelongsToScenario = $stream->scenario->buckets() ->where('uuid', $this->bucket_id) ->exists(); if (! $bucketBelongsToScenario) { $validator->errors()->add('bucket_id', 'The selected bucket does not belong to this scenario.'); } } // For expense streams, bucket is required if ($this->type === Stream::TYPE_EXPENSE && ! $this->bucket_id) { $validator->errors()->add('bucket_id', 'A bucket must be selected for expense streams.'); } }); } protected function prepareForValidation(): void { // Ensure dates are in the correct format if ($this->has('start_date') && ! empty($this->start_date)) { $this->merge([ 'start_date' => date('Y-m-d', strtotime($this->start_date)), ]); } if ($this->has('end_date') && ! empty($this->end_date)) { $this->merge([ 'end_date' => date('Y-m-d', strtotime($this->end_date)), ]); } // Convert amount to float if ($this->has('amount')) { $this->merge([ 'amount' => (float) $this->amount, ]); } // Default is_active to current value if not provided if (! $this->has('is_active')) { /** @var Stream $stream */ $stream = $this->route('stream'); $this->merge([ 'is_active' => $stream->is_active, ]); } } }