project = $project; if ($issue) { abort_unless($issue->project_id === $project->id, 404); } abort_unless($this->canAccessProject() && Auth::user()->can($issue ? 'edit issues' : 'create issues'), 403); $this->projectUsers = $project->users()->orderBy('name')->get(); if ($issue) { $this->issue = $issue; $this->title = $issue->title; $this->description = $issue->description ?? ''; $this->status = $issue->status; $this->priority = $issue->priority; $this->assignedTo = $issue->assigned_to ?? ''; $this->resolutionNotes = $issue->resolution_notes ?? ''; $this->featureId = $issue->feature_id; $this->inspectionId = $issue->inspection_id; $this->featureName = $issue->feature?->name; } elseif ($featureId = request()->integer('feature')) { // Pre-link to a map element when reporting from the project map. $feature = \App\Models\Feature::with('layer.phase')->find($featureId); if ($feature && $feature->layer?->phase?->project_id === $project->id) { $this->featureId = $feature->id; $this->featureName = $feature->name; } } } private function canAccessProject(): bool { $user = Auth::user(); return $user->can('manage all') || $this->project->users()->where('user_id', $user->id)->exists(); } protected function rules(): array { return [ 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'status' => 'required|in:' . implode(',', Issue::STATUSES), 'priority' => 'required|in:' . implode(',', Issue::PRIORITIES), 'assignedTo' => 'nullable|exists:users,id', 'resolutionNotes' => 'nullable|string', ]; } public function save() { abort_unless(Auth::user()->can($this->issue ? 'edit issues' : 'create issues'), 403); $this->validate(); $payload = [ 'title' => $this->title, 'description' => $this->description, 'status' => $this->status, 'priority' => $this->priority, 'feature_id' => $this->featureId, 'inspection_id' => $this->inspectionId, 'assigned_to' => $this->assignedTo ?: null, 'resolution_notes' => $this->resolutionNotes ?: null, ]; // Keep resolved_at in sync with the status $payload['resolved_at'] = in_array($this->status, ['resolved', 'closed']) ? now() : null; if ($this->issue) { $previousAssignee = $this->issue->assigned_to; // Don't overwrite an existing resolved date if ($this->issue->resolved_at && in_array($this->status, ['resolved', 'closed'])) { unset($payload['resolved_at']); } $this->issue->update($payload); $issue = $this->issue; // Notify a newly assigned user (when it changed and isn't the current actor). if ($issue->assigned_to && $issue->assigned_to !== $previousAssignee && $issue->assigned_to !== Auth::id()) { $issue->assignee?->notify(new IssueAssignedNotification($issue)); } } else { $issue = Issue::create(array_merge($payload, [ 'project_id' => $this->project->id, 'reported_by' => Auth::id(), ])); $issue->load(['feature', 'assignee']); $creator = $this->project->creator; if ($creator && $creator->id !== Auth::id()) { $creator->notify(new IssueReportedNotification($issue)); } if ($issue->assignee && $issue->assignee->id !== Auth::id()) { $issue->assignee->notify(new IssueReportedNotification($issue)); } } session()->flash('message', $this->issue ? 'Incidencia actualizada' : 'Incidencia creada'); return $this->redirectRoute('projects.issues.show', ['project' => $this->project, 'issue' => $issue], navigate: true); } public function render() { return view('livewire.issues.issue-form'); } }