user(); abort_unless( $this->permissionService->allows($user, Permission::ProjectWrite, $project), 403, ); $source = Arr::get($request->query(), 'source') === 'board' ? 'board' : 'backlog'; $defaultTicketStatus = $source === 'board' ? $project->ticketStatuses()->whereNotNull('board_column_id')->first() : $project->ticketStatuses()->whereNull('board_column_id')->first(); $defaultTicketStatus ??= $project->ticketStatuses()->first(); return Inertia::render('projects/TicketCreate', [ 'defaultTicketStatusId' => $defaultTicketStatus?->id, 'project' => $project, 'source' => $source, 'status' => session('status'), 'ticketPriorities' => $project->ticketPriorities()->get(), 'ticketStatusOptions' => $project->ticketStatuses() ->with('boardColumn') ->get(), 'ticketTypes' => $project->ticketTypes()->get(), 'users' => User::query() ->orderBy('name') ->get(), ]); } public function store(StoreProjectTicketRequest $request, Project $project): RedirectResponse { $user = $request->user(); abort_unless( $this->permissionService->allows($user, Permission::ProjectWrite, $project), 403, ); $ticket = $this->createProjectTicket->handle( $project, $request->validated(), $user, ); return to_route('projects.tickets.show', [$project, $ticket]) ->with('status', "{$ticket->ticket_key} was created."); } public function show(Request $request, Project $project, Ticket $ticket): Response { $user = $request->user(); abort_unless( $this->permissionService->allows($user, Permission::ProjectRead, $project), 403, ); $canEditTicket = $this->permissionService->allows($user, Permission::ProjectWrite, $project); $canManageProject = $this->permissionService->allows($user, Permission::ProjectAdmin, $project); $displayTicket = $ticket->load([ 'assignee', 'author', 'fieldValues.ticketFieldDefinition', 'fieldValues.userValue', 'parentTicket', 'ticketPriority', 'ticketStatus.boardColumn', 'ticketType', 'worklogEntries.actor', ]); if (! $project->show_author && ! $canManageProject) { $displayTicket->setRelation('author', null); $displayTicket->author_user_id = null; } return Inertia::render('projects/Ticket', [ 'canEditTicket' => $canEditTicket, 'canManageProject' => $canManageProject, 'childTicketTree' => $this->buildChildTicketTree($ticket), 'linkedTickets' => $this->linkedTickets($ticket), 'parentTicketOptions' => Ticket::query() ->where('project_id', $project->id) ->whereKeyNot($ticket->id) ->whereHas('ticketType', fn ($query) => $query->where('is_parent', true)) ->orderByDesc('sequence_number') ->get() ->map(fn (Ticket $parentTicket): array => [ 'id' => $parentTicket->id, 'ticket_key' => $parentTicket->ticket_key, 'title' => $parentTicket->title, ]), 'project' => $project, 'repositoryBranches' => $this->resolveTicketBranchMatches->handle( $user, $project, $ticket, ), 'repositoryPullRequests' => $this->resolveTicketPullRequestMatches->handle( $user, $project, $ticket, ), 'status' => session('status'), 'suggestedBranchName' => $this->buildSuggestedTicketBranchName->handle($ticket), 'ticket' => $displayTicket, 'ticketFieldDefinitions' => $project->ticketFieldDefinitions()->get(), 'ticketLinkOptions' => Ticket::query() ->where('project_id', $project->id) ->whereKeyNot($ticket->id) ->orderByDesc('sequence_number') ->get() ->map(fn (Ticket $linkedTicket): array => [ 'id' => $linkedTicket->id, 'ticket_key' => $linkedTicket->ticket_key, 'title' => $linkedTicket->title, ]), 'ticketLinkTypes' => $project->ticketLinkTypes()->get(), 'ticketPriorities' => $project->ticketPriorities()->get(), 'ticketTypes' => $project->ticketTypes()->get(), 'users' => User::query() ->orderBy('name') ->get(), ]); } public function update( UpdateProjectTicketRequest $request, Project $project, Ticket $ticket, ): RedirectResponse { $user = $request->user(); abort_unless( $this->permissionService->allows($user, Permission::ProjectWrite, $project), 403, ); $validated = $request->validated(); $fieldValues = Arr::get($validated, 'field_values', []); $ticketAttributes = $this->buildProjectTicketUpdateAttributes->handle($ticket, $validated); $wasUpdated = $this->updateProjectTicket->handle( $ticket, $user, $ticketAttributes, $fieldValues, ); $statusMessage = $wasUpdated ? "{$ticket->ticket_key} was updated." : 'No ticket changes were detected.'; return to_route('projects.tickets.show', [$project, $ticket]) ->with('status', $statusMessage); } /** * @return array, * id: int, * ticket_key: string, * ticket_type_name: string|null, * title: string * }> */ private function buildChildTicketTree(Ticket $ticket): array { return $ticket->childTickets() ->with('ticketType') ->get() ->map(fn (Ticket $childTicket): array => [ 'children' => $this->buildChildTicketTree($childTicket), 'id' => $childTicket->id, 'ticket_key' => $childTicket->ticket_key, 'ticket_type_name' => $childTicket->ticketType?->name, 'title' => $childTicket->title, ]) ->all(); } /** * @return array */ private function linkedTickets(Ticket $ticket): array { return TicketLink::query() ->with(['sourceTicket', 'targetTicket', 'ticketLinkType']) ->where(function ($query) use ($ticket): void { $query ->where('source_ticket_id', $ticket->id) ->orWhere('target_ticket_id', $ticket->id); }) ->orderBy('id') ->get() ->map(function (TicketLink $ticketLink) use ($ticket): array { $linkedTicket = $ticketLink->source_ticket_id === $ticket->id ? $ticketLink->targetTicket : $ticketLink->sourceTicket; return [ 'created_at' => $ticketLink->created_at?->toISOString(), 'id' => $ticketLink->id, 'linked_ticket' => [ 'id' => $linkedTicket->id, 'ticket_key' => $linkedTicket->ticket_key, 'title' => $linkedTicket->title, ], 'source_ticket_id' => $ticketLink->source_ticket_id, 'target_ticket_id' => $ticketLink->target_ticket_id, 'ticket_link_type' => $ticketLink->ticketLinkType, 'ticket_link_type_id' => $ticketLink->ticket_link_type_id, 'updated_at' => $ticketLink->updated_at?->toISOString(), ]; }) ->all(); } }