$fieldValueAttributes */ public function handle( Ticket $ticket, User $actor, array $ticketAttributes, array $fieldValueAttributes, ): bool { return DB::transaction(function () use ($actor, $fieldValueAttributes, $ticket, $ticketAttributes): bool { $ticket->loadMissing([ 'fieldValues.ticketFieldDefinition', 'fieldValues.userValue', 'parentTicket', 'project', 'ticketPriority', 'ticketType', ]); /** @var Collection $fieldDefinitions */ $fieldDefinitions = $ticket->project->ticketFieldDefinitions() ->get() ->keyBy('id'); $ticketTypeOptions = $ticket->project->ticketTypes() ->get() ->keyBy('id'); $ticketPriorityOptions = $ticket->project->ticketPriorities() ->get() ->keyBy('id'); /** @var Collection $existingFieldValues */ $existingFieldValues = $ticket->fieldValues ->keyBy('ticket_field_definition_id'); $changes = []; $summaryLabels = []; $this->trackScalarChange( $changes, $summaryLabels, 'title', 'title', $ticket->title, $ticketAttributes['title'], ); $this->trackScalarChange( $changes, $summaryLabels, 'description', 'description', $ticket->description, $ticketAttributes['description'], ); $this->trackScalarChange( $changes, $summaryLabels, 'acceptance_criteria', 'acceptance criteria', $ticket->acceptance_criteria, $ticketAttributes['acceptance_criteria'], ); if ($ticket->ticket_type_id !== $ticketAttributes['ticket_type_id']) { $changes['ticket_type'] = [ 'from' => $ticket->ticketType?->name, 'to' => $ticketTypeOptions->get($ticketAttributes['ticket_type_id'])?->name, ]; $summaryLabels[] = 'type'; } if ($ticket->ticket_priority_id !== $ticketAttributes['ticket_priority_id']) { $changes['ticket_priority'] = [ 'from' => $ticket->ticketPriority?->name, 'to' => $ticketPriorityOptions->get($ticketAttributes['ticket_priority_id'])?->name, ]; $summaryLabels[] = 'priority'; } if ($ticket->parent_ticket_id !== $ticketAttributes['parent_ticket_id']) { $newParentTicket = $ticketAttributes['parent_ticket_id'] === null ? null : Ticket::query()->find($ticketAttributes['parent_ticket_id']); $changes['parent_ticket'] = [ 'from' => $ticket->parentTicket?->ticket_key, 'to' => $newParentTicket?->ticket_key, ]; $summaryLabels[] = 'parent ticket'; } $ticket->fill($ticketAttributes); if ($ticket->isDirty()) { $ticket->save(); } foreach ($fieldValueAttributes as $fieldValueAttribute) { $fieldDefinition = $fieldDefinitions->get((int) $fieldValueAttribute['ticket_field_definition_id']); if ($fieldDefinition === null) { continue; } $existingFieldValue = $existingFieldValues->get($fieldDefinition->id); if ($fieldDefinition->field_type === 'user') { $this->syncUserFieldValue( $changes, $existingFieldValue, $fieldDefinition, $fieldValueAttribute, $summaryLabels, $ticket, ); continue; } $this->syncNumberFieldValue( $changes, $existingFieldValue, $fieldDefinition, $fieldValueAttribute, $summaryLabels, $ticket, ); } if ($summaryLabels === []) { return false; } $this->createTicketWorklogEntry->handle( $ticket->fresh(), $actor, 'updated', $this->buildSummary($summaryLabels), $changes, ); return true; }); } /** * @param array $changes * @param array $summaryLabels */ private function trackScalarChange( array &$changes, array &$summaryLabels, string $changeKey, string $summaryLabel, ?string $currentValue, ?string $newValue, ): void { if ($currentValue === $newValue) { return; } $changes[$changeKey] = [ 'from' => $currentValue, 'to' => $newValue, ]; $summaryLabels[] = $summaryLabel; } /** * @param array $changes * @param array{value_user_id?: int|string|null} $fieldValueAttribute * @param array $summaryLabels */ private function syncUserFieldValue( array &$changes, ?TicketFieldValue $existingFieldValue, TicketFieldDefinition $fieldDefinition, array $fieldValueAttribute, array &$summaryLabels, Ticket $ticket, ): void { $newUserId = $fieldValueAttribute['value_user_id'] ?? null; $newUserId = $newUserId === null || $newUserId === '' ? null : (int) $newUserId; if ($existingFieldValue?->value_user_id === $newUserId) { return; } $oldUserName = $existingFieldValue?->userValue?->name; $newUserName = $newUserId === null ? null : User::query()->find($newUserId)?->name; $changes['field_values'][$fieldDefinition->id] = [ 'field' => $fieldDefinition->label, 'from' => $oldUserName, 'to' => $newUserName, ]; $summaryLabels[] = "{$fieldDefinition->label} field"; if ($newUserId === null) { $existingFieldValue?->delete(); return; } $ticket->fieldValues()->updateOrCreate( [ 'ticket_field_definition_id' => $fieldDefinition->id, ], [ 'value_number' => null, 'value_user_id' => $newUserId, ], ); } /** * @param array $changes * @param array{value_number?: float|int|string|null} $fieldValueAttribute * @param array $summaryLabels */ private function syncNumberFieldValue( array &$changes, ?TicketFieldValue $existingFieldValue, TicketFieldDefinition $fieldDefinition, array $fieldValueAttribute, array &$summaryLabels, Ticket $ticket, ): void { $newNumber = $fieldValueAttribute['value_number'] ?? null; $newNumber = $newNumber === null || $newNumber === '' ? null : (float) $newNumber; $oldNumber = $existingFieldValue?->value_number; if ($oldNumber === $newNumber) { return; } $changes['field_values'][$fieldDefinition->id] = [ 'field' => $fieldDefinition->label, 'from' => $this->formatNumber($oldNumber), 'to' => $this->formatNumber($newNumber), ]; $summaryLabels[] = "{$fieldDefinition->label} field"; if ($newNumber === null) { $existingFieldValue?->delete(); return; } $ticket->fieldValues()->updateOrCreate( [ 'ticket_field_definition_id' => $fieldDefinition->id, ], [ 'value_number' => $newNumber, 'value_user_id' => null, ], ); } /** * @param array $summaryLabels */ private function buildSummary(array $summaryLabels): string { $uniqueLabels = collect($summaryLabels) ->unique() ->values(); if ($uniqueLabels->count() === 1) { return 'Updated '.$uniqueLabels->first().'.'; } if ($uniqueLabels->count() === 2) { return 'Updated '.$uniqueLabels->first().' and '.$uniqueLabels->last().'.'; } $lastLabel = $uniqueLabels->pop(); return 'Updated '.$uniqueLabels->implode(', ').', and '.$lastLabel.'.'; } private function formatNumber(?float $value): ?string { if ($value === null) { return null; } return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.'); } }