Files
chill-bundles/src/Bundle/ChillTicketBundle/src/Serializer/Normalizer/TicketNormalizer.php

284 lines
11 KiB
PHP

<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\TicketBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserGroup;
use Chill\PersonBundle\Entity\Person;
use Chill\TicketBundle\Entity\AddresseeHistory;
use Chill\TicketBundle\Entity\CallerHistory;
use Chill\TicketBundle\Entity\Comment;
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
use Chill\TicketBundle\Entity\MotiveHistory;
use Chill\TicketBundle\Entity\PersonHistory;
use Chill\TicketBundle\Entity\StateHistory;
use Chill\TicketBundle\Entity\Ticket;
use Chill\TicketBundle\Security\Voter\CommentVoter;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class TicketNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
use NormalizerAwareTrait;
public function __construct(private Security $security) {}
public function normalize($object, ?string $format = null, array $context = [])
{
if (!$object instanceof Ticket) {
throw new UnexpectedValueException();
}
$data = [
'type' => 'ticket_ticket',
'id' => $object->getId(),
'externalRef' => $object->getExternalRef(),
'createdAt' => $this->normalizer->normalize($object->getCreatedAt(), $format, $context),
'currentPersons' => $this->normalizer->normalize($object->getPersons(), $format, [
'groups' => 'read',
]),
'currentAddressees' => $this->normalizer->normalize($object->getCurrentAddressee(), $format, ['groups' => 'read']),
'currentInputs' => $this->normalizer->normalize($object->getCurrentInputs(), $format, ['groups' => 'read']),
'currentMotive' => $this->normalizer->normalize($object->getMotive(), $format, ['groups' => ['read', MotiveNormalizer::GROUP_CHILDREN_TO_PARENT]]),
'currentState' => $object->getState()?->value ?? 'open',
'emergency' => $object->getEmergencyStatus()?->value ?? 'no',
'caller' => $this->normalizer->normalize($object->getCaller(), $format, ['groups' => 'read']),
];
if ('read:simple' === $context['groups']) {
$data += ['type_extended' => 'ticket_ticket:simple'];
return $data;
}
$data += [
'type_extended' => 'ticket_ticket:extended',
'history' => array_values($this->serializeHistory($object, $format, ['groups' => 'read'])),
'updatedAt' => $this->normalizer->normalize($object->getUpdatedAt(), $format, $context),
'updatedBy' => $this->normalizer->normalize($object->getUpdatedBy(), $format, $context),
'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, $context),
];
return $data;
}
public function supportsNormalization($data, ?string $format = null)
{
return 'json' === $format && $data instanceof Ticket;
}
private function serializeHistory(Ticket $ticket, string $format, array $context): array
{
$events = [
...array_map(
fn (StateHistory $stateHistory) => [
'event_type' => 'state_change',
'at' => $stateHistory->getStartDate(),
'by' => $stateHistory->getCreatedBy(),
'data' => [
'new_state' => $stateHistory->getState()->value,
],
],
$ticket->getStateHistories()->toArray(),
),
...array_map(
fn (MotiveHistory $motiveHistory) => [
'event_type' => 'set_motive',
'at' => $motiveHistory->getStartDate(),
'by' => $motiveHistory->getCreatedBy(),
'data' => $motiveHistory,
],
$ticket->getMotiveHistories()->toArray()
),
...array_map(
fn (PersonHistory $personHistory) => [
'event_type' => 'add_person',
'at' => $personHistory->getStartDate(),
'by' => $personHistory->getCreatedBy(),
'data' => $personHistory,
],
$ticket->getPersonHistories()->toArray(),
),
...array_map(
fn (Comment $comment) => [
'event_type' => 'add_comment',
'at' => $comment->getCreatedAt(),
'by' => $comment->getCreatedBy(),
'data' => $comment,
],
$ticket->getComments()->filter(fn (Comment $comment) => $this->security->isGranted(CommentVoter::READ, $comment))->toArray(),
),
...$this->addresseesStates($ticket),
...$this->personStates($ticket),
...array_map(
fn (EmergencyStatusHistory $stateHistory) => [
'event_type' => 'emergency_change',
'at' => $stateHistory->getStartDate(),
'by' => $stateHistory->getCreatedBy(),
'data' => [
'new_emergency' => $stateHistory->getEmergencyStatus()->value,
],
],
$ticket->getEmergencyStatusHistories()->toArray(),
),
...array_map(
fn (CallerHistory $stateHistory) => [
'event_type' => 'set_caller',
'at' => $stateHistory->getStartDate(),
'by' => $stateHistory->getCreatedBy(),
'data' => [
'new_caller' => $this->normalizer->normalize($ticket->getCaller(), $format, ['groups' => ['read']]),
],
],
$ticket->getCallerHistories()->toArray(),
),
];
if (null !== $ticket->getCreatedBy() && null !== $ticket->getCreatedAt()) {
$events[] =
[
'event_type' => 'create_ticket',
'at' => \DateTimeImmutable::createFromInterface($ticket->getCreatedAt())
->sub(new \DateInterval('PT1S')), // TODO hack to avoid collision with creation of the ticket event,
'by' => $ticket->getCreatedBy(),
'data' => [],
];
}
usort(
$events,
static fn (array $a, array $b): int => $a['at'] <=> $b['at']
);
return array_map(
fn ($data) => [
'event_type' => $data['event_type'],
'at' => $this->normalizer->normalize($data['at'], $format, $context),
'by' => $this->normalizer->normalize($data['by'], $format, $context),
'data' => $this->normalizer->normalize($data['data'], $format, $this->contextByEventType($data['event_type'], $context)),
],
$events
);
}
private function contextByEventType(string $eventType, array $context): array
{
return match($eventType) {
'set_motive' => array_merge($context, ['groups' => ['read', MotiveNormalizer::GROUP_CHILDREN_TO_PARENT]]),
default => $context,
};
}
private function addresseesStates(Ticket $ticket): array
{
/** @var array{string, array{added: list<AddresseeHistory>, removed: list<AddresseeHistory>}} $changes */
$changes = [];
$dateFormat = 'Y-m-d-m-Y-H-i-s';
foreach ($ticket->getAddresseeHistories() as $history) {
$changes[$history->getStartDate()->format($dateFormat)]['added'][] = $history;
if (null !== $history->getEndDate()) {
$changes[$history->getEndDate()->format($dateFormat)]['removed'][] = $history;
}
}
ksort($changes);
$currents = [];
$steps = [];
foreach ($changes as $change) {
$historiesAdded = $change['added'] ?? [];
$historiesRemoved = $change['removed'] ?? [];
if (0 < count($historiesAdded)) {
$at = $historiesAdded[0]->getStartDate();
$by = $historiesAdded[0]->getCreatedBy();
} elseif (0 < count($historiesRemoved)) {
$at = $historiesRemoved[0]->getEndDate();
$by = $historiesRemoved[0]->getRemovedBy();
} else {
throw new \LogicException('it should have at least one history');
}
$removed = array_map(fn (AddresseeHistory $history) => $history->getAddressee(), $historiesRemoved);
$currents = array_filter($currents, fn (User|UserGroup $a) => !in_array($a, $removed, true));
foreach ($historiesAdded as $history) {
$currents[] = $history->getAddressee();
}
$steps[] = [
'event_type' => 'addressees_state',
'at' => $at,
'by' => $by,
'data' => [
'addressees' => array_values($currents),
],
];
}
return $steps;
}
private function personStates(Ticket $ticket): array
{
/** @var array{string, array{added: list<PersonHistory>, removed: list<PersonHistory>}} $changes */
$changes = [];
$dateFormat = 'Y-m-d-m-Y-H-i-s';
foreach ($ticket->getPersonHistories() as $history) {
$changes[$history->getStartDate()->format($dateFormat)]['added'][] = $history;
if (null !== $history->getEndDate()) {
$changes[$history->getEndDate()->format($dateFormat)]['removed'][] = $history;
}
}
ksort($changes);
$currents = [];
$steps = [];
foreach ($changes as $change) {
$historiesAdded = $change['added'] ?? [];
$historiesRemoved = $change['removed'] ?? [];
if (0 < count($historiesAdded)) {
$at = $historiesAdded[0]->getStartDate();
$by = $historiesAdded[0]->getCreatedBy();
} elseif (0 < count($historiesRemoved)) {
$at = $historiesRemoved[0]->getEndDate();
$by = $historiesRemoved[0]->getRemovedBy();
} else {
throw new \LogicException('it should have at least one history');
}
$removed = array_map(fn (PersonHistory $history) => $history->getPerson(), $historiesRemoved);
$currents = array_filter($currents, fn (Person $a) => !in_array($a, $removed, true));
foreach ($historiesAdded as $history) {
$currents[] = $history->getPerson();
}
$steps[] = [
'event_type' => 'persons_state',
'at' => $at,
'by' => $by,
'data' => [
'persons' => array_values($currents),
],
];
}
return $steps;
}
}