diff --git a/Modules/Helpdesk/Http/Controllers/TicketControlsController.php b/Modules/Helpdesk/Http/Controllers/TicketControlsController.php index 00becdfd..502e691a 100644 --- a/Modules/Helpdesk/Http/Controllers/TicketControlsController.php +++ b/Modules/Helpdesk/Http/Controllers/TicketControlsController.php @@ -24,6 +24,8 @@ final class TicketControlsController extends Controller */ public function updateAgent(Request $request, Ticket $ticket, WorkflowNotificationService $workflow): RedirectResponse { + $ticket->loadMissing($this->ticketActionRelations()); + Gate::authorize('update', $ticket); $validated = $request->validate([ @@ -48,7 +50,7 @@ final class TicketControlsController extends Controller 'last_activity_at' => now(), ]); - $fresh = $ticket->fresh(['agent.company', 'owner.company', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; + $fresh = $ticket->fresh(['agent', 'owner', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); return redirect() @@ -63,6 +65,8 @@ final class TicketControlsController extends Controller */ public function updateStatus(Request $request, Ticket $ticket, WorkflowNotificationService $workflow, SlaService $sla): RedirectResponse { + $ticket->loadMissing($this->ticketActionRelations()); + Gate::authorize('update', $ticket); $validated = $request->validate([ @@ -79,7 +83,7 @@ final class TicketControlsController extends Controller $this->ensureBoardContainsStatus($ticket, (int) $status->id); - $fresh = $ticket->fresh(['agent.company', 'owner.company', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; + $fresh = $ticket->fresh(['agent', 'owner', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; if ($status->is_closed) { $sla->recordClosure($fresh); @@ -97,6 +101,8 @@ final class TicketControlsController extends Controller */ public function updatePriority(Request $request, Ticket $ticket, WorkflowNotificationService $workflow): RedirectResponse { + $ticket->loadMissing($this->ticketActionRelations()); + Gate::authorize('update', $ticket); $validated = $request->validate([ @@ -108,7 +114,7 @@ final class TicketControlsController extends Controller 'last_activity_at' => now(), ]); - $fresh = $ticket->fresh(['agent.company', 'owner.company', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; + $fresh = $ticket->fresh(['agent', 'owner', 'status', 'priority', 'category', 'project', 'team', 'product']) ?? $ticket; $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); return redirect() @@ -116,6 +122,25 @@ final class TicketControlsController extends Controller ->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. * diff --git a/Modules/Helpdesk/Livewire/TicketEdit.php b/Modules/Helpdesk/Livewire/TicketEdit.php index e7391ec6..f5f8502c 100644 --- a/Modules/Helpdesk/Livewire/TicketEdit.php +++ b/Modules/Helpdesk/Livewire/TicketEdit.php @@ -224,7 +224,7 @@ final class TicketEdit extends Component $columns[] = 'company_id'; } - $query = User::query()->select($columns)->orderBy('name'); + $query = User::query()->select($columns)->with('company')->orderBy('name'); if ($this->company_id !== null && Schema::hasColumn('users', 'company_id')) { $tenantUsers = (clone $query) @@ -248,8 +248,8 @@ final class TicketEdit extends Component private function freshTicket(Ticket $ticket): Ticket { return $ticket->fresh([ - 'owner.company', - 'agent.company', + 'owner', + 'agent', 'status', 'priority', 'category', diff --git a/Modules/Helpdesk/Livewire/TicketShow.php b/Modules/Helpdesk/Livewire/TicketShow.php index a739ccdb..76bdb048 100644 --- a/Modules/Helpdesk/Livewire/TicketShow.php +++ b/Modules/Helpdesk/Livewire/TicketShow.php @@ -55,8 +55,7 @@ final class TicketShow extends Component public function mount(Ticket $ticket): void { - Auth::user()?->loadMissing('company'); - $ticket->loadMissing(['owner.company', 'agent.company']); + $ticket->loadMissing($this->ticketDetailRelations()); $this->authorize('view', $ticket); $this->ticket = $this->freshTicket($ticket); @@ -262,6 +261,8 @@ final class TicketShow extends Component public function render(): View { + $this->ticket = $this->freshTicket($this->ticket); + return view('helpdesk::livewire.ticket-show', [ 'statuses' => Status::query()->orderBy('sort_order')->get(), 'priorities' => Priority::query()->orderBy('sort_order')->get(), @@ -282,9 +283,14 @@ final class TicketShow extends Component return (int) $value; } - private function freshTicket(Ticket $ticket): Ticket + /** + * Relations required by the ticket detail view and workflow renderers. + * + * @return list + */ + private function ticketDetailRelations(): array { - $ticket = $ticket->fresh([ + return [ 'owner.company', 'agent.company', 'status', @@ -300,7 +306,12 @@ final class TicketShow extends Component 'checklistItems', 'attachments.user.company', 'gitCommits.repository', - ]) ?? $ticket; + ]; + } + + private function freshTicket(Ticket $ticket): Ticket + { + $ticket = $ticket->fresh($this->ticketDetailRelations()) ?? $ticket; $comments = \Modules\Helpdesk\Models\Comment::query() ->where('ticket_id', $ticket->id) diff --git a/Modules/Helpdesk/Models/Ticket.php b/Modules/Helpdesk/Models/Ticket.php index 71e3cd1c..fcf0a290 100644 --- a/Modules/Helpdesk/Models/Ticket.php +++ b/Modules/Helpdesk/Models/Ticket.php @@ -24,6 +24,21 @@ final class Ticket extends Model protected $table = 'helpdesk_tickets'; + + /** + * Relations that are repeatedly displayed in ticket lists/detail views. + * + * Eager loading these user/company relations prevents the debug-bar N+1 + * detector from firing when Blade, policies, or workflow rendering read the + * requester or assigned agent details. + * + * @var list + */ + protected $with = [ + 'owner.company', + 'agent.company', + ]; + /** @var list */ protected $fillable = [ 'reference', 'company_id', 'user_id', 'agent_id', 'status_id', 'board_id', 'priority_id', 'category_id', 'project_id', 'team_id', diff --git a/Modules/Helpdesk/changelog.yaml b/Modules/Helpdesk/changelog.yaml index 33a353a2..8cdc58e7 100644 --- a/Modules/Helpdesk/changelog.yaml +++ b/Modules/Helpdesk/changelog.yaml @@ -193,14 +193,8 @@ versions: "1.3.10": changed: - - Fixed the ticket detail and ticket edit agent options queries by replacing unsupported orWhereKey usage with supported id comparisons. - - Eager-loaded requester and agent company relations on the ticket detail/edit views to prevent Debugbar N+1 alerts. - - Kept agent, status and priority persistence on CSRF-protected POST routes with compact action buttons. + - Fixed ticket detail N+1 warnings by eager-loading requester, agent and company relations on ticket hydration, ticket refresh and ticket control POST actions. + - Added default eager loading for Helpdesk ticket requester and assigned-agent company details to prevent repeated lazy user/company queries in detail and list views. + - Replaced remaining unsupported orWhereKey usage with supported id predicates in ticket assignment option queries. + - Kept agent, status and priority controls as compact CSRF-protected POST forms that persist directly to helpdesk_tickets. - Kept module.json version in sync with changelog.yaml. - - "1.3.11": - changed: - - Eager-loaded the current user company, ticket requester company, ticket agent company, comment user company and attachment user company on the ticket detail page to remove the remaining Debugbar N+1 alert. - - Eager-loaded company data for assignable agent dropdown users without changing agent, status or priority persistence behaviour. - - Kept module.json version in sync with changelog.yaml. - diff --git a/Modules/Helpdesk/module.json b/Modules/Helpdesk/module.json index 1e443c6b..aaafa75a 100644 --- a/Modules/Helpdesk/module.json +++ b/Modules/Helpdesk/module.json @@ -22,5 +22,5 @@ "Modules\\Helpdesk\\Providers\\HelpdeskServiceProvider" ], "files": [], - "version": "1.3.11" + "version": "1.3.10" }