mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-07-03 07:26:12 +00:00
Créer un point d'api de suggestion des usagers pour un ticket
This commit is contained in:
parent
0566ab0910
commit
4f93150874
@ -18,7 +18,6 @@ use Symfony\Component\Form\Extension\Core\Type\FormType;
|
|||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
use function count;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command to get the report with curl:
|
* Command to get the report with curl:
|
||||||
|
@ -17,7 +17,6 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
|||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
use function count;
|
|
||||||
|
|
||||||
// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff
|
// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
|||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
use function count;
|
|
||||||
|
|
||||||
// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff
|
// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff
|
||||||
|
|
||||||
|
@ -18,12 +18,15 @@ use Doctrine\ORM\EntityRepository;
|
|||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
use libphonenumber\PhoneNumber;
|
||||||
|
use libphonenumber\PhoneNumberFormat;
|
||||||
|
use libphonenumber\PhoneNumberUtil;
|
||||||
|
|
||||||
class ThirdPartyRepository implements ObjectRepository
|
class ThirdPartyRepository implements ObjectRepository
|
||||||
{
|
{
|
||||||
private readonly EntityRepository $repository;
|
private readonly EntityRepository $repository;
|
||||||
|
|
||||||
public function __construct(EntityManagerInterface $em, private readonly Connection $connection)
|
public function __construct(EntityManagerInterface $em, private readonly Connection $connection, private readonly PhoneNumberUtil $phonenumberUtil)
|
||||||
{
|
{
|
||||||
$this->repository = $em->getRepository(ThirdParty::class);
|
$this->repository = $em->getRepository(ThirdParty::class);
|
||||||
}
|
}
|
||||||
@ -122,6 +125,43 @@ class ThirdPartyRepository implements ObjectRepository
|
|||||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds third-party records by phone number.
|
||||||
|
*
|
||||||
|
* The search is performed agains every phonenumber field (there are two phonenumber on a thirdParty).
|
||||||
|
*
|
||||||
|
* @param string|PhoneNumber $phonenumber The phone number to search for. Can be a string or a PhoneNumber object.
|
||||||
|
* @param int $firstResult the index of the first result to retrieve (pagination start)
|
||||||
|
* @param int $maxResults the maximum number of results to retrieve (pagination limit)
|
||||||
|
*
|
||||||
|
* @return list<ThirdParty> the result set containing matching third-party records
|
||||||
|
*/
|
||||||
|
public function findByPhonenumber(string|PhoneNumber $phonenumber, int $firstResult = 0, int $maxResults = 20): array
|
||||||
|
{
|
||||||
|
if ('' === $phonenumber) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$qb = $this->createQueryBuilder('tp');
|
||||||
|
$qb->select('tp');
|
||||||
|
|
||||||
|
$qb->where(
|
||||||
|
$qb->expr()->orX(
|
||||||
|
$qb->expr()->eq('t.telephone', ':phonenumber'),
|
||||||
|
$qb->expr()->eq('t.telephone2', ':phonenumber')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$qb->setParameter(
|
||||||
|
'phonenumber',
|
||||||
|
is_string($phonenumber) ? $phonenumber : $this->phonenumberUtil->format($phonenumber, PhoneNumberFormat::E164)
|
||||||
|
);
|
||||||
|
|
||||||
|
$qb->setFirstResult($firstResult)->setMaxResults($maxResults);
|
||||||
|
|
||||||
|
return $qb->getQuery()->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search amongst parties associated to $centers, with $terms parameters.
|
* Search amongst parties associated to $centers, with $terms parameters.
|
||||||
*
|
*
|
||||||
|
@ -164,8 +164,8 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/UserGroupById'
|
- $ref: '#/components/schemas/UserGroupById'
|
||||||
- $ref: '#/components/schemas/UserById'
|
- $ref: '#/components/schemas/UserById'
|
||||||
|
|
||||||
|
|
||||||
responses:
|
responses:
|
||||||
@ -197,8 +197,8 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
addressee:
|
addressee:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/UserGroupById'
|
- $ref: '#/components/schemas/UserGroupById'
|
||||||
- $ref: '#/components/schemas/UserById'
|
- $ref: '#/components/schemas/UserById'
|
||||||
|
|
||||||
|
|
||||||
responses:
|
responses:
|
||||||
@ -288,3 +288,82 @@ paths:
|
|||||||
description: "OK"
|
description: "OK"
|
||||||
401:
|
401:
|
||||||
description: "UNAUTHORIZED"
|
description: "UNAUTHORIZED"
|
||||||
|
|
||||||
|
/1.0/ticket/ticket/{id}/set-caller:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- ticket
|
||||||
|
summary: Set a caller for this ticket
|
||||||
|
description: |
|
||||||
|
Set a caller to the ticket.
|
||||||
|
|
||||||
|
To remove the caller, set the caller field to null
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The ticket id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: integer
|
||||||
|
minimum: 1
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
caller:
|
||||||
|
nullable: true
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/PersonById'
|
||||||
|
- $ref: '#/components/schemas/ThirdPartyById'
|
||||||
|
examples:
|
||||||
|
add_user:
|
||||||
|
value:
|
||||||
|
caller:
|
||||||
|
type: person
|
||||||
|
id: 8
|
||||||
|
summary: Set the person with id 8
|
||||||
|
add_third_party:
|
||||||
|
value:
|
||||||
|
caller:
|
||||||
|
type: thirdparty
|
||||||
|
id: 10
|
||||||
|
summary: Set the third party with id 10
|
||||||
|
remove:
|
||||||
|
value:
|
||||||
|
caller: null
|
||||||
|
summary: Remove the caller (set the caller to null)
|
||||||
|
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "OK"
|
||||||
|
401:
|
||||||
|
description: "UNAUTHORIZED"
|
||||||
|
/1.0/ticket/ticket/{id}/suggest-person:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- ticket
|
||||||
|
summary: Get a list of person suggested for the given ticket
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The ticket id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: integer
|
||||||
|
minimum: 1
|
||||||
|
responses:
|
||||||
|
401:
|
||||||
|
description: "UNAUTHORIZED"
|
||||||
|
200:
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Person'
|
||||||
|
@ -13,8 +13,9 @@ namespace Chill\TicketBundle\Action\Ticket\Handler;
|
|||||||
|
|
||||||
use Chill\MainBundle\Phonenumber\PhonenumberHelper;
|
use Chill\MainBundle\Phonenumber\PhonenumberHelper;
|
||||||
use Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface;
|
use Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface;
|
||||||
|
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
|
||||||
use Chill\TicketBundle\Action\Ticket\AssociateByPhonenumberCommand;
|
use Chill\TicketBundle\Action\Ticket\AssociateByPhonenumberCommand;
|
||||||
use Chill\TicketBundle\Entity\PersonHistory;
|
use Chill\TicketBundle\Entity\CallerHistory;
|
||||||
use Chill\TicketBundle\Entity\Ticket;
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Component\Clock\ClockInterface;
|
use Symfony\Component\Clock\ClockInterface;
|
||||||
@ -23,6 +24,7 @@ class AssociateByPhonenumberCommandHandler
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly PersonACLAwareRepositoryInterface $personRepository,
|
private readonly PersonACLAwareRepositoryInterface $personRepository,
|
||||||
|
private readonly ThirdPartyRepository $thirdPartyRepository,
|
||||||
private readonly PhonenumberHelper $phonenumberHelper,
|
private readonly PhonenumberHelper $phonenumberHelper,
|
||||||
private readonly ClockInterface $clock,
|
private readonly ClockInterface $clock,
|
||||||
private readonly EntityManagerInterface $entityManager,
|
private readonly EntityManagerInterface $entityManager,
|
||||||
@ -31,10 +33,13 @@ class AssociateByPhonenumberCommandHandler
|
|||||||
public function __invoke(Ticket $ticket, AssociateByPhonenumberCommand $command): void
|
public function __invoke(Ticket $ticket, AssociateByPhonenumberCommand $command): void
|
||||||
{
|
{
|
||||||
$phone = $this->phonenumberHelper->parse($command->phonenumber);
|
$phone = $this->phonenumberHelper->parse($command->phonenumber);
|
||||||
$persons = $this->personRepository->findByPhone($phone);
|
$callers = [
|
||||||
|
...$this->personRepository->findByPhone($phone),
|
||||||
|
...$this->thirdPartyRepository->findByPhonenumber($phone),
|
||||||
|
];
|
||||||
|
|
||||||
foreach ($persons as $person) {
|
if (count($callers) > 0) {
|
||||||
$history = new PersonHistory($person, $ticket, $this->clock->now());
|
$history = new CallerHistory($callers[0], $ticket, $this->clock->now());
|
||||||
$this->entityManager->persist($history);
|
$this->entityManager->persist($history);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
<?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\Action\Ticket\Handler;
|
||||||
|
|
||||||
|
use Chill\TicketBundle\Action\Ticket\SetCallerCommand;
|
||||||
|
use Chill\TicketBundle\Entity\CallerHistory;
|
||||||
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
|
use Symfony\Component\Clock\ClockInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for setting the caller of a ticket.
|
||||||
|
*/
|
||||||
|
class SetCallerCommandHandler
|
||||||
|
{
|
||||||
|
public function __construct(private readonly ClockInterface $clock) {}
|
||||||
|
|
||||||
|
public function __invoke(Ticket $ticket, SetCallerCommand $command): Ticket
|
||||||
|
{
|
||||||
|
// If the ticket already has the requested caller, return it without changes
|
||||||
|
$currentCaller = $ticket->getCaller();
|
||||||
|
if ($currentCaller === $command->caller) {
|
||||||
|
return $ticket;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End the current caller history (if any)
|
||||||
|
foreach ($ticket->getCallerHistories() as $callerHistory) {
|
||||||
|
if (null === $callerHistory->getEndDate()) {
|
||||||
|
$callerHistory->setEndDate($this->clock->now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new caller history with the new caller
|
||||||
|
new CallerHistory(
|
||||||
|
$command->caller,
|
||||||
|
$ticket,
|
||||||
|
$this->clock->now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $ticket;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<?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\Action\Ticket;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to set the caller of a ticket.
|
||||||
|
* The caller can be either a Person or a ThirdParty.
|
||||||
|
*/
|
||||||
|
final readonly class SetCallerCommand
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Person|ThirdParty|null $caller The caller to associate with the ticket
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public Person|ThirdParty|null $caller,
|
||||||
|
) {}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use Chill\TicketBundle\Action\Ticket\SetCallerCommand;
|
||||||
|
use Chill\TicketBundle\Action\Ticket\Handler\SetCallerCommandHandler;
|
||||||
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for setting the caller of a ticket.
|
||||||
|
*/
|
||||||
|
final readonly class SetCallerApiController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private SetCallerCommandHandler $setCallerCommandHandler,
|
||||||
|
private Security $security,
|
||||||
|
private EntityManagerInterface $entityManager,
|
||||||
|
private SerializerInterface $serializer,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/api/1.0/ticket/ticket/{id}/set-caller', name: 'chill_ticket_ticket_set_caller_api', requirements: ['id' => '\d+'], methods: ['POST'])]
|
||||||
|
public function setCaller(Ticket $ticket, Request $request): Response
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted('ROLE_USER')) {
|
||||||
|
throw new AccessDeniedHttpException('Only users are allowed to set ticket callers.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** @var SetCallerCommand $command */
|
||||||
|
$command = $this->serializer->deserialize(
|
||||||
|
$request->getContent(),
|
||||||
|
SetCallerCommand::class,
|
||||||
|
'json'
|
||||||
|
);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
throw new BadRequestHttpException('Invalid request body: '.$e->getMessage(), $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setCallerCommandHandler->__invoke($ticket, $command);
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new JsonResponse(
|
||||||
|
$this->serializer->serialize($ticket, 'json', ['groups' => ['read']]),
|
||||||
|
json: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
|
use Chill\TicketBundle\Service\Ticket\SuggestPersonForTicketInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
|
||||||
|
final readonly class SuggestPersonForTicketApiController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private SuggestPersonForTicketInterface $suggestPersonForTicket,
|
||||||
|
private SerializerInterface $serializer,
|
||||||
|
private Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/api/1.0/ticket/ticket/{id}/suggest-person', name: 'chill_ticket_ticket_suggest_person_api', requirements: ['id' => '\d+'], methods: ['GET'])]
|
||||||
|
public function __invoke(Ticket $ticket): Response
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted('ROLE_USER')) {
|
||||||
|
throw new AccessDeniedHttpException('Only users are allowed to suggest persons for tickets.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$persons = $this->suggestPersonForTicket->suggestPerson($ticket, 0, 10);
|
||||||
|
|
||||||
|
return new JsonResponse(
|
||||||
|
$this->serializer->serialize($persons, 'json', ['groups' => ['read']]),
|
||||||
|
json: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
148
src/Bundle/ChillTicketBundle/src/Entity/CallerHistory.php
Normal file
148
src/Bundle/ChillTicketBundle/src/Entity/CallerHistory.php
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
<?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\Entity;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the history of a caller associated with a ticket.
|
||||||
|
*
|
||||||
|
* This entity is used to track the changes in a ticket's caller over time.
|
||||||
|
* The caller can be either a Person or a ThirdParty.
|
||||||
|
* Implements the TrackCreationInterface for tracking entity lifecycle creation.
|
||||||
|
*/
|
||||||
|
#[ORM\Entity]
|
||||||
|
#[ORM\Table(name: 'caller_history', schema: 'chill_ticket')]
|
||||||
|
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['ticket_caller_history' => CallerHistory::class])]
|
||||||
|
class CallerHistory implements TrackCreationInterface
|
||||||
|
{
|
||||||
|
use TrackCreationTrait;
|
||||||
|
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: false)]
|
||||||
|
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true, options: ['default' => null])]
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
|
private ?\DateTimeImmutable $endDate = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(targetEntity: Person::class)]
|
||||||
|
#[ORM\JoinColumn(nullable: true)]
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
|
private ?Person $person = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(targetEntity: ThirdParty::class)]
|
||||||
|
#[ORM\JoinColumn(nullable: true)]
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
|
private ?ThirdParty $thirdParty = null;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ThirdParty|Person|null $caller,
|
||||||
|
#[ORM\ManyToOne(targetEntity: Ticket::class)]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private Ticket $ticket,
|
||||||
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: false)]
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
|
private \DateTimeImmutable $startDate = new \DateTimeImmutable('now'),
|
||||||
|
) {
|
||||||
|
$this->setCaller($caller);
|
||||||
|
$ticket->addCallerHistory($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEndDate(): ?\DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPerson(): ?Person
|
||||||
|
{
|
||||||
|
return $this->person;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getThirdParty(): ?ThirdParty
|
||||||
|
{
|
||||||
|
return $this->thirdParty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStartDate(): \DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTicket(): Ticket
|
||||||
|
{
|
||||||
|
return $this->ticket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEndDate(?\DateTimeImmutable $endDate): void
|
||||||
|
{
|
||||||
|
$this->endDate = $endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPerson(?Person $person): self
|
||||||
|
{
|
||||||
|
$this->person = $person;
|
||||||
|
|
||||||
|
// If setting a person, ensure thirdParty is null
|
||||||
|
if (null !== $person) {
|
||||||
|
$this->thirdParty = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setThirdParty(?ThirdParty $thirdParty): self
|
||||||
|
{
|
||||||
|
$this->thirdParty = $thirdParty;
|
||||||
|
|
||||||
|
// If setting a thirdParty, ensure person is null
|
||||||
|
if (null !== $thirdParty) {
|
||||||
|
$this->person = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the caller.
|
||||||
|
*
|
||||||
|
* This is a private method and should be only called while instance creation
|
||||||
|
*/
|
||||||
|
private function setCaller(Person|ThirdParty|null $caller): void
|
||||||
|
{
|
||||||
|
if ($caller instanceof Person) {
|
||||||
|
$this->setPerson($caller);
|
||||||
|
} elseif ($caller instanceof ThirdParty) {
|
||||||
|
$this->setThirdParty($caller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the caller, which can be either a Person or a ThirdParty.
|
||||||
|
*/
|
||||||
|
public function getCaller(): Person|ThirdParty|null
|
||||||
|
{
|
||||||
|
return $this->person ?? $this->thirdParty;
|
||||||
|
}
|
||||||
|
}
|
@ -96,6 +96,12 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
#[ORM\OneToMany(targetEntity: EmergencyStatusHistory::class, mappedBy: 'ticket', cascade: ['persist', 'remove'])]
|
#[ORM\OneToMany(targetEntity: EmergencyStatusHistory::class, mappedBy: 'ticket', cascade: ['persist', 'remove'])]
|
||||||
private Collection $emergencyStatusHistories;
|
private Collection $emergencyStatusHistories;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, CallerHistory>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: CallerHistory::class, mappedBy: 'ticket', cascade: ['persist', 'remove'])]
|
||||||
|
private Collection $callerHistories;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->addresseeHistory = new ArrayCollection();
|
$this->addresseeHistory = new ArrayCollection();
|
||||||
@ -105,6 +111,7 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
$this->inputHistories = new ArrayCollection();
|
$this->inputHistories = new ArrayCollection();
|
||||||
$this->stateHistories = new ArrayCollection();
|
$this->stateHistories = new ArrayCollection();
|
||||||
$this->emergencyStatusHistories = new ArrayCollection();
|
$this->emergencyStatusHistories = new ArrayCollection();
|
||||||
|
$this->callerHistories = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
@ -294,4 +301,36 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
{
|
{
|
||||||
return $this->emergencyStatusHistories;
|
return $this->emergencyStatusHistories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal use @see{CallerHistory::__construct} instead
|
||||||
|
*/
|
||||||
|
public function addCallerHistory(CallerHistory $callerHistory): void
|
||||||
|
{
|
||||||
|
$this->callerHistories->add($callerHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current caller (Person or ThirdParty) associated with this ticket.
|
||||||
|
*
|
||||||
|
* @return Person|ThirdParty|null
|
||||||
|
*/
|
||||||
|
public function getCaller()
|
||||||
|
{
|
||||||
|
foreach ($this->callerHistories as $callerHistory) {
|
||||||
|
if (null === $callerHistory->getEndDate()) {
|
||||||
|
return $callerHistory->getCaller();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ReadableCollection<int, CallerHistory>
|
||||||
|
*/
|
||||||
|
public function getCallerHistories(): ReadableCollection
|
||||||
|
{
|
||||||
|
return $this->callerHistories;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
<?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\Repository;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||||
|
|
||||||
|
final readonly class PersonTicketACLAwareRepository implements PersonTicketACLAwareRepositoryInterface
|
||||||
|
{
|
||||||
|
public function __construct(private EntityManagerInterface $em) {}
|
||||||
|
|
||||||
|
public function findPersonPreviouslyAssociatedWithCaller(Person|ThirdParty $caller, int $start = 0, int $limit = 100): array
|
||||||
|
{
|
||||||
|
$resultSetMappingBuilder = new ResultSetMappingBuilder($this->em);
|
||||||
|
$resultSetMappingBuilder->addRootEntityFromClassMetadata(Person::class, 'p');
|
||||||
|
|
||||||
|
$callerClause = match ($caller instanceof Person) {
|
||||||
|
true => 'caller_history.person_id = :callerId',
|
||||||
|
false => 'caller_history.thirdparty_id = :callerId',
|
||||||
|
};
|
||||||
|
|
||||||
|
$query = <<<SQL
|
||||||
|
SELECT DISTINCT {$resultSetMappingBuilder->generateSelectClause()} FROM chill_person_person p
|
||||||
|
WHERE p.id IN (
|
||||||
|
SELECT person_id
|
||||||
|
FROM chill_ticket.person_history person_history
|
||||||
|
WHERE person_history.endDate IS NULL
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1 FROM chill_ticket.caller_history
|
||||||
|
WHERE
|
||||||
|
caller_history.ticket_id = person_history.ticket_id
|
||||||
|
AND caller_history.endDate IS NULL
|
||||||
|
AND {$callerClause}
|
||||||
|
)
|
||||||
|
ORDER BY person_history.startDate DESC, p.id ASC
|
||||||
|
)
|
||||||
|
OFFSET :start LIMIT :limit;
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
$nql = $this->em->createNativeQuery($query, $resultSetMappingBuilder);
|
||||||
|
$nql
|
||||||
|
->setParameter('callerId', $caller->getId())
|
||||||
|
->setParameter('start', $start)
|
||||||
|
->setParameter('limit', $limit);
|
||||||
|
|
||||||
|
|
||||||
|
return $nql->getResult();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<?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\Repository;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for repository operations related to accessing and managing
|
||||||
|
* person tickets within an ACL-aware context.
|
||||||
|
*/
|
||||||
|
interface PersonTicketACLAwareRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Find all the Person entity that were previously associated with a ticket with the same caller.
|
||||||
|
*
|
||||||
|
* @return list<Person>
|
||||||
|
*/
|
||||||
|
public function findPersonPreviouslyAssociatedWithCaller(Person|ThirdParty $caller, int $start = 0, int $limit = 100): array;
|
||||||
|
}
|
@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\User;
|
|||||||
use Chill\MainBundle\Entity\UserGroup;
|
use Chill\MainBundle\Entity\UserGroup;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\TicketBundle\Entity\AddresseeHistory;
|
use Chill\TicketBundle\Entity\AddresseeHistory;
|
||||||
|
use Chill\TicketBundle\Entity\CallerHistory;
|
||||||
use Chill\TicketBundle\Entity\Comment;
|
use Chill\TicketBundle\Entity\Comment;
|
||||||
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
||||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||||
@ -53,6 +54,7 @@ final class TicketNormalizer implements NormalizerInterface, NormalizerAwareInte
|
|||||||
'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, $context),
|
'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, $context),
|
||||||
'currentState' => $object->getState()?->value ?? 'open',
|
'currentState' => $object->getState()?->value ?? 'open',
|
||||||
'emergency' => $object->getEmergencyStatus()?->value ?? 'no',
|
'emergency' => $object->getEmergencyStatus()?->value ?? 'no',
|
||||||
|
'caller' => $this->normalizer->normalize($object->getCaller(), $format, ['groups' => 'read']),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +117,17 @@ final class TicketNormalizer implements NormalizerInterface, NormalizerAwareInte
|
|||||||
],
|
],
|
||||||
$ticket->getEmergencyStatusHistories()->toArray(),
|
$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()) {
|
if (null !== $ticket->getCreatedBy() && null !== $ticket->getCreatedAt()) {
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
<?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\Service\Ticket;
|
||||||
|
|
||||||
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
|
use Chill\TicketBundle\Repository\PersonTicketACLAwareRepositoryInterface;
|
||||||
|
|
||||||
|
final readonly class SuggestPersonForTicket implements SuggestPersonForTicketInterface
|
||||||
|
{
|
||||||
|
public function __construct(private PersonTicketACLAwareRepositoryInterface $personTicketACLAwareRepository) {}
|
||||||
|
|
||||||
|
public function suggestPerson(Ticket $ticket, int $start = 0, int $limit = 20): array
|
||||||
|
{
|
||||||
|
$caller = $ticket->getCaller();
|
||||||
|
if (null === $caller) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->personTicketACLAwareRepository->findPersonPreviouslyAssociatedWithCaller($caller, $start, $limit);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
<?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\Service\Ticket;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suggest Person entities for a ticket.
|
||||||
|
*/
|
||||||
|
interface SuggestPersonForTicketInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return list<Person>
|
||||||
|
*/
|
||||||
|
public function suggestPerson(Ticket $ticket, int $start = 0, int $limit = 20): array;
|
||||||
|
}
|
@ -17,6 +17,9 @@ services:
|
|||||||
Chill\TicketBundle\Serializer\:
|
Chill\TicketBundle\Serializer\:
|
||||||
resource: '../Serializer/'
|
resource: '../Serializer/'
|
||||||
|
|
||||||
|
Chill\TicketBundle\Service\:
|
||||||
|
resource: '../Service/'
|
||||||
|
|
||||||
Chill\TicketBundle\Menu\:
|
Chill\TicketBundle\Menu\:
|
||||||
resource: '../Menu/'
|
resource: '../Menu/'
|
||||||
|
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
<?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\Migrations\Ticket;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20250624105842 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add CallerHistory entity to associate a ticket with either a Person or a ThirdParty entity';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE SEQUENCE chill_ticket.caller_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE chill_ticket.caller_history (id INT NOT NULL, person_id INT DEFAULT NULL, ticket_id INT NOT NULL, endDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, startDate TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, thirdParty_id INT DEFAULT NULL, createdBy_id INT DEFAULT NULL, PRIMARY KEY(id))
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE INDEX IDX_AD0DCE24217BBB47 ON chill_ticket.caller_history (person_id)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE INDEX IDX_AD0DCE243EA5CAB0 ON chill_ticket.caller_history (thirdParty_id)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE INDEX IDX_AD0DCE24700047D2 ON chill_ticket.caller_history (ticket_id)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE INDEX IDX_AD0DCE243174800F ON chill_ticket.caller_history (createdBy_id)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
COMMENT ON COLUMN chill_ticket.caller_history.endDate IS '(DC2Type:datetime_immutable)'
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
COMMENT ON COLUMN chill_ticket.caller_history.startDate IS '(DC2Type:datetime_immutable)'
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
COMMENT ON COLUMN chill_ticket.caller_history.createdAt IS '(DC2Type:datetime_immutable)'
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history ADD CONSTRAINT FK_AD0DCE24217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history ADD CONSTRAINT FK_AD0DCE243EA5CAB0 FOREIGN KEY (thirdParty_id) REFERENCES chill_3party.third_party (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history ADD CONSTRAINT FK_AD0DCE24700047D2 FOREIGN KEY (ticket_id) REFERENCES chill_ticket.ticket (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history ADD CONSTRAINT FK_AD0DCE243174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history ADD CONSTRAINT caller_history_not_overlaps
|
||||||
|
exclude using gist (ticket_id with =, tsrange(startdate, enddate) with &&)
|
||||||
|
deferrable initially deferred
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP SEQUENCE chill_ticket.caller_history_id_seq CASCADE
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history DROP CONSTRAINT FK_AD0DCE24217BBB47
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history DROP CONSTRAINT FK_AD0DCE243EA5CAB0
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history DROP CONSTRAINT FK_AD0DCE24700047D2
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history DROP CONSTRAINT FK_AD0DCE243174800F
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE chill_ticket.caller_history
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
}
|
@ -14,10 +14,13 @@ namespace Chill\TicketBundle\Tests\Action\Ticket\Handler;
|
|||||||
use Chill\MainBundle\Phonenumber\PhonenumberHelper;
|
use Chill\MainBundle\Phonenumber\PhonenumberHelper;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface;
|
use Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
|
||||||
use Chill\TicketBundle\Action\Ticket\AssociateByPhonenumberCommand;
|
use Chill\TicketBundle\Action\Ticket\AssociateByPhonenumberCommand;
|
||||||
use Chill\TicketBundle\Action\Ticket\Handler\AssociateByPhonenumberCommandHandler;
|
use Chill\TicketBundle\Action\Ticket\Handler\AssociateByPhonenumberCommandHandler;
|
||||||
use Chill\TicketBundle\Entity\Ticket;
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use libphonenumber\PhoneNumber;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
use Prophecy\PhpUnit\ProphecyTrait;
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
@ -37,6 +40,7 @@ class AssociateByPhonenumberCommandHandlerTest extends TestCase
|
|||||||
|
|
||||||
private function getHandler(
|
private function getHandler(
|
||||||
PersonACLAwareRepositoryInterface $personACLAwareRepository,
|
PersonACLAwareRepositoryInterface $personACLAwareRepository,
|
||||||
|
ThirdPartyRepository $thirdPartyRepository,
|
||||||
): AssociateByPhonenumberCommandHandler {
|
): AssociateByPhonenumberCommandHandler {
|
||||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||||
$phonenumberHelper = new PhonenumberHelper(
|
$phonenumberHelper = new PhonenumberHelper(
|
||||||
@ -51,6 +55,7 @@ class AssociateByPhonenumberCommandHandlerTest extends TestCase
|
|||||||
|
|
||||||
return new AssociateByPhonenumberCommandHandler(
|
return new AssociateByPhonenumberCommandHandler(
|
||||||
$personACLAwareRepository,
|
$personACLAwareRepository,
|
||||||
|
$thirdPartyRepository,
|
||||||
$phonenumberHelper,
|
$phonenumberHelper,
|
||||||
new MockClock(),
|
new MockClock(),
|
||||||
$entityManager->reveal()
|
$entityManager->reveal()
|
||||||
@ -63,12 +68,29 @@ class AssociateByPhonenumberCommandHandlerTest extends TestCase
|
|||||||
|
|
||||||
$personAclAwareRepository = $this->prophesize(PersonACLAwareRepositoryInterface::class);
|
$personAclAwareRepository = $this->prophesize(PersonACLAwareRepositoryInterface::class);
|
||||||
$personAclAwareRepository->findByPhone(Argument::any())->willReturn([$person]);
|
$personAclAwareRepository->findByPhone(Argument::any())->willReturn([$person]);
|
||||||
|
$thirdPartyRepository = $this->prophesize(ThirdPartyRepository::class);
|
||||||
|
$thirdPartyRepository->findByPhonenumber(Argument::type(PhoneNumber::class))->willReturn([]);
|
||||||
|
|
||||||
$handler = $this->getHandler($personAclAwareRepository->reveal());
|
$handler = $this->getHandler($personAclAwareRepository->reveal(), $thirdPartyRepository->reveal());
|
||||||
|
|
||||||
$ticket = new Ticket();
|
$ticket = new Ticket();
|
||||||
$handler($ticket, new AssociateByPhonenumberCommand('+3281136917'));
|
$handler($ticket, new AssociateByPhonenumberCommand('+3281136917'));
|
||||||
|
|
||||||
self::assertSame($person, $ticket->getPersons()[0]);
|
self::assertSame($person, $ticket->getCaller());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHandleWithThirdPartyFoundByPhonenumber(): void
|
||||||
|
{
|
||||||
|
$personAclAwareRepository = $this->prophesize(PersonACLAwareRepositoryInterface::class);
|
||||||
|
$personAclAwareRepository->findByPhone(Argument::any())->willReturn([]);
|
||||||
|
$thirdPartyRepository = $this->prophesize(ThirdPartyRepository::class);
|
||||||
|
$thirdPartyRepository->findByPhonenumber(Argument::type(PhoneNumber::class))->willReturn([$thirdParty = new ThirdParty()]);
|
||||||
|
|
||||||
|
$handler = $this->getHandler($personAclAwareRepository->reveal(), $thirdPartyRepository->reveal());
|
||||||
|
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$handler($ticket, new AssociateByPhonenumberCommand('081136917'));
|
||||||
|
|
||||||
|
self::assertSame($thirdParty, $ticket->getCaller());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,178 @@
|
|||||||
|
<?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\Tests\Action\Ticket\Handler;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\TicketBundle\Action\Ticket\Handler\SetCallerCommandHandler;
|
||||||
|
use Chill\TicketBundle\Action\Ticket\SetCallerCommand;
|
||||||
|
use Chill\TicketBundle\Entity\CallerHistory;
|
||||||
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Symfony\Component\Clock\ClockInterface;
|
||||||
|
use Symfony\Component\Clock\MockClock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class SetCallerCommandHandlerTest extends TestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
private \DateTimeImmutable $now;
|
||||||
|
private ClockInterface $clock;
|
||||||
|
private SetCallerCommandHandler $handler;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->now = new \DateTimeImmutable('2023-01-01 12:00:00');
|
||||||
|
$this->clock = new MockClock($this->now);
|
||||||
|
$this->handler = new SetCallerCommandHandler($this->clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetPersonAsCaller(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$person = new Person();
|
||||||
|
$command = new SetCallerCommand($person);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$result = ($this->handler)($ticket, $command);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
self::assertSame($ticket, $result);
|
||||||
|
self::assertSame($person, $ticket->getCaller());
|
||||||
|
self::assertCount(1, $ticket->getCallerHistories());
|
||||||
|
|
||||||
|
$callerHistory = $ticket->getCallerHistories()->first();
|
||||||
|
self::assertInstanceOf(CallerHistory::class, $callerHistory);
|
||||||
|
self::assertSame($person, $callerHistory->getPerson());
|
||||||
|
self::assertNull($callerHistory->getThirdParty());
|
||||||
|
self::assertEquals($this->now->getTimestamp(), $callerHistory->getStartDate()->getTimestamp());
|
||||||
|
self::assertNull($callerHistory->getEndDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetThirdPartyAsCaller(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$thirdParty = new ThirdParty();
|
||||||
|
$command = new SetCallerCommand($thirdParty);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$result = ($this->handler)($ticket, $command);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
self::assertSame($ticket, $result);
|
||||||
|
self::assertSame($thirdParty, $ticket->getCaller());
|
||||||
|
self::assertCount(1, $ticket->getCallerHistories());
|
||||||
|
|
||||||
|
$callerHistory = $ticket->getCallerHistories()->first();
|
||||||
|
self::assertInstanceOf(CallerHistory::class, $callerHistory);
|
||||||
|
self::assertNull($callerHistory->getPerson());
|
||||||
|
self::assertSame($thirdParty, $callerHistory->getThirdParty());
|
||||||
|
self::assertEquals($this->now->getTimestamp(), $callerHistory->getStartDate()->getTimestamp());
|
||||||
|
self::assertNull($callerHistory->getEndDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testChangeCallerFromPersonToThirdParty(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$person = new Person();
|
||||||
|
$thirdParty = new ThirdParty();
|
||||||
|
|
||||||
|
// Set initial person caller
|
||||||
|
$initialCallerHistory = new CallerHistory($person, $ticket, $this->now);
|
||||||
|
$initialCallerHistory->setPerson($person);
|
||||||
|
|
||||||
|
// Create command to change to third party
|
||||||
|
$command = new SetCallerCommand($thirdParty);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$this->clock->modify('+ 10 minutes');
|
||||||
|
$result = ($this->handler)($ticket, $command);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
self::assertSame($ticket, $result);
|
||||||
|
self::assertSame($thirdParty, $ticket->getCaller());
|
||||||
|
self::assertCount(2, $ticket->getCallerHistories());
|
||||||
|
|
||||||
|
// Check that the first history is ended
|
||||||
|
$firstCallerHistory = $ticket->getCallerHistories()->first();
|
||||||
|
self::assertSame($person, $firstCallerHistory->getPerson());
|
||||||
|
self::assertEquals($this->clock->now()->getTimestamp(), $firstCallerHistory->getEndDate()->getTimestamp());
|
||||||
|
|
||||||
|
// Check that the new history is created correctly
|
||||||
|
$lastCallerHistory = $ticket->getCallerHistories()->last();
|
||||||
|
self::assertNull($lastCallerHistory->getPerson());
|
||||||
|
self::assertSame($thirdParty, $lastCallerHistory->getThirdParty());
|
||||||
|
self::assertSame($this->clock->now()->getTimestamp(), $lastCallerHistory->getStartDate()->getTimestamp());
|
||||||
|
self::assertNull($lastCallerHistory->getEndDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRemoveCaller(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$person = new Person();
|
||||||
|
|
||||||
|
// Set initial person caller
|
||||||
|
$initialCallerHistory = new CallerHistory($person, $ticket, $this->now);
|
||||||
|
|
||||||
|
// Create command to remove caller
|
||||||
|
$command = new SetCallerCommand(null);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$result = ($this->handler)($ticket, $command);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
self::assertSame($ticket, $result);
|
||||||
|
self::assertNull($ticket->getCaller());
|
||||||
|
self::assertCount(2, $ticket->getCallerHistories());
|
||||||
|
|
||||||
|
// Check that the history is ended
|
||||||
|
$callerHistory = $ticket->getCallerHistories()->first();
|
||||||
|
self::assertSame($person, $callerHistory->getPerson());
|
||||||
|
self::assertEquals($this->now->getTimestamp(), $callerHistory->getEndDate()->getTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNoChangeWhenCallerIsAlreadySet(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$person = new Person();
|
||||||
|
|
||||||
|
// Set initial person caller
|
||||||
|
$initialCallerHistory = new CallerHistory($person, $ticket, $this->now);
|
||||||
|
|
||||||
|
// Create command with the same person
|
||||||
|
$command = new SetCallerCommand($person);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$result = ($this->handler)($ticket, $command);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
self::assertSame($ticket, $result);
|
||||||
|
self::assertSame($person, $ticket->getCaller());
|
||||||
|
self::assertCount(1, $ticket->getCallerHistories());
|
||||||
|
|
||||||
|
// Check that the history is unchanged
|
||||||
|
$callerHistory = $ticket->getCallerHistories()->first();
|
||||||
|
self::assertSame($person, $callerHistory->getPerson());
|
||||||
|
self::assertNull($callerHistory->getEndDate());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,200 @@
|
|||||||
|
<?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\Tests\Controller;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\TicketBundle\Action\Ticket\Handler\SetCallerCommandHandler;
|
||||||
|
use Chill\TicketBundle\Action\Ticket\SetCallerCommand;
|
||||||
|
use Chill\TicketBundle\Controller\SetCallerApiController;
|
||||||
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class SetCallerApiControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
public function testSetCallerWithoutPermission(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$request = new Request();
|
||||||
|
|
||||||
|
$security = $this->prophesize(Security::class);
|
||||||
|
$security->isGranted('ROLE_USER')->willReturn(false);
|
||||||
|
|
||||||
|
$setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class);
|
||||||
|
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$serializer = $this->prophesize(SerializerInterface::class);
|
||||||
|
|
||||||
|
$controller = new SetCallerApiController(
|
||||||
|
$setCallerCommandHandler->reveal(),
|
||||||
|
$security->reveal(),
|
||||||
|
$entityManager->reveal(),
|
||||||
|
$serializer->reveal(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(AccessDeniedHttpException::class);
|
||||||
|
$controller->setCaller($ticket, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetCallerWithInvalidRequestBody(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$request = new Request([], [], [], [], [], [], 'invalid json');
|
||||||
|
|
||||||
|
$security = $this->prophesize(Security::class);
|
||||||
|
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||||
|
|
||||||
|
$setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class);
|
||||||
|
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
$serializer = $this->prophesize(SerializerInterface::class);
|
||||||
|
$serializer->deserialize('invalid json', SetCallerCommand::class, 'json')
|
||||||
|
->willThrow(new \Exception('Invalid JSON'));
|
||||||
|
|
||||||
|
$controller = new SetCallerApiController(
|
||||||
|
$setCallerCommandHandler->reveal(),
|
||||||
|
$security->reveal(),
|
||||||
|
$entityManager->reveal(),
|
||||||
|
$serializer->reveal(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(BadRequestHttpException::class);
|
||||||
|
$controller->setCaller($ticket, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetCallerWithValidRequest(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$request = new Request([], [], [], [], [], [], '{"caller": {"id": 123, "type": "person"}}');
|
||||||
|
|
||||||
|
$person = new Person();
|
||||||
|
$command = new SetCallerCommand($person);
|
||||||
|
|
||||||
|
$security = $this->prophesize(Security::class);
|
||||||
|
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||||
|
|
||||||
|
$serializer = $this->prophesize(SerializerInterface::class);
|
||||||
|
$serializer->deserialize('{"caller": {"id": 123, "type": "person"}}', SetCallerCommand::class, 'json')
|
||||||
|
->willReturn($command);
|
||||||
|
$serializer->serialize($ticket, 'json', ['groups' => ['read']])
|
||||||
|
->willReturn('{}')
|
||||||
|
->shouldBeCalled();
|
||||||
|
|
||||||
|
$setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class);
|
||||||
|
$setCallerCommandHandler->__invoke($ticket, $command)
|
||||||
|
->willReturn($ticket)
|
||||||
|
->shouldBeCalled();
|
||||||
|
|
||||||
|
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$entityManager->flush()->shouldBeCalled();
|
||||||
|
|
||||||
|
$controller = new SetCallerApiController(
|
||||||
|
$setCallerCommandHandler->reveal(),
|
||||||
|
$security->reveal(),
|
||||||
|
$entityManager->reveal(),
|
||||||
|
$serializer->reveal(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = $controller->setCaller($ticket, $request);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetCallerWithThirdParty(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$request = new Request([], [], [], [], [], [], '{"caller": {"id": 456, "type": "thirdParty"}}');
|
||||||
|
|
||||||
|
$thirdParty = $this->prophesize(ThirdParty::class)->reveal();
|
||||||
|
$command = new SetCallerCommand($thirdParty);
|
||||||
|
|
||||||
|
$security = $this->prophesize(Security::class);
|
||||||
|
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||||
|
|
||||||
|
$serializer = $this->prophesize(SerializerInterface::class);
|
||||||
|
$serializer->deserialize('{"caller": {"id": 456, "type": "thirdParty"}}', SetCallerCommand::class, 'json')
|
||||||
|
->willReturn($command);
|
||||||
|
$serializer->serialize($ticket, 'json', ['groups' => ['read']])
|
||||||
|
->willReturn('{}')
|
||||||
|
->shouldBeCalled();
|
||||||
|
|
||||||
|
$setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class);
|
||||||
|
$setCallerCommandHandler->__invoke($ticket, $command)
|
||||||
|
->willReturn($ticket)
|
||||||
|
->shouldBeCalled();
|
||||||
|
|
||||||
|
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$entityManager->flush()->shouldBeCalled();
|
||||||
|
|
||||||
|
$controller = new SetCallerApiController(
|
||||||
|
$setCallerCommandHandler->reveal(),
|
||||||
|
$security->reveal(),
|
||||||
|
$entityManager->reveal(),
|
||||||
|
$serializer->reveal(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = $controller->setCaller($ticket, $request);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetCallerToNull(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$request = new Request([], [], [], [], [], [], '{"caller": null}');
|
||||||
|
|
||||||
|
$command = new SetCallerCommand(null);
|
||||||
|
|
||||||
|
$security = $this->prophesize(Security::class);
|
||||||
|
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||||
|
|
||||||
|
$serializer = $this->prophesize(SerializerInterface::class);
|
||||||
|
$serializer->deserialize('{"caller": null}', SetCallerCommand::class, 'json')
|
||||||
|
->willReturn($command);
|
||||||
|
$serializer->serialize($ticket, 'json', ['groups' => ['read']])
|
||||||
|
->willReturn('{}')
|
||||||
|
->shouldBeCalled();
|
||||||
|
|
||||||
|
$setCallerCommandHandler = $this->prophesize(SetCallerCommandHandler::class);
|
||||||
|
$setCallerCommandHandler->__invoke($ticket, $command)
|
||||||
|
->willReturn($ticket)
|
||||||
|
->shouldBeCalled();
|
||||||
|
|
||||||
|
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$entityManager->flush()->shouldBeCalled();
|
||||||
|
|
||||||
|
$controller = new SetCallerApiController(
|
||||||
|
$setCallerCommandHandler->reveal(),
|
||||||
|
$security->reveal(),
|
||||||
|
$entityManager->reveal(),
|
||||||
|
$serializer->reveal(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = $controller->setCaller($ticket, $request);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
<?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\Tests\Entity;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\TicketBundle\Entity\CallerHistory;
|
||||||
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class CallerHistoryTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testConstructorWithPerson(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
|
||||||
|
$callerHistory = new CallerHistory($person = new Person(), $ticket);
|
||||||
|
|
||||||
|
self::assertSame($ticket, $callerHistory->getTicket());
|
||||||
|
self::assertNull($callerHistory->getEndDate());
|
||||||
|
self::assertSame($person, $callerHistory->getPerson());
|
||||||
|
self::assertNull($callerHistory->getThirdParty());
|
||||||
|
self::assertSame($person, $callerHistory->getCaller());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConstructorWithThirdParty(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
|
||||||
|
$callerHistory = new CallerHistory($thirdParty = new ThirdParty(), $ticket);
|
||||||
|
|
||||||
|
self::assertSame($ticket, $callerHistory->getTicket());
|
||||||
|
self::assertNull($callerHistory->getEndDate());
|
||||||
|
self::assertNull($callerHistory->getPerson());
|
||||||
|
self::assertSame($thirdParty, $callerHistory->getThirdParty());
|
||||||
|
self::assertSame($thirdParty, $callerHistory->getCaller());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetEndDate(): void
|
||||||
|
{
|
||||||
|
$ticket = $this->createMock(Ticket::class);
|
||||||
|
$callerHistory = new CallerHistory(new ThirdParty(), $ticket);
|
||||||
|
|
||||||
|
$endDate = new \DateTimeImmutable('2023-01-01');
|
||||||
|
$callerHistory->setEndDate($endDate);
|
||||||
|
|
||||||
|
self::assertSame($endDate, $callerHistory->getEndDate());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
<?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\Tests\Entity;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\TicketBundle\Entity\CallerHistory;
|
||||||
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class TicketCallerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetCaller(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
|
||||||
|
// Initially, there should be no caller
|
||||||
|
self::assertNull($ticket->getCaller());
|
||||||
|
|
||||||
|
// Create a person
|
||||||
|
$person = new Person();
|
||||||
|
|
||||||
|
// Create a caller history with the person
|
||||||
|
$callerHistory = new CallerHistory($person, $ticket);
|
||||||
|
|
||||||
|
// The ticket should now return the person as the caller
|
||||||
|
self::assertSame($person, $ticket->getCaller());
|
||||||
|
|
||||||
|
// Create a third party
|
||||||
|
$thirdParty = new ThirdParty();
|
||||||
|
|
||||||
|
// Create a new caller history with the third party
|
||||||
|
$callerHistory2 = new CallerHistory($thirdParty, $ticket);
|
||||||
|
|
||||||
|
// End the first caller history
|
||||||
|
$callerHistory->setEndDate(new \DateTimeImmutable());
|
||||||
|
|
||||||
|
// The ticket should now return the third party as the caller
|
||||||
|
self::assertSame($thirdParty, $ticket->getCaller());
|
||||||
|
|
||||||
|
// End the second caller history
|
||||||
|
$callerHistory2->setEndDate(new \DateTimeImmutable());
|
||||||
|
|
||||||
|
// The ticket should now return null as there is no active caller
|
||||||
|
self::assertNull($ticket->getCaller());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetCallerHistories(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
|
||||||
|
// Initially, there should be no caller histories
|
||||||
|
self::assertCount(0, $ticket->getCallerHistories());
|
||||||
|
|
||||||
|
// Create a caller history
|
||||||
|
$callerHistory = new CallerHistory(new Person(), $ticket);
|
||||||
|
|
||||||
|
// The ticket should now have one caller history
|
||||||
|
self::assertCount(1, $ticket->getCallerHistories());
|
||||||
|
self::assertSame($callerHistory, $ticket->getCallerHistories()->first());
|
||||||
|
|
||||||
|
// Create another caller history
|
||||||
|
$callerHistory2 = new CallerHistory(new ThirdParty(), $ticket);
|
||||||
|
|
||||||
|
// The ticket should now have two caller histories
|
||||||
|
self::assertCount(2, $ticket->getCallerHistories());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
<?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 Repository;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\DataFixtures\Helper\RandomPersonHelperTrait;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\TicketBundle\Repository\PersonTicketACLAwareRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class PersonTicketACLAwareRepositoryTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
use RandomPersonHelperTrait;
|
||||||
|
|
||||||
|
private PersonTicketACLAwareRepository $repository;
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->repository = self::getContainer()->get(PersonTicketACLAwareRepository::class);
|
||||||
|
$this->entityManager = self::getContainer()->get(EntityManagerInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFindPersonPreviouslyAssociatedWithCallerWithPerson()
|
||||||
|
{
|
||||||
|
$person = $this->getRandomPerson(self::getContainer()->get(EntityManagerInterface::class));
|
||||||
|
|
||||||
|
$actual = $this->repository->findPersonPreviouslyAssociatedWithCaller($person);
|
||||||
|
|
||||||
|
self::assertIsList($actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFindPersonPreviouslyAssociatedWithCallerWithThirdParty()
|
||||||
|
{
|
||||||
|
$thirdParty = $this->entityManager->createQuery(
|
||||||
|
sprintf('SELECT t FROM %s t', ThirdParty::class)
|
||||||
|
)
|
||||||
|
->setMaxResults(1)
|
||||||
|
->getSingleResult();
|
||||||
|
|
||||||
|
if (null === $thirdParty) {
|
||||||
|
throw new \RuntimeException('the third party table seems to be empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
$actual = $this->repository->findPersonPreviouslyAssociatedWithCaller($thirdParty);
|
||||||
|
|
||||||
|
self::assertIsList($actual);
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\User;
|
|||||||
use Chill\MainBundle\Entity\UserGroup;
|
use Chill\MainBundle\Entity\UserGroup;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\TicketBundle\Entity\AddresseeHistory;
|
use Chill\TicketBundle\Entity\AddresseeHistory;
|
||||||
|
use Chill\TicketBundle\Entity\CallerHistory;
|
||||||
use Chill\TicketBundle\Entity\Comment;
|
use Chill\TicketBundle\Entity\Comment;
|
||||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||||
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
||||||
@ -91,27 +92,16 @@ class TicketNormalizerTest extends KernelTestCase
|
|||||||
'history' => [
|
'history' => [
|
||||||
[
|
[
|
||||||
'event_type' => 'create_ticket',
|
'event_type' => 'create_ticket',
|
||||||
'at' => 1718495999,
|
|
||||||
'by' => [
|
|
||||||
0 => 'user',
|
|
||||||
],
|
|
||||||
'data' => [],
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'event_type' => 'state_change',
|
'event_type' => 'state_change',
|
||||||
'at' => 1718495999,
|
|
||||||
'by' => [
|
|
||||||
0 => 'user',
|
|
||||||
],
|
|
||||||
'data' => [
|
|
||||||
'new_state' => 'open',
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'currentState' => 'open',
|
'currentState' => 'open',
|
||||||
'updatedAt' => $t->getUpdatedAt()->getTimestamp(),
|
'updatedAt' => $t->getUpdatedAt()->getTimestamp(),
|
||||||
'updatedBy' => ['user'],
|
'updatedBy' => ['user'],
|
||||||
'emergency' => 'no',
|
'emergency' => 'no',
|
||||||
|
'caller' => null,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -165,6 +155,36 @@ class TicketNormalizerTest extends KernelTestCase
|
|||||||
'updatedAt' => $ticket->getUpdatedAt()->getTimestamp(),
|
'updatedAt' => $ticket->getUpdatedAt()->getTimestamp(),
|
||||||
'updatedBy' => ['user'],
|
'updatedBy' => ['user'],
|
||||||
'emergency' => 'yes',
|
'emergency' => 'yes',
|
||||||
|
'caller' => null,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// ticket with caller
|
||||||
|
$ticket = new Ticket();
|
||||||
|
$ticket->setCreatedAt(new \DateTimeImmutable('2024-06-16T00:00:00'));
|
||||||
|
$ticket->setUpdatedAt(new \DateTimeImmutable('2024-06-16T00:00:00'));
|
||||||
|
new CallerHistory(new Person(), $ticket, new \DateTimeImmutable('2024-04-01T12:00:00'));
|
||||||
|
|
||||||
|
yield [
|
||||||
|
$ticket,
|
||||||
|
[
|
||||||
|
'type' => 'ticket_ticket',
|
||||||
|
'createdAt' => $ticket->getCreatedAt()?->getTimestamp(),
|
||||||
|
'createdBy' => null,
|
||||||
|
'id' => null,
|
||||||
|
'externalRef' => '',
|
||||||
|
'currentPersons' => [],
|
||||||
|
'currentAddressees' => [],
|
||||||
|
'currentInputs' => [],
|
||||||
|
'currentMotive' => null,
|
||||||
|
'history' => [
|
||||||
|
['event_type' => 'set_caller'],
|
||||||
|
],
|
||||||
|
'currentState' => 'open',
|
||||||
|
'updatedAt' => $ticket->getUpdatedAt()->getTimestamp(),
|
||||||
|
'updatedBy' => null,
|
||||||
|
'emergency' => 'no',
|
||||||
|
'caller' => ['person'],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -224,6 +244,11 @@ class TicketNormalizerTest extends KernelTestCase
|
|||||||
'json',
|
'json',
|
||||||
['groups' => 'read']
|
['groups' => 'read']
|
||||||
)->will(fn ($args): array => $args[0]);
|
)->will(fn ($args): array => $args[0]);
|
||||||
|
$normalizer->normalize(
|
||||||
|
Argument::that(fn ($arg) => is_array($arg) && 1 === count($arg) && array_key_exists('new_caller', $arg)),
|
||||||
|
'json',
|
||||||
|
['groups' => 'read']
|
||||||
|
)->will(fn ($args): array => ['new_caller' => ['dummy']]);
|
||||||
|
|
||||||
// datetime
|
// datetime
|
||||||
$normalizer->normalize(Argument::type(\DateTimeImmutable::class), 'json', Argument::type('array'))
|
$normalizer->normalize(Argument::type(\DateTimeImmutable::class), 'json', Argument::type('array'))
|
||||||
@ -231,6 +256,9 @@ class TicketNormalizerTest extends KernelTestCase
|
|||||||
// user
|
// user
|
||||||
$normalizer->normalize(Argument::type(User::class), 'json', Argument::type('array'))
|
$normalizer->normalize(Argument::type(User::class), 'json', Argument::type('array'))
|
||||||
->willReturn(['user']);
|
->willReturn(['user']);
|
||||||
|
// person
|
||||||
|
$normalizer->normalize(Argument::type(Person::class), 'json', Argument::type('array'))
|
||||||
|
->willReturn(['person']);
|
||||||
// motive
|
// motive
|
||||||
$normalizer->normalize(Argument::type(Motive::class), 'json', Argument::type('array'))->willReturn(['type' => 'motive', 'id' => 0]);
|
$normalizer->normalize(Argument::type(Motive::class), 'json', Argument::type('array'))->willReturn(['type' => 'motive', 'id' => 0]);
|
||||||
// person history
|
// person history
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
<?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 Service\Ticket;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\TicketBundle\Entity\CallerHistory;
|
||||||
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
|
use Chill\TicketBundle\Repository\PersonTicketACLAwareRepositoryInterface;
|
||||||
|
use Chill\TicketBundle\Service\Ticket\SuggestPersonForTicket;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Argument;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class SuggestPersonForTicketTest extends TestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
private \Prophecy\Prophecy\ObjectProphecy $repository;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->repository = $this->prophesize(PersonTicketACLAwareRepositoryInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildSuggestPersonForTicket(): SuggestPersonForTicket
|
||||||
|
{
|
||||||
|
return new SuggestPersonForTicket($this->repository->reveal());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSuggestPersonNoCaller()
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
|
||||||
|
assert(null === $ticket->getCaller());
|
||||||
|
|
||||||
|
$this->repository->findPersonPreviouslyAssociatedWithCaller(Argument::any())->shouldNotBeCalled();
|
||||||
|
$suggester = $this->buildSuggestPersonForTicket();
|
||||||
|
|
||||||
|
self::assertEquals([], $suggester->suggestPerson($ticket));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSuggestPersonCaller()
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
new CallerHistory($person = new Person(), $ticket);
|
||||||
|
|
||||||
|
assert($person === $ticket->getCaller());
|
||||||
|
|
||||||
|
$this->repository->findPersonPreviouslyAssociatedWithCaller($person, 0, 20)
|
||||||
|
->willReturn($result = [new Person(), new Person()])
|
||||||
|
->shouldBeCalledOnce();
|
||||||
|
$suggester = $this->buildSuggestPersonForTicket();
|
||||||
|
|
||||||
|
self::assertEquals($result, $suggester->suggestPerson($ticket, 0, 20));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user