diff --git a/Modules/Helpdesk/Livewire/KanbanBoard.php b/Modules/Helpdesk/Livewire/KanbanBoard.php index 13dcf90f..2331a590 100644 --- a/Modules/Helpdesk/Livewire/KanbanBoard.php +++ b/Modules/Helpdesk/Livewire/KanbanBoard.php @@ -94,42 +94,59 @@ final class KanbanBoard extends Component /** * Move a ticket from its current board to another board. A ticket can only belong to one board. + * + * This intentionally redirects to the target board after the update so the browser, + * Livewire state and database state cannot fall back to the default board. */ - public function moveTicketToBoard(int|string $ticketId, int|string|null $boardId, WorkflowNotificationService $workflow): void + public function moveTicketToBoard(int|string $ticketId, int|string|null $boardId, WorkflowNotificationService $workflow) { - $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); - } + if (! is_numeric($ticketId) || ! is_numeric($boardId)) { + return; } - $ticket->forceFill([ - 'board_id' => $board->id, - 'last_activity_at' => now(), - ])->save(); + $board = KanbanBoardModel::query() + ->with(['statuses' => fn ($query) => $query->orderBy('helpdesk_kanban_board_statuses.sort_order')->orderBy('helpdesk_statuses.sort_order'), 'owner:id,name,email']) + ->findOrFail((int) $boardId); - $this->board_id = $board->id; + if ($board->statuses->isEmpty()) { + $this->ensureBoardHasStatuses($board); + $board->load(['statuses' => fn ($query) => $query->orderBy('helpdesk_kanban_board_statuses.sort_order')->orderBy('helpdesk_statuses.sort_order'), 'owner:id,name,email']); + } - $fresh = $ticket->fresh(['status', 'priority', 'category', 'project', 'team', 'board']) ?? $ticket; - $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); + $fresh = DB::transaction(function () use ($ticketId, $board) { + $ticket = Ticket::query() + ->with(['board', 'status']) + ->visibleTo(Auth::user()) + ->lockForUpdate() + ->findOrFail((int) $ticketId); - $message = 'Ticket ' . $fresh->reference . ' moved from ' . $oldBoardName . ' to ' . $board->name . '.'; - session()->flash('success', $message); + $this->authorize('update', $ticket); - $this->refreshBoard(); - $this->dispatch('helpdesk-kanban-moved', ticketId: (int) $fresh->id, statusId: (int) $fresh->status_id); - $this->dispatch('wms:toast', type: 'success', title: 'Board changed', message: $message); + $targetStatus = $board->statuses->firstWhere('id', (int) $ticket->status_id) + ?: $board->statuses->first(); + + if ($targetStatus === null) { + $this->addStatusToBoard($board->id, (int) $ticket->status_id); + $targetStatus = $ticket->status; + } + + $ticket->forceFill([ + 'board_id' => $board->id, + 'status_id' => $targetStatus?->id ?? $ticket->status_id, + 'completed_at' => $targetStatus?->is_closed ? now() : null, + 'last_activity_at' => now(), + ])->save(); + + return $ticket->fresh(['status', 'priority', 'category', 'project', 'team', 'product', 'tags', 'checklistItems', 'board']); + }); + + if ($fresh instanceof Ticket) { + $workflow->dispatch($fresh, 'ticket.updated', Auth::user()); + + session()->flash('success', 'Ticket ' . $fresh->reference . ' moved to ' . $board->name . '.'); + } + + return redirect()->route('helpdesk.kanban', ['board_id' => $board->id]); } /**