loadMissing($this->ticketActionRelations()); Gate::authorize('update', $ticket); $validated = $request->validate([ 'agent_id' => [ 'nullable', 'integer', Rule::exists('users', 'id')->where(function ($query) use ($ticket): void { if ($ticket->company_id !== null && Schema::hasColumn('users', 'company_id')) { $query->where(function ($query) use ($ticket): void { $query->where('company_id', $ticket->company_id) ->orWhereNull('company_id'); }); } }), ], ]); $agentId = $this->nullableInt($validated['agent_id'] ?? null); $this->updateTicket($ticket, [ 'agent_id' => $agentId, 'last_activity_at' => now(), ]); $fresh = $ticket->fresh(['agent', 'owner', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); return redirect() ->route('helpdesk.tickets.show', $fresh) ->with('ticket-action-updated', $agentId === null ? 'Ticket is now unassigned.' : 'Ticket assigned to '.($fresh->agent?->name ?? 'selected agent').'.'); } /** * Persist the selected ticket status from the ticket detail controls. */ public function updateStatus(Request $request, Ticket $ticket, WorkflowNotificationService $workflow, SlaService $sla): RedirectResponse { $ticket->loadMissing($this->ticketActionRelations()); Gate::authorize('update', $ticket); $validated = $request->validate([ 'status_id' => ['required', 'integer', Rule::exists('helpdesk_statuses', 'id')], ]); $status = Status::query()->findOrFail((int) $validated['status_id']); $this->updateTicket($ticket, [ 'status_id' => $status->id, 'completed_at' => $status->is_closed ? now() : null, 'last_activity_at' => now(), ]); $this->ensureBoardContainsStatus($ticket, (int) $status->id); $fresh = $ticket->fresh(['agent', 'owner', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; if ($status->is_closed) { $sla->recordClosure($fresh); } $workflow->dispatch($fresh, $status->is_closed ? 'ticket.closed' : 'ticket.updated', Auth::user()); return redirect() ->route('helpdesk.tickets.show', $fresh) ->with('ticket-action-updated', 'Status updated to '.($fresh->status?->name ?? 'selected status').'.'); } /** * Persist the selected ticket priority from the ticket detail controls. */ public function updatePriority(Request $request, Ticket $ticket, WorkflowNotificationService $workflow): RedirectResponse { $ticket->loadMissing($this->ticketActionRelations()); Gate::authorize('update', $ticket); $validated = $request->validate([ 'priority_id' => ['required', 'integer', Rule::exists('helpdesk_priorities', 'id')], ]); $this->updateTicket($ticket, [ 'priority_id' => (int) $validated['priority_id'], 'last_activity_at' => now(), ]); $fresh = $ticket->fresh(['agent', 'owner', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); return redirect() ->route('helpdesk.tickets.show', $fresh) ->with('ticket-action-updated', 'Priority updated to '.($fresh->priority?->name ?? 'selected priority').'.'); } /** * Relations needed by policies, redirect messages, and workflow templates. * * @return list */ private function ticketActionRelations(): array { return [ 'owner.company', 'agent.company', 'status', 'priority', 'category', 'project', 'team', 'product', ]; } /** * Apply a direct guarded ticket update without relying on Livewire hydration. * * @param array $updates */ private function updateTicket(Ticket $ticket, array $updates): void { if (Schema::hasColumn('helpdesk_tickets', 'updated_at')) { $updates['updated_at'] = now(); } DB::table('helpdesk_tickets') ->where('id', $ticket->id) ->update($updates); } /** * Keep the active Kanban board usable when a ticket is moved to a status not yet assigned to the board. */ private function ensureBoardContainsStatus(Ticket $ticket, int $statusId): void { if ($ticket->board_id === null || ! Schema::hasTable('helpdesk_kanban_board_statuses')) { return; } DB::table('helpdesk_kanban_board_statuses')->updateOrInsert( ['board_id' => $ticket->board_id, 'status_id' => $statusId], ['updated_at' => now(), 'created_at' => now()], ); } /** * Convert form select values to nullable integers. */ private function nullableInt(mixed $value): ?int { if ($value === null || $value === '' || $value === '0' || $value === 0) { return null; } return (int) $value; } }