board_id = KanbanBoardModel::query() ->where('is_default', true) ->orderBy('sort_order') ->value('id') ?: KanbanBoardModel::query()->orderBy('sort_order')->value('id'); } /** * Move a ticket to another workflow status on the active board. */ public function moveTicket(int|string $ticketId, int|string $statusId, WorkflowNotificationService $workflow): void { $ticket = Ticket::query()->visibleTo(Auth::user())->findOrFail((int) $ticketId); $status = Status::query()->findOrFail((int) $statusId); $board = $this->activeBoard(); $oldStatusName = $ticket->status?->name ?? 'previous status'; $this->authorize('update', $ticket); if ($board !== null && ! $board->statuses()->whereKey($status->id)->exists()) { $this->addStatusToBoard($board->id, $status->id); } $ticket->forceFill([ 'board_id' => $board?->id, 'status_id' => $status->id, 'completed_at' => $status->is_closed ? now() : null, 'last_activity_at' => now(), ])->save(); $workflow->dispatch( $ticket->fresh(['owner', 'agent', 'status', 'priority', 'category', 'project', 'team']) ?? $ticket, $status->is_closed ? 'ticket.closed' : 'ticket.updated', Auth::user() ); $message = 'Ticket '.$ticket->reference.' moved from '.$oldStatusName.' to '.$status->name.'.'; session()->flash('success', $message); $this->dispatch('wms:toast', type: 'success', title: 'Ticket moved', message: $message); } /** * Move a ticket from its current board to another board. A ticket can only belong to one board. */ public function moveTicketToBoard(int|string $ticketId, int|string|null $boardId, WorkflowNotificationService $workflow): void { $ticket = Ticket::query()->with('board')->visibleTo(Auth::user())->findOrFail((int) $ticketId); $board = KanbanBoardModel::query()->with(['statuses', 'owner:id,name,email'])->findOrFail((int) $boardId); $oldBoardName = $ticket->board?->name ?? 'No board'; $this->authorize('update', $ticket); if (! $board->statuses->contains('id', $ticket->status_id)) { $firstStatus = $board->statuses->first(); if ($firstStatus !== null) { $ticket->status_id = $firstStatus->id; $ticket->completed_at = $firstStatus->is_closed ? now() : null; } else { $this->addStatusToBoard($board->id, (int) $ticket->status_id); } } $ticket->forceFill([ 'board_id' => $board->id, 'last_activity_at' => now(), ])->save(); $fresh = $ticket->fresh(['owner', 'agent', 'status', 'priority', 'category', 'project', 'team', 'board']) ?? $ticket; $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); $message = 'Ticket '.$fresh->reference.' moved from '.$oldBoardName.' to '.$board->name.'.'; session()->flash('success', $message); $this->dispatch('wms:toast', type: 'success', title: 'Board changed', message: $message); } /** * Render the Kanban board. */ public function render(): View { $boards = KanbanBoardModel::query() ->with(['statuses', 'owner:id,name,email']) ->orderBy('sort_order') ->orderBy('name') ->get(); $board = $boards->firstWhere('id', $this->board_id) ?? $boards->first(); $statuses = $board?->statuses ?? collect(); if ($statuses->isEmpty()) { $statuses = Status::query()->orderBy('sort_order')->orderBy('name')->get(); } return view('helpdesk::livewire.kanban-board', [ 'boards' => $boards, 'board' => $board, 'statuses' => $statuses, 'ticketsByStatus' => $this->ticketsByStatus(), ])->layout('layouts.app'); } /** * Get visible tickets grouped by status for the active board only. * * @return array> */ private function ticketsByStatus(): array { $board = $this->activeBoard(); return Ticket::query() ->with(['owner', 'agent', 'status', 'priority', 'category', 'project', 'team', 'product', 'tags', 'checklistItems', 'board']) ->visibleTo(Auth::user()) ->when($board !== null, fn ($query) => $query->where('board_id', $board->id)) ->latest('last_activity_at') ->limit(300) ->get() ->groupBy('status_id') ->all(); } /** * Resolve the current board from the selected board ID. */ private function activeBoard(): ?KanbanBoardModel { if ($this->board_id !== null) { return KanbanBoardModel::query()->find((int) $this->board_id); } return KanbanBoardModel::query() ->where('is_default', true) ->orderBy('sort_order') ->first() ?: KanbanBoardModel::query()->orderBy('sort_order')->first(); } /** * Attach a missing status to a board when a move requires it. */ private function addStatusToBoard(int $boardId, int $statusId): void { DB::table('helpdesk_kanban_board_statuses')->updateOrInsert( ['board_id' => $boardId, 'status_id' => $statusId], ['sort_order' => 999, 'updated_at' => now(), 'created_at' => now()] ); } }