307 lines
9.3 KiB
PHP
307 lines
9.3 KiB
PHP
<?php
|
|
|
|
namespace App\Actions\Tickets;
|
|
|
|
use App\Models\Ticket;
|
|
use App\Models\TicketFieldDefinition;
|
|
use App\Models\TicketFieldValue;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class UpdateProjectTicket
|
|
{
|
|
public function __construct(
|
|
private readonly CreateTicketWorklogEntry $createTicketWorklogEntry,
|
|
) {}
|
|
|
|
/**
|
|
* @param array{
|
|
* acceptance_criteria: string|null,
|
|
* description: string|null,
|
|
* ticket_priority_id: int,
|
|
* ticket_type_id: int,
|
|
* title: string
|
|
* } $ticketAttributes
|
|
* @param array<int, array{
|
|
* ticket_field_definition_id: int,
|
|
* value_number?: float|int|string|null,
|
|
* value_user_id?: int|string|null
|
|
* }> $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',
|
|
'project',
|
|
'ticketPriority',
|
|
'ticketType',
|
|
]);
|
|
|
|
/** @var Collection<int, TicketFieldDefinition> $fieldDefinitions */
|
|
$fieldDefinitions = $ticket->project->ticketFieldDefinitions()
|
|
->get()
|
|
->keyBy('id');
|
|
|
|
$ticketTypeOptions = $ticket->project->ticketTypes()
|
|
->get()
|
|
->keyBy('id');
|
|
$ticketPriorityOptions = $ticket->project->ticketPriorities()
|
|
->get()
|
|
->keyBy('id');
|
|
|
|
/** @var Collection<int, TicketFieldValue> $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';
|
|
}
|
|
|
|
$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<string, mixed> $changes
|
|
* @param array<int, string> $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<string, mixed> $changes
|
|
* @param array{value_user_id?: int|string|null} $fieldValueAttribute
|
|
* @param array<int, string> $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<string, mixed> $changes
|
|
* @param array{value_number?: float|int|string|null} $fieldValueAttribute
|
|
* @param array<int, string> $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<int, string> $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'), '.');
|
|
}
|
|
}
|