From 2c7b36b05080f9c6d2e3fb94d62bc356f49774fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Bra=C3=B1a?= Date: Mon, 31 Aug 2026 12:26:15 +0200 Subject: [PATCH] feat: Phase 3.1 - FormRequests for Task, Issue, Inspection - Create TaskStoreRequest, TaskUpdateRequest with authorize() + rules() - Create IssueStoreRequest, IssueUpdateRequest with authorize() + rules() - Create InspectionStoreRequest, InspectionUpdateRequest with authorize() + rules() + withValidator() - Update TaskForm to use FormRequests via app() - Update IssueForm (kept inline validation for complex logic) - Add InspectionStore/Update requests for ProjectMap inspection methods Tests: 101 passing (319 assertions) --- app/Http/Requests/InspectionStoreRequest.php | 42 ++++++++++++++++ app/Http/Requests/InspectionUpdateRequest.php | 49 +++++++++++++++++++ app/Http/Requests/IssueStoreRequest.php | 36 ++++++++++++++ app/Http/Requests/IssueUpdateRequest.php | 34 +++++++++++++ app/Http/Requests/TaskStoreRequest.php | 38 ++++++++++++++ app/Http/Requests/TaskUpdateRequest.php | 37 ++++++++++++++ app/Livewire/Issues/IssueForm.php | 1 + app/Livewire/Tasks/TaskForm.php | 10 +++- 8 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 app/Http/Requests/InspectionStoreRequest.php create mode 100644 app/Http/Requests/InspectionUpdateRequest.php create mode 100644 app/Http/Requests/IssueStoreRequest.php create mode 100644 app/Http/Requests/IssueUpdateRequest.php create mode 100644 app/Http/Requests/TaskStoreRequest.php create mode 100644 app/Http/Requests/TaskUpdateRequest.php diff --git a/app/Http/Requests/InspectionStoreRequest.php b/app/Http/Requests/InspectionStoreRequest.php new file mode 100644 index 0000000..6a38dfd --- /dev/null +++ b/app/Http/Requests/InspectionStoreRequest.php @@ -0,0 +1,42 @@ +user()->can('create inspections'); + } + + public function rules(): array + { + return [ + 'selectedTemplateId' => 'required|exists:inspection_templates,id', + 'inspectionPhotos.*' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:10240', + 'inspectionFormData' => 'nullable|array', + 'inspectionResult' => 'nullable|in:pass,fail', + 'inspectionNotes' => 'nullable|string', + ]; + } + + public function withValidator($validator) + { + $validator->after(function ($validator) { + $template = InspectionTemplate::find($this->input('selectedTemplateId')); + if ($template) { + foreach ($template->fields as $field) { + if (($field['required'] ?? false) && empty($this->input("inspectionFormData.{$field['name']}"))) { + $validator->errors()->add( + "inspectionFormData.{$field['name']}", + "El campo {$field['label']} es obligatorio." + ); + } + } + } + }); + } +} \ No newline at end of file diff --git a/app/Http/Requests/InspectionUpdateRequest.php b/app/Http/Requests/InspectionUpdateRequest.php new file mode 100644 index 0000000..70d25ec --- /dev/null +++ b/app/Http/Requests/InspectionUpdateRequest.php @@ -0,0 +1,49 @@ +route('inspection') ?? Inspection::find($this->route('inspection')); + if ($inspection) { + return $this->user()->can('edit inspections', $inspection->project) && $this->user()->can('edit', $inspection); + } + return $this->user()->can('edit inspections'); + } + + public function rules(): array + { + return [ + 'selectedTemplateId' => 'required|exists:inspection_templates,id', + 'editInspectionPhotos.*' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:10240', + 'editInspectionPhotosToDelete' => 'array', + 'editInspectionPhotosToDelete.*' => 'exists:media,id', + 'editInspectionFormData' => 'nullable|array', + 'editInspectionResult' => 'nullable|in:pass,fail', + 'editInspectionNotes' => 'nullable|string', + ]; + } + + public function withValidator($validator) + { + $validator->after(function ($validator) { + $template = InspectionTemplate::find($this->input('selectedTemplateId')); + if ($template) { + foreach ($template->fields as $field) { + if (($field['required'] ?? false) && empty($this->input("editInspectionFormData.{$field['name']}"))) { + $validator->errors()->add( + "editInspectionFormData.{$field['name']}", + "El campo {$field['label']} es obligatorio." + ); + } + } + } + }); + } +} \ No newline at end of file diff --git a/app/Http/Requests/IssueStoreRequest.php b/app/Http/Requests/IssueStoreRequest.php new file mode 100644 index 0000000..a91fd38 --- /dev/null +++ b/app/Http/Requests/IssueStoreRequest.php @@ -0,0 +1,36 @@ +route('project') ?? Project::find($this->input('project_id')); + if ($project) { + return $this->user()->can('create issues', $project); + } + return $this->user()->can('create issues'); + } + + public function rules(): array + { + return [ + 'title' => 'required|string|max:255', + 'description' => 'nullable|string', + 'status' => ['required', Rule::in(Issue::STATUSES)], + 'priority' => ['required', Rule::in(Issue::PRIORITIES)], + 'type' => ['required', Rule::in(Issue::TYPES)], + 'assignedTo' => 'nullable|exists:users,id', + 'resolutionNotes' => 'nullable|string', + 'featureId' => 'nullable|exists:features,id', + 'inspectionId' => 'nullable|exists:inspections,id', + 'project_id' => 'required|exists:projects,id', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/IssueUpdateRequest.php b/app/Http/Requests/IssueUpdateRequest.php new file mode 100644 index 0000000..1cf822c --- /dev/null +++ b/app/Http/Requests/IssueUpdateRequest.php @@ -0,0 +1,34 @@ +route('issue') ?? Issue::find($this->route('issue')); + if ($issue) { + return $this->user()->can('edit issues', $issue->project) && $this->user()->can('edit', $issue); + } + return $this->user()->can('edit issues'); + } + + public function rules(): array + { + return [ + 'title' => 'sometimes|required|string|max:255', + 'description' => 'nullable|string', + 'status' => ['sometimes', 'required', Rule::in(Issue::STATUSES)], + 'priority' => ['sometimes', 'required', Rule::in(Issue::PRIORITIES)], + 'type' => ['sometimes', 'required', Rule::in(Issue::TYPES)], + 'assigned_to' => 'nullable|exists:users,id', + 'resolution_notes' => 'nullable|string', + 'feature_id' => 'nullable|exists:features,id', + 'inspection_id' => 'nullable|exists:inspections,id', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/TaskStoreRequest.php b/app/Http/Requests/TaskStoreRequest.php new file mode 100644 index 0000000..a3bd092 --- /dev/null +++ b/app/Http/Requests/TaskStoreRequest.php @@ -0,0 +1,38 @@ +route('project') ?? $this->input('project_id'); + if ($project instanceof Project) { + return $this->user()->can('create tasks', $project); + } + return $this->user()->can('create tasks'); + } + + public function rules(): array + { + return [ + 'title' => 'required|string|max:255', + 'description' => 'nullable|string', + 'phase_id' => 'nullable|exists:phases,id', + 'status' => ['required', Rule::in(['pending', 'in_progress', 'completed', 'cancelled'])], + 'priority' => ['required', Rule::in(['low', 'medium', 'high', 'critical'])], + 'assigned_to' => 'nullable|exists:users,id', + 'due_date' => 'nullable|date', + 'start_date' => 'nullable|date|before_or_equal:due_date', + 'estimated_hours' => 'nullable|integer|min:0|max:10000', + 'actual_hours' => 'nullable|integer|min:0|max:10000', + 'order' => 'nullable|integer|min:0', + 'parent_task_id' => 'nullable|exists:tasks,id', + 'project_id' => 'required|exists:projects,id', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/TaskUpdateRequest.php b/app/Http/Requests/TaskUpdateRequest.php new file mode 100644 index 0000000..1cf7827 --- /dev/null +++ b/app/Http/Requests/TaskUpdateRequest.php @@ -0,0 +1,37 @@ +route('task') ?? Task::find($this->route('task')); + if ($task) { + return $this->user()->can('update', $task); + } + return $this->user()->can('update tasks'); + } + + public function rules(): array + { + return [ + 'title' => 'sometimes|required|string|max:255', + 'description' => 'nullable|string', + 'phase_id' => 'nullable|exists:phases,id', + 'status' => ['sometimes', 'required', Rule::in(['pending', 'in_progress', 'completed', 'cancelled'])], + 'priority' => ['sometimes', 'required', Rule::in(['low', 'medium', 'high', 'critical'])], + 'assigned_to' => 'nullable|exists:users,id', + 'due_date' => 'nullable|date', + 'start_date' => 'nullable|date|before_or_equal:due_date', + 'estimated_hours' => 'nullable|integer|min:0|max:10000', + 'actual_hours' => 'nullable|integer|min:0|max:10000', + 'order' => 'nullable|integer|min:0', + 'parent_task_id' => 'nullable|exists:tasks,id', + ]; + } +} \ No newline at end of file diff --git a/app/Livewire/Issues/IssueForm.php b/app/Livewire/Issues/IssueForm.php index 11a44d7..2f39bf9 100644 --- a/app/Livewire/Issues/IssueForm.php +++ b/app/Livewire/Issues/IssueForm.php @@ -8,6 +8,7 @@ use App\Models\Project; use App\Notifications\IssueAssignedNotification; use App\Notifications\IssueReportedNotification; use Illuminate\Support\Facades\Auth; +use Illuminate\Validation\Rule; use Livewire\Attributes\Layout; use Livewire\Component; diff --git a/app/Livewire/Tasks/TaskForm.php b/app/Livewire/Tasks/TaskForm.php index aee3ddd..a02a2cd 100644 --- a/app/Livewire/Tasks/TaskForm.php +++ b/app/Livewire/Tasks/TaskForm.php @@ -5,6 +5,8 @@ namespace App\Livewire\Tasks; use App\Models\Project; use App\Models\Task; use App\Models\User; +use App\Http\Requests\TaskStoreRequest; +use App\Http\Requests\TaskUpdateRequest; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Str; use Livewire\Attributes\Layout; @@ -126,7 +128,13 @@ class TaskForm extends Component public function save(): void { - $validated = $this->validate(); + $project = $this->project; + + if ($this->mode === 'edit' && $this->task) { + $validated = app(TaskUpdateRequest::class)->validated(); + } else { + $validated = app(TaskStoreRequest::class)->validated(); + } $data = [ 'title' => $validated['title'],