mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-14 14:24:24 +00:00
Merge branch 'ticket-app/create-entities' into 'ticket-app-master'
Add phone number search function to PersonACLAwareRepository See merge request Chill-Projet/chill-bundles!677
This commit is contained in:
commit
3f789ad0f4
@ -55,7 +55,7 @@ Arborescence:
|
||||
- person
|
||||
- personvendee
|
||||
- household_edit_metadata
|
||||
- index.js
|
||||
- index.ts
|
||||
```
|
||||
|
||||
## Organisation des feuilles de styles
|
||||
|
@ -126,8 +126,9 @@
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"App\\": "tests/",
|
||||
"App\\": "tests",
|
||||
"Chill\\DocGeneratorBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests",
|
||||
"Chill\\TicketBundle\\Tests\\": "src/Bundle/ChillTicketBundle/tests",
|
||||
"Chill\\WopiBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests",
|
||||
"Chill\\Utils\\Rector\\Tests\\": "utils/rector/tests"
|
||||
}
|
||||
|
@ -49,6 +49,10 @@
|
||||
<!-- temporarily removed, the time to find a fix -->
|
||||
<exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="TicketBundle">
|
||||
<directory suffix="Test.php">src/Bundle/ChillTicketBundle/tests/</directory>
|
||||
</testsuite>
|
||||
<!--
|
||||
<testsuite name="ReportBundle">
|
||||
<directory suffix="Test.php">src/Bundle/ChillReportBundle/Tests/</directory>
|
||||
|
74
src/Bundle/ChillMainBundle/Entity/UserGroup.php
Normal file
74
src/Bundle/ChillMainBundle/Entity/UserGroup.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?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\MainBundle\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'chill_main_user_group')]
|
||||
class UserGroup
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: false)]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]'])]
|
||||
private array $label = [];
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Collections\Collection<int, \Chill\MainBundle\Entity\User>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||
#[ORM\JoinTable(name: 'chill_main_user_group_user')]
|
||||
private Collection $users;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->users = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function addUser(User $user): self
|
||||
{
|
||||
if (!$this->users->contains($user)) {
|
||||
$this->users[] = $user;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeUser(User $user): self
|
||||
{
|
||||
if ($this->users->contains($user)) {
|
||||
$this->users->removeElement($user);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getLabel(): array
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function getUsers(): Collection
|
||||
{
|
||||
return $this->users;
|
||||
}
|
||||
}
|
@ -68,6 +68,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block wrapping_content %}
|
||||
{% block content %}
|
||||
<div class="col-8 main_search">
|
||||
{% if app.user.isAbsent %}
|
||||
@ -98,6 +99,7 @@
|
||||
{% include '@ChillMain/Homepage/index.html.twig' %}
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -0,0 +1,41 @@
|
||||
<?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\Main;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20240416145021 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create tables for user_group';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE SEQUENCE chill_main_user_group_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE TABLE chill_main_user_group (id INT NOT NULL, label JSON DEFAULT \'[]\' NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE TABLE chill_main_user_group_user (usergroup_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(usergroup_id, user_id))');
|
||||
$this->addSql('CREATE INDEX IDX_1E07F044D2112630 ON chill_main_user_group_user (usergroup_id)');
|
||||
$this->addSql('CREATE INDEX IDX_1E07F044A76ED395 ON chill_main_user_group_user (user_id)');
|
||||
$this->addSql('ALTER TABLE chill_main_user_group_user ADD CONSTRAINT FK_1E07F044D2112630 FOREIGN KEY (usergroup_id) REFERENCES chill_main_user_group (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_main_user_group_user ADD CONSTRAINT FK_1E07F044A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP SEQUENCE chill_main_user_group_id_seq');
|
||||
$this->addSql('DROP TABLE chill_main_user_group_user');
|
||||
$this->addSql('DROP TABLE chill_main_user_group');
|
||||
}
|
||||
}
|
@ -21,6 +21,8 @@ use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NonUniqueResultException;
|
||||
use Doctrine\ORM\Query;
|
||||
use libphonenumber\PhoneNumber;
|
||||
use libphonenumber\PhoneNumberFormat;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final readonly class PersonACLAwareRepository implements PersonACLAwareRepositoryInterface
|
||||
@ -298,4 +300,27 @@ final readonly class PersonACLAwareRepository implements PersonACLAwareRepositor
|
||||
\array_map(static fn (Center $c) => $c->getId(), $authorizedCenters)
|
||||
);
|
||||
}
|
||||
|
||||
public function findByPhone(PhoneNumber $phoneNumber, int $start = 0, int $limit = 20): array
|
||||
{
|
||||
$authorizedCenters = $this->authorizationHelper
|
||||
->getReachableCenters($this->security->getUser(), PersonVoter::SEE);
|
||||
|
||||
if ([] === $authorizedCenters) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$util = \libphonenumber\PhoneNumberUtil::getInstance();
|
||||
|
||||
return $this->em->createQuery(
|
||||
'SELECT p FROM '.Person::class.' p LEFT JOIN p.otherPhoneNumbers opn JOIN p.centerCurrent pcc '.
|
||||
'WHERE (p.phonenumber LIKE :phone OR p.mobilenumber LIKE :phone OR opn.phonenumber LIKE :phone) '.
|
||||
'AND pcc.center IN (:centers)'
|
||||
)
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($start)
|
||||
->setParameter('phone', $util->format($phoneNumber, PhoneNumberFormat::E164))
|
||||
->setParameter('centers', $authorizedCenters)
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Search\SearchApiQuery;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use libphonenumber\PhoneNumber;
|
||||
|
||||
interface PersonACLAwareRepositoryInterface
|
||||
{
|
||||
@ -60,4 +61,13 @@ interface PersonACLAwareRepositoryInterface
|
||||
?string $phonenumber = null,
|
||||
?string $city = null
|
||||
): array;
|
||||
|
||||
/**
|
||||
* @return list<Person>
|
||||
*/
|
||||
public function findByPhone(
|
||||
PhoneNumber $phoneNumber,
|
||||
int $start = 0,
|
||||
int $limit = 20
|
||||
): array;
|
||||
}
|
||||
|
@ -11,14 +11,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\PersonBundle\Tests\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Repository\CenterRepositoryInterface;
|
||||
use Chill\MainBundle\Repository\CountryRepository;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Entity\PersonPhone;
|
||||
use Chill\PersonBundle\Repository\PersonACLAwareRepository;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
@ -98,4 +101,67 @@ final class PersonACLAwareRepositoryTest extends KernelTestCase
|
||||
$this->assertStringContainsString('diallo', strtolower($person->getFirstName().' '.$person->getLastName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providePersonsWithPhoneNumbers
|
||||
*/
|
||||
public function testFindByPhonenumber(\libphonenumber\PhoneNumber $phoneNumber, ?int $expectedId): void
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
|
||||
$authorizationHelper->getReachableCenters(Argument::exact($user), Argument::exact(PersonVoter::SEE))
|
||||
->willReturn($this->centerRepository->findAll());
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->getUser()->willReturn($user);
|
||||
|
||||
$repository = new PersonACLAwareRepository(
|
||||
$security->reveal(),
|
||||
$this->entityManager,
|
||||
$this->countryRepository,
|
||||
$authorizationHelper->reveal()
|
||||
);
|
||||
|
||||
$actual = $repository->findByPhone($phoneNumber, 0, 10);
|
||||
|
||||
if (null === $expectedId) {
|
||||
self::assertCount(0, $actual);
|
||||
} else {
|
||||
$actualIds = array_map(fn (Person $person) => $person->getId(), $actual);
|
||||
|
||||
self::assertContains($expectedId, $actualIds);
|
||||
}
|
||||
}
|
||||
|
||||
public static function providePersonsWithPhoneNumbers(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||
$center = $em->createQuery('SELECT c FROM '.Center::class.' c ')->setMaxResults(1)
|
||||
->getSingleResult();
|
||||
$util = \libphonenumber\PhoneNumberUtil::getInstance();
|
||||
|
||||
$mobile = $util->parse('+32486123456');
|
||||
$fixed = $util->parse('+3281136917');
|
||||
$anotherMobile = $util->parse('+32486123478');
|
||||
$person = (new Person())->setFirstName('diallo')->setLastName('diallo')->setCenter($center);
|
||||
$person->setMobilenumber($mobile)->setPhonenumber($fixed);
|
||||
$otherPhone = new PersonPhone();
|
||||
$otherPhone->setPerson($person);
|
||||
$otherPhone->setPhonenumber($anotherMobile);
|
||||
$otherPhone->setType('mobile');
|
||||
|
||||
$em->persist($person);
|
||||
$em->persist($otherPhone);
|
||||
|
||||
$em->flush();
|
||||
|
||||
self::ensureKernelShutdown();
|
||||
|
||||
yield [$mobile, $person->getId()];
|
||||
yield [$anotherMobile, $person->getId()];
|
||||
yield [$fixed, $person->getId()];
|
||||
yield [$util->parse('+331234567890'), null];
|
||||
}
|
||||
}
|
||||
|
66
src/Bundle/ChillTicketBundle/chill.api.specs.yaml
Normal file
66
src/Bundle/ChillTicketBundle/chill.api.specs.yaml
Normal file
@ -0,0 +1,66 @@
|
||||
components:
|
||||
schemas:
|
||||
Motive:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
label:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
example:
|
||||
fr: Retard de livraison
|
||||
active:
|
||||
type: boolean
|
||||
MotiveById:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- ticket_motive
|
||||
required:
|
||||
- id
|
||||
- type
|
||||
|
||||
paths:
|
||||
/1.0/ticket/motive.json:
|
||||
get:
|
||||
tags:
|
||||
- ticket
|
||||
summary: A list of available ticket's motive
|
||||
responses:
|
||||
200:
|
||||
description: "OK"
|
||||
|
||||
/1.0/ticket/{id}/motive/set:
|
||||
post:
|
||||
tags:
|
||||
- ticket
|
||||
summary: Replace the existing ticket's motive by a new one
|
||||
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:
|
||||
motive:
|
||||
$ref: "#/components/schemas/MotiveById"
|
||||
responses:
|
||||
201:
|
||||
description: "ACCEPTED"
|
||||
422:
|
||||
description: "UNPROCESSABLE ENTITY"
|
@ -1,4 +1,4 @@
|
||||
// this file loads all assets from the Chill person bundle
|
||||
module.exports = function(encore, entries) {
|
||||
encore.addEntry('ticket_app', __dirname + '/src/Resources/public/vuejs/TicketApp/index.ts');
|
||||
encore.addEntry('page_ticket', __dirname + '/src/Resources/public/page/ticket/index.ts');
|
||||
encore.addEntry('vue_ticket_app', __dirname + '/src/Resources/public/vuejs/TicketApp/index.ts');
|
||||
};
|
||||
|
@ -0,0 +1,19 @@
|
||||
<?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;
|
||||
|
||||
class AssociateByPhonenumberCommand
|
||||
{
|
||||
public function __construct(
|
||||
public string $phonenumber,
|
||||
) {}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?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;
|
||||
|
||||
final readonly class CreateTicketCommand
|
||||
{
|
||||
public function __construct(
|
||||
public string $externalReference = '',
|
||||
) {}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?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\PersonBundle\Repository\PersonACLAwareRepositoryInterface;
|
||||
use Chill\TicketBundle\Action\Ticket\AssociateByPhonenumberCommand;
|
||||
use Chill\TicketBundle\Entity\PersonHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
|
||||
class AssociateByPhonenumberCommandHandler
|
||||
{
|
||||
public function __construct(
|
||||
private PersonACLAwareRepositoryInterface $personRepository,
|
||||
private PhoneNumberUtil $phoneNumberUtil,
|
||||
private ClockInterface $clock,
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {}
|
||||
|
||||
public function __invoke(Ticket $ticket, AssociateByPhonenumberCommand $command): void
|
||||
{
|
||||
$phone = $this->phoneNumberUtil->parse($command->phonenumber);
|
||||
$persons = $this->personRepository->findByPhone($phone);
|
||||
|
||||
foreach ($persons as $person) {
|
||||
$history = new PersonHistory($person, $ticket, $this->clock->now());
|
||||
$this->entityManager->persist($history);
|
||||
}
|
||||
}
|
||||
}
|
@ -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\Action\Ticket\Handler;
|
||||
|
||||
use Chill\TicketBundle\Action\Ticket\CreateTicketCommand;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
|
||||
class CreateTicketCommandHandler
|
||||
{
|
||||
public function __invoke(CreateTicketCommand $command): Ticket
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$ticket->setExternalRef($command->externalReference);
|
||||
|
||||
return $ticket;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?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\ReplaceMotiveCommand;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
|
||||
final readonly class ReplaceMotiveCommandHandler
|
||||
{
|
||||
public function __construct(
|
||||
private ClockInterface $clock,
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {}
|
||||
|
||||
public function handle(Ticket $ticket, ReplaceMotiveCommand $command): void
|
||||
{
|
||||
if (null === $command->motive) {
|
||||
throw new \InvalidArgumentException('The new motive cannot be null');
|
||||
}
|
||||
|
||||
// will add if there are no existing motive
|
||||
$readyToAdd = 0 === count($ticket->getMotiveHistories());
|
||||
|
||||
foreach ($ticket->getMotiveHistories() as $history) {
|
||||
if (null !== $history->getEndDate()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($history->getMotive() === $command->motive) {
|
||||
// we apply the same motive, we do nothing
|
||||
continue;
|
||||
}
|
||||
|
||||
$history->setEndDate($this->clock->now());
|
||||
$readyToAdd = true;
|
||||
}
|
||||
|
||||
if ($readyToAdd) {
|
||||
$history = new MotiveHistory($command->motive, $ticket, $this->clock->now());
|
||||
$this->entityManager->persist($history);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?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\TicketBundle\Entity\Motive;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
final readonly class ReplaceMotiveCommand
|
||||
{
|
||||
public function __construct(
|
||||
#[Assert\NotNull]
|
||||
#[Groups(['write'])]
|
||||
public ?Motive $motive,
|
||||
) {}
|
||||
}
|
@ -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\Controller;
|
||||
|
||||
use Chill\TicketBundle\Action\Ticket\AssociateByPhonenumberCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\AssociateByPhonenumberCommandHandler;
|
||||
use Chill\TicketBundle\Action\Ticket\CreateTicketCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\CreateTicketCommandHandler;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final readonly class CreateTicketController
|
||||
{
|
||||
public function __construct(
|
||||
private CreateTicketCommandHandler $createTicketCommandHandler,
|
||||
private AssociateByPhonenumberCommandHandler $associateByPhonenumberCommandHandler,
|
||||
private Security $security,
|
||||
private UrlGeneratorInterface $urlGenerator,
|
||||
private EntityManagerInterface $entityManager
|
||||
) {}
|
||||
|
||||
#[Route('{_locale}/ticket/ticket/create')]
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users are allowed to create tickets.');
|
||||
}
|
||||
|
||||
$createCommand = new CreateTicketCommand($request->query->get('extId', ''));
|
||||
$ticket = $this->createTicketCommandHandler->__invoke($createCommand);
|
||||
|
||||
$this->entityManager->persist($ticket);
|
||||
|
||||
if ($request->query->has('caller')) {
|
||||
$associateByPhonenumberCommand = new AssociateByPhonenumberCommand($request->query->get('caller'));
|
||||
$this->associateByPhonenumberCommandHandler->__invoke($ticket, $associateByPhonenumberCommand);
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->urlGenerator->generate('chill_ticket_ticket_edit', ['id' => $ticket->getId()])
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?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 Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Twig\Environment;
|
||||
|
||||
class EditTicketController
|
||||
{
|
||||
public function __construct(
|
||||
private Environment $templating
|
||||
) {}
|
||||
|
||||
#[Route('/{_locale}/ticket/ticket/{id}/edit', name: 'chill_ticket_ticket_edit')]
|
||||
public function __invoke(
|
||||
Ticket $ticket
|
||||
): Response {
|
||||
return new Response(
|
||||
$this->templating->render(
|
||||
'@ChillTicket/Ticket/edit.html.twig',
|
||||
[
|
||||
'ticket' => $ticket,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?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\MainBundle\CRUD\Controller\ApiController;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
final class MotiveApiController extends ApiController
|
||||
{
|
||||
protected function customizeQuery(string $action, Request $request, $query): void
|
||||
{
|
||||
/* @var $query QueryBuilder */
|
||||
$query->andWhere('e.active = TRUE');
|
||||
}
|
||||
}
|
@ -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 Chill\TicketBundle\Controller;
|
||||
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\ReplaceMotiveCommandHandler;
|
||||
use Chill\TicketBundle\Action\Ticket\ReplaceMotiveCommand;
|
||||
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\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
final readonly class ReplaceMotiveController
|
||||
{
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private ReplaceMotiveCommandHandler $replaceMotiveCommandHandler,
|
||||
private SerializerInterface $serializer,
|
||||
private ValidatorInterface $validator,
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {}
|
||||
|
||||
#[Route('/api/1.0/ticket/{id}/motive/set', name: 'chill_ticket_motive_set', methods: ['POST'])]
|
||||
public function __invoke(Ticket $ticket, Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('');
|
||||
}
|
||||
|
||||
$command = $this->serializer->deserialize($request->getContent(), ReplaceMotiveCommand::class, 'json', [
|
||||
AbstractNormalizer::GROUPS => ['write'],
|
||||
]);
|
||||
|
||||
$errors = $this->validator->validate($command);
|
||||
|
||||
if (0 < $errors->count()) {
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize($errors, 'json'),
|
||||
Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||
);
|
||||
}
|
||||
|
||||
$this->replaceMotiveCommandHandler->handle($ticket, $command);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(null, Response::HTTP_CREATED);
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?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 Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class ViewTicketController
|
||||
{
|
||||
#[Route('/{_locale}/ticket/ticket/{ticketId}', name: 'chill_ticket_ticket_view')]
|
||||
public function __invoke(int $ticketId): Response
|
||||
{
|
||||
return new Response('ok');
|
||||
}
|
||||
}
|
@ -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\DataFixtures\ORM;
|
||||
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
final class LoadMotives extends Fixture implements FixtureGroupInterface
|
||||
{
|
||||
public static function getGroups(): array
|
||||
{
|
||||
return ['ticket'];
|
||||
}
|
||||
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
foreach (explode("\n", self::MOTIVES) as $label) {
|
||||
if ('' === trim($label)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$motive = new Motive();
|
||||
$motive->setLabel(['fr' => trim($label)]);
|
||||
|
||||
$manager->persist($motive);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
private const MOTIVES = <<<'TXT'
|
||||
Coordonnées
|
||||
Horaire de passage
|
||||
Retard de livraison
|
||||
Erreur de livraison
|
||||
Colis incomplet
|
||||
MATLOC
|
||||
Retard DASRI
|
||||
Planning d'astreintes
|
||||
Planning des tournées
|
||||
Contrôle pompe
|
||||
Changement de rendez-vous
|
||||
Renseignement facturation/prestation
|
||||
Décès patient
|
||||
Demande de prise en charge
|
||||
Information absence
|
||||
Demande bulletin de situation
|
||||
Difficultés accès logement
|
||||
Déplacement inutile
|
||||
Problème de prélèvement/de commande
|
||||
Parc auto
|
||||
Demande d'admission
|
||||
Retrait de matériel au domicile
|
||||
Comptes-rendus
|
||||
Démarchage commercial
|
||||
Demande de transport
|
||||
Demande laboratoire
|
||||
Demande admission
|
||||
Suivi de prise en charge
|
||||
Mauvaise adresse
|
||||
Patient absent
|
||||
Annulation
|
||||
Colis perdu
|
||||
Changement de rendez-vous
|
||||
Coordination interservices
|
||||
Problème de substitution produits
|
||||
Problème ordonnance
|
||||
Réclamations facture
|
||||
Préparation urgente
|
||||
TXT;
|
||||
}
|
@ -11,16 +11,54 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\TicketBundle\DependencyInjection;
|
||||
|
||||
use Chill\TicketBundle\Controller\MotiveApiController;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
|
||||
use Symfony\Component\DependencyInjection\Loader;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class ChillTicketExtension extends Extension
|
||||
class ChillTicketExtension extends Extension implements PrependExtensionInterface
|
||||
{
|
||||
public function load(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
|
||||
$loader->load('services.yaml');
|
||||
}
|
||||
|
||||
public function prepend(ContainerBuilder $container)
|
||||
{
|
||||
$this->prependApi($container);
|
||||
}
|
||||
|
||||
private function prependApi(ContainerBuilder $container): void
|
||||
{
|
||||
$container->prependExtensionConfig('chill_main', [
|
||||
'apis' => [
|
||||
[
|
||||
'class' => Motive::class,
|
||||
'name' => 'motive',
|
||||
'base_path' => '/api/1.0/ticket/motive',
|
||||
'controller' => MotiveApiController::class,
|
||||
'base_role' => 'ROLE_USER',
|
||||
'actions' => [
|
||||
'_index' => [
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true,
|
||||
],
|
||||
],
|
||||
'_entity' => [
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
97
src/Bundle/ChillTicketBundle/src/Entity/AddresseeHistory.php
Normal file
97
src/Bundle/ChillTicketBundle/src/Entity/AddresseeHistory.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?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\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\UserGroup;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity()]
|
||||
#[ORM\Table(name: 'addressee_history', schema: 'chill_ticket')]
|
||||
class AddresseeHistory implements TrackUpdateInterface, TrackCreationInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
use TrackUpdateTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: false)]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?User $addresseeUser = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: UserGroup::class)]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?UserGroup $addresseeGroup = null;
|
||||
|
||||
private ?\DateTimeImmutable $endDate = null;
|
||||
|
||||
public function __construct(
|
||||
User|UserGroup $addressee,
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: false)]
|
||||
private \DateTimeImmutable $startDate,
|
||||
#[ORM\ManyToOne(targetEntity: Ticket::class)]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private Ticket $ticket,
|
||||
) {
|
||||
if ($addressee instanceof User) {
|
||||
$this->addresseeUser = $addressee;
|
||||
} else {
|
||||
$this->addresseeGroup = $addressee;
|
||||
}
|
||||
}
|
||||
|
||||
public function getAddressee(): UserGroup|User
|
||||
{
|
||||
if (null !== $this->addresseeGroup) {
|
||||
return $this->addresseeGroup;
|
||||
}
|
||||
|
||||
return $this->addresseeUser;
|
||||
}
|
||||
|
||||
public function getAddresseeGroup(): ?UserGroup
|
||||
{
|
||||
return $this->addresseeGroup;
|
||||
}
|
||||
|
||||
public function getAddresseeUser(): ?User
|
||||
{
|
||||
return $this->addresseeUser;
|
||||
}
|
||||
|
||||
public function getEndDate(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getStartDate(): \DateTimeImmutable
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
public function getTicket(): Ticket
|
||||
{
|
||||
return $this->ticket;
|
||||
}
|
||||
}
|
55
src/Bundle/ChillTicketBundle/src/Entity/Comment.php
Normal file
55
src/Bundle/ChillTicketBundle/src/Entity/Comment.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?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\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Mapping\JoinColumn;
|
||||
|
||||
#[ORM\Entity()]
|
||||
#[ORM\Table(name: 'comment', schema: 'chill_ticket')]
|
||||
class Comment implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
use TrackUpdateTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: false)]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
private ?int $id = null;
|
||||
|
||||
public function __construct(
|
||||
#[ORM\ManyToOne(targetEntity: Ticket::class, inversedBy: 'comments')]
|
||||
#[JoinColumn(nullable: false)]
|
||||
private Ticket $ticket,
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])]
|
||||
private string $content = ''
|
||||
) {}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getContent(): string
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function getTicket(): Ticket
|
||||
{
|
||||
return $this->ticket;
|
||||
}
|
||||
}
|
91
src/Bundle/ChillTicketBundle/src/Entity/InputHistory.php
Normal file
91
src/Bundle/ChillTicketBundle/src/Entity/InputHistory.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?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\Entity\User;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity()]
|
||||
#[ORM\Table(name: 'input_history', schema: 'chill_ticket')]
|
||||
class InputHistory
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: false)]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Person::class)]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?Person $person = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: ThirdParty::class)]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?ThirdParty $thirdParty = null;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true, options: ['default' => null])]
|
||||
private ?\DateTimeImmutable $endDate = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?User $removedBy = null;
|
||||
|
||||
public function __construct(
|
||||
Person|ThirdParty $input,
|
||||
#[ORM\ManyToOne(targetEntity: Ticket::class)]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private Ticket $ticket,
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: false)]
|
||||
private \DateTimeImmutable $startDate,
|
||||
) {
|
||||
if ($input instanceof Person) {
|
||||
$this->person = $input;
|
||||
} else {
|
||||
$this->thirdParty = $input;
|
||||
}
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getEndDate(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
public function getRemovedBy(): ?User
|
||||
{
|
||||
return $this->removedBy;
|
||||
}
|
||||
|
||||
public function getStartDate(): \DateTimeImmutable
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
public function getTicket(): Ticket
|
||||
{
|
||||
return $this->ticket;
|
||||
}
|
||||
|
||||
public function getInput(): Person|ThirdParty
|
||||
{
|
||||
if (null !== $this->person) {
|
||||
return $this->person;
|
||||
}
|
||||
|
||||
return $this->thirdParty;
|
||||
}
|
||||
}
|
60
src/Bundle/ChillTicketBundle/src/Entity/Motive.php
Normal file
60
src/Bundle/ChillTicketBundle/src/Entity/Motive.php
Normal file
@ -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\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
#[ORM\Entity()]
|
||||
#[ORM\Table(name: 'motive', schema: 'chill_ticket')]
|
||||
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['ticket_motive' => Motive::class])]
|
||||
class Motive
|
||||
{
|
||||
#[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::JSON, nullable: false, options: ['default' => '[]'])]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private array $label = [];
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, nullable: false, options: ['default' => true])]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private bool $active = true;
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
public function setActive(bool $active): void
|
||||
{
|
||||
$this->active = $active;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getLabel(): array
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setLabel(array $label): void
|
||||
{
|
||||
$this->label = $label;
|
||||
}
|
||||
}
|
80
src/Bundle/ChillTicketBundle/src/Entity/MotiveHistory.php
Normal file
80
src/Bundle/ChillTicketBundle/src/Entity/MotiveHistory.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?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 Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'motives_history', schema: 'chill_ticket')]
|
||||
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['ticket_motive_history' => MotiveHistory::class])]
|
||||
class MotiveHistory 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;
|
||||
|
||||
public function __construct(
|
||||
#[ORM\ManyToOne(targetEntity: Motive::class)]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private Motive $motive,
|
||||
#[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')
|
||||
) {
|
||||
$ticket->addMotiveHistory($this);
|
||||
}
|
||||
|
||||
public function getEndDate(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getMotive(): Motive
|
||||
{
|
||||
return $this->motive;
|
||||
}
|
||||
|
||||
public function getStartDate(): \DateTimeImmutable
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
public function getTicket(): Ticket
|
||||
{
|
||||
return $this->ticket;
|
||||
}
|
||||
|
||||
public function setEndDate(?\DateTimeImmutable $endDate): void
|
||||
{
|
||||
$this->endDate = $endDate;
|
||||
}
|
||||
}
|
87
src/Bundle/ChillTicketBundle/src/Entity/PersonHistory.php
Normal file
87
src/Bundle/ChillTicketBundle/src/Entity/PersonHistory.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?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\MainBundle\Entity\User;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'person_history', schema: 'chill_ticket')]
|
||||
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['ticket_person_history' => PersonHistory::class])]
|
||||
class PersonHistory 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: User::class)]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private ?User $removedBy = null;
|
||||
|
||||
public function __construct(
|
||||
#[ORM\ManyToOne(targetEntity: Person::class, fetch: 'EAGER')]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private Person $person,
|
||||
#[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,
|
||||
) {
|
||||
// keep ticket instance in sync with this
|
||||
$this->ticket->addPersonHistory($this);
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getPerson(): Person
|
||||
{
|
||||
return $this->person;
|
||||
}
|
||||
|
||||
public function getEndDate(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
public function getTicket(): Ticket
|
||||
{
|
||||
return $this->ticket;
|
||||
}
|
||||
|
||||
public function getStartDate(): \DateTimeImmutable
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
public function getRemovedBy(): ?User
|
||||
{
|
||||
return $this->removedBy;
|
||||
}
|
||||
}
|
187
src/Bundle/ChillTicketBundle/src/Entity/Ticket.php
Normal file
187
src/Bundle/ChillTicketBundle/src/Entity/Ticket.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?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\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\UserGroup;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\ReadableCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'ticket', schema: 'chill_ticket')]
|
||||
class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
use TrackUpdateTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: false)]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, AddresseeHistory>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: AddresseeHistory::class, mappedBy: 'ticket')]
|
||||
private Collection $addresseeHistory;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Comment>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'ticket')]
|
||||
private Collection $comments;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])]
|
||||
private string $externalRef = '';
|
||||
|
||||
/**
|
||||
* @var Collection<int, InputHistory>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: InputHistory::class, mappedBy: 'ticket')]
|
||||
private Collection $inputHistories;
|
||||
|
||||
/**
|
||||
* @var Collection<int, MotiveHistory>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: MotiveHistory::class, mappedBy: 'ticket')]
|
||||
private Collection $motiveHistories;
|
||||
|
||||
/**
|
||||
* @var Collection<int, PersonHistory>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: PersonHistory::class, mappedBy: 'ticket')]
|
||||
private Collection $personHistories;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addresseeHistory = new ArrayCollection();
|
||||
$this->comments = new ArrayCollection();
|
||||
$this->motiveHistories = new ArrayCollection();
|
||||
$this->personHistories = new ArrayCollection();
|
||||
$this->inputHistories = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getExternalRef(): string
|
||||
{
|
||||
return $this->externalRef;
|
||||
}
|
||||
|
||||
public function setExternalRef(string $externalRef): void
|
||||
{
|
||||
$this->externalRef = $externalRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Person>
|
||||
*/
|
||||
public function getPersons(): array
|
||||
{
|
||||
return $this->personHistories
|
||||
->filter(fn (PersonHistory $personHistory) => null === $personHistory->getEndDate())
|
||||
->map(fn (PersonHistory $personHistory) => $personHistory->getPerson())
|
||||
->getValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a PersonHistory.
|
||||
*
|
||||
* This method should not be used, use @see{PersonHistory::__construct()} insted.
|
||||
*/
|
||||
public function addPersonHistory(PersonHistory $personHistory): void
|
||||
{
|
||||
$this->personHistories->add($personHistory);
|
||||
}
|
||||
|
||||
public function addMotiveHistory(MotiveHistory $motiveHistory): void
|
||||
{
|
||||
$this->motiveHistories->add($motiveHistory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<UserGroup|User>
|
||||
*/
|
||||
public function getCurrentAddressee(): array
|
||||
{
|
||||
$addresses = [];
|
||||
|
||||
foreach ($this->addresseeHistory
|
||||
->filter(fn (AddresseeHistory $addresseeHistory) => null === $addresseeHistory->getEndDate()) as $addressHistory) {
|
||||
$addresses[] = $addressHistory->getAddressee();
|
||||
}
|
||||
|
||||
return $addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReadableCollection<int, Comment>
|
||||
*/
|
||||
public function getComments(): ReadableCollection
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<ThirdParty|Person>
|
||||
*/
|
||||
public function getCurrentInputs(): array
|
||||
{
|
||||
$inputs = [];
|
||||
|
||||
foreach ($this->inputHistories
|
||||
->filter(fn (InputHistory $inputHistory) => null === $inputHistory->getEndDate()) as $inputHistory
|
||||
) {
|
||||
$inputs[] = $inputHistory->getInput();
|
||||
}
|
||||
|
||||
return $inputs;
|
||||
}
|
||||
|
||||
public function getMotive(): ?Motive
|
||||
{
|
||||
foreach ($this->motiveHistories as $motiveHistory) {
|
||||
if (null === $motiveHistory->getEndDate()) {
|
||||
return $motiveHistory->getMotive();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReadableCollection<int, MotiveHistory>
|
||||
*/
|
||||
public function getMotiveHistories(): ReadableCollection
|
||||
{
|
||||
return $this->motiveHistories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReadableCollection<int, PersonHistory>
|
||||
*/
|
||||
public function getPersonHistories(): ReadableCollection
|
||||
{
|
||||
return $this->personHistories;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
@import '~ChillMainAssets/module/bootstrap/shared';
|
||||
|
||||
div.banner {
|
||||
div#header-ticket-main {
|
||||
background: none repeat scroll 0 0 #ae986fFF;
|
||||
color: $white;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
div#header-ticket-details {
|
||||
background: none repeat scroll 0 0 #d3c7b1FF;
|
||||
color: $white;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
div.contact {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
& > * {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
import './banner.scss';
|
@ -0,0 +1,52 @@
|
||||
import {DateTime, TranslatableString, User} from "../../../../ChillMainBundle/Resources/public/types";
|
||||
import {Person} from "../../../../ChillPersonBundle/Resources/public/types";
|
||||
|
||||
export interface Motive {
|
||||
type: "ticket_motive"
|
||||
id: number,
|
||||
active: boolean,
|
||||
label: TranslatableString
|
||||
}
|
||||
|
||||
interface TicketHistory<T extends string, D extends object> {
|
||||
event_type: T,
|
||||
at: DateTime,
|
||||
by: User,
|
||||
data: D
|
||||
}
|
||||
|
||||
interface PersonHistory {
|
||||
type: "ticket_person_history",
|
||||
id: number,
|
||||
startDate: DateTime,
|
||||
endDate: null|DateTime,
|
||||
person: Person,
|
||||
removedBy: null,
|
||||
createdBy: User|null,
|
||||
createdAt: DateTime|null
|
||||
}
|
||||
|
||||
interface MotiveHistory {
|
||||
type: "ticket_motive_history",
|
||||
id: number,
|
||||
startDate: null,
|
||||
endDate: null|DateTime,
|
||||
motive: Motive,
|
||||
createdBy: User|null,
|
||||
createdAt: DateTime|null,
|
||||
}
|
||||
|
||||
interface AddPersonEvent extends TicketHistory<"add_person", PersonHistory> {};
|
||||
interface SetMotiveEvent extends TicketHistory<"set_motive", MotiveHistory> {};
|
||||
|
||||
type TicketHistoryLine = AddPersonEvent | SetMotiveEvent;
|
||||
|
||||
export interface Ticket {
|
||||
type: "ticket_ticket"
|
||||
id: number
|
||||
externalRef: string
|
||||
currentPersons: Person[]
|
||||
currentMotive: null|Motive
|
||||
history: TicketHistoryLine[],
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p>{{ $t('hello', {'name': 'Boris'})}}</p>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
@ -0,0 +1,5 @@
|
||||
export const messages = {
|
||||
fr: {
|
||||
hello: "Bonjour {name}"
|
||||
}
|
||||
};
|
@ -0,0 +1,38 @@
|
||||
import {createApp} from "vue";
|
||||
import { _createI18n } from "../../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
||||
import VueToast from 'vue-toast-notification';
|
||||
import 'vue-toast-notification/dist/theme-sugar.css';
|
||||
import {messages} from "./i18n/messages";
|
||||
import App from './App.vue';
|
||||
import {Ticket} from "../../types";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
initialTicket: string
|
||||
}
|
||||
}
|
||||
|
||||
const i18n = _createI18n(messages)
|
||||
|
||||
// the initial ticket is serialized there:
|
||||
console.log(window.initialTicket);
|
||||
// to have js object
|
||||
const ticket = JSON.parse(window.initialTicket) as Ticket;
|
||||
console.log("the ticket for this app (at page loading)", ticket);
|
||||
|
||||
for (const eh of ticket.history) {
|
||||
if (eh.event_type === 'add_person') {
|
||||
console.log("add_person", eh.data.person);
|
||||
} else if (eh.event_type === 'set_motive') {
|
||||
console.log("set_motive", eh.data.motive);
|
||||
}
|
||||
}
|
||||
|
||||
const _app = createApp({
|
||||
template: '<app></app>',
|
||||
})
|
||||
.use(i18n)
|
||||
.use(VueToast)
|
||||
.component('app', App)
|
||||
.mount('#ticketRoot');
|
||||
|
@ -0,0 +1,24 @@
|
||||
<div class="banner banner-ticket">
|
||||
<div id="header-ticket-main" class="header-name">
|
||||
<div class="container-xxl">
|
||||
<div class="row">
|
||||
<div class="col-md-6 ps-md-5 ps-xxl-0">
|
||||
TODO
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
TODO
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="header-ticket-details" class="header-details">
|
||||
<div class="container-xxl">
|
||||
<div class="row justify-content-between">
|
||||
<div class="col-md-12 ps-md-5 ps-xxl-0 container">
|
||||
<p>TODO</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,18 @@
|
||||
{% extends '@ChillTicket/layout.html.twig' %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('vue_ticket_app') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
window.initialTicket = "{{ ticket|serialize('json', {'groups': 'read'})|escape('js') }}";
|
||||
</script>
|
||||
{{ encore_entry_script_tags('vue_ticket_app') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="ticketRoot"></div>
|
||||
{% endblock %}
|
@ -0,0 +1,21 @@
|
||||
{% extends '@ChillMain/layout.html.twig' %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('page_ticket') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ encore_entry_script_tags('page_ticket') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block top_banner %}
|
||||
{{ include('@ChillTicket/Banner/banner.html.twig') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block wrapping_content %}
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-sm-12">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,91 @@
|
||||
<?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\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\PersonHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
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 normalize($object, ?string $format = null, array $context = [])
|
||||
{
|
||||
if (!$object instanceof Ticket) {
|
||||
throw new UnexpectedValueException();
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'ticket_ticket',
|
||||
'id' => $object->getId(),
|
||||
'externalRef' => $object->getExternalRef(),
|
||||
'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']),
|
||||
'history' => array_values($this->serializeHistory($object, $format, ['groups' => 'read'])),
|
||||
];
|
||||
}
|
||||
|
||||
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 (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(),
|
||||
),
|
||||
];
|
||||
|
||||
usort(
|
||||
$events,
|
||||
static function (array $a, array $b): int {
|
||||
return $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, $context),
|
||||
],
|
||||
$events
|
||||
);
|
||||
}
|
||||
}
|
@ -8,5 +8,11 @@ services:
|
||||
tags:
|
||||
- controller.service_arguments
|
||||
|
||||
Chill\TicketBundle\Action\Ticket\Handler\:
|
||||
resource: '../Action/Ticket/Handler/'
|
||||
|
||||
Chill\TicketBundle\Serializer\:
|
||||
resource: '../Serializer/'
|
||||
|
||||
Chill\TicketBundle\DataFixtures\:
|
||||
resource: '../DataFixtures/'
|
||||
|
@ -0,0 +1,139 @@
|
||||
<?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 Version20240416145919 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create schema and tables for chill ticket';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE SCHEMA chill_ticket');
|
||||
$this->addSql('CREATE SEQUENCE chill_ticket.addressee_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE SEQUENCE chill_ticket.comment_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE SEQUENCE chill_ticket.input_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE SEQUENCE chill_ticket.motive_id_seq INCREMENT BY 1 MINVALUE 1 START 1000');
|
||||
$this->addSql('CREATE SEQUENCE chill_ticket.motives_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE SEQUENCE chill_ticket.person_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE SEQUENCE chill_ticket.ticket_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE TABLE chill_ticket.addressee_history (id INT NOT NULL, ticket_id INT NOT NULL, startDate TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, addresseeUser_id INT DEFAULT NULL, addresseeGroup_id INT DEFAULT NULL, createdBy_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_434EBDBD4D06F00C ON chill_ticket.addressee_history (addresseeUser_id)');
|
||||
$this->addSql('CREATE INDEX IDX_434EBDBD776D9A84 ON chill_ticket.addressee_history (addresseeGroup_id)');
|
||||
$this->addSql('CREATE INDEX IDX_434EBDBD700047D2 ON chill_ticket.addressee_history (ticket_id)');
|
||||
$this->addSql('CREATE INDEX IDX_434EBDBD3174800F ON chill_ticket.addressee_history (createdBy_id)');
|
||||
$this->addSql('CREATE INDEX IDX_434EBDBD65FF1AEC ON chill_ticket.addressee_history (updatedBy_id)');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.addressee_history.startDate IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.addressee_history.createdAt IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.addressee_history.updatedAt IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('CREATE TABLE chill_ticket.comment (id INT NOT NULL, ticket_id INT NOT NULL, content TEXT DEFAULT \'\' NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, createdBy_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_79EBD416700047D2 ON chill_ticket.comment (ticket_id)');
|
||||
$this->addSql('CREATE INDEX IDX_79EBD4163174800F ON chill_ticket.comment (createdBy_id)');
|
||||
$this->addSql('CREATE INDEX IDX_79EBD41665FF1AEC ON chill_ticket.comment (updatedBy_id)');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.comment.createdAt IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.comment.updatedAt IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('CREATE TABLE chill_ticket.input_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, thirdParty_id INT DEFAULT NULL, removedBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_E2AA301F217BBB47 ON chill_ticket.input_history (person_id)');
|
||||
$this->addSql('CREATE INDEX IDX_E2AA301F3EA5CAB0 ON chill_ticket.input_history (thirdParty_id)');
|
||||
$this->addSql('CREATE INDEX IDX_E2AA301FB8346CCF ON chill_ticket.input_history (removedBy_id)');
|
||||
$this->addSql('CREATE INDEX IDX_E2AA301F700047D2 ON chill_ticket.input_history (ticket_id)');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.input_history.endDate IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.input_history.startDate IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('CREATE TABLE chill_ticket.motive (id INT NOT NULL, label JSON DEFAULT \'[]\' NOT NULL, active BOOLEAN DEFAULT true NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE TABLE chill_ticket.motives_history (id INT NOT NULL, motive_id INT NOT 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, createdBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_48995CFF9658649C ON chill_ticket.motives_history (motive_id)');
|
||||
$this->addSql('CREATE INDEX IDX_48995CFF700047D2 ON chill_ticket.motives_history (ticket_id)');
|
||||
$this->addSql('CREATE INDEX IDX_48995CFF3174800F ON chill_ticket.motives_history (createdBy_id)');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.motives_history.endDate IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.motives_history.startDate IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.motives_history.createdAt IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('CREATE TABLE chill_ticket.person_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, removedBy_id INT DEFAULT NULL, createdBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_F2969246B8346CCF ON chill_ticket.person_history (removedBy_id)');
|
||||
$this->addSql('CREATE INDEX IDX_F2969246217BBB47 ON chill_ticket.person_history (person_id)');
|
||||
$this->addSql('CREATE INDEX IDX_F2969246700047D2 ON chill_ticket.person_history (ticket_id)');
|
||||
$this->addSql('CREATE INDEX IDX_F29692463174800F ON chill_ticket.person_history (createdBy_id)');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.person_history.endDate IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.person_history.startDate IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.person_history.createdAt IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('CREATE TABLE chill_ticket.ticket (id INT NOT NULL, externalRef TEXT DEFAULT \'\' NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, createdBy_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_B0A5F7233174800F ON chill_ticket.ticket (createdBy_id)');
|
||||
$this->addSql('CREATE INDEX IDX_B0A5F72365FF1AEC ON chill_ticket.ticket (updatedBy_id)');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.ticket.createdAt IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_ticket.ticket.updatedAt IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('ALTER TABLE chill_ticket.addressee_history ADD CONSTRAINT FK_434EBDBD4D06F00C FOREIGN KEY (addresseeUser_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.addressee_history ADD CONSTRAINT FK_434EBDBD776D9A84 FOREIGN KEY (addresseeGroup_id) REFERENCES chill_main_user_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.addressee_history ADD CONSTRAINT FK_434EBDBD700047D2 FOREIGN KEY (ticket_id) REFERENCES chill_ticket.ticket (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.addressee_history ADD CONSTRAINT FK_434EBDBD3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.addressee_history ADD CONSTRAINT FK_434EBDBD65FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.comment ADD CONSTRAINT FK_79EBD416700047D2 FOREIGN KEY (ticket_id) REFERENCES chill_ticket.ticket (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.comment ADD CONSTRAINT FK_79EBD4163174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.comment ADD CONSTRAINT FK_79EBD41665FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.input_history ADD CONSTRAINT FK_E2AA301F217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.input_history ADD CONSTRAINT FK_E2AA301F3EA5CAB0 FOREIGN KEY (thirdParty_id) REFERENCES chill_3party.third_party (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.input_history ADD CONSTRAINT FK_E2AA301FB8346CCF FOREIGN KEY (removedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.input_history ADD CONSTRAINT FK_E2AA301F700047D2 FOREIGN KEY (ticket_id) REFERENCES chill_ticket.ticket (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.motives_history ADD CONSTRAINT FK_48995CFF9658649C FOREIGN KEY (motive_id) REFERENCES chill_ticket.motive (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.motives_history ADD CONSTRAINT FK_48995CFF700047D2 FOREIGN KEY (ticket_id) REFERENCES chill_ticket.ticket (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.motives_history ADD CONSTRAINT FK_48995CFF3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.person_history ADD CONSTRAINT FK_F2969246B8346CCF FOREIGN KEY (removedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.person_history ADD CONSTRAINT FK_F2969246217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.person_history ADD CONSTRAINT FK_F2969246700047D2 FOREIGN KEY (ticket_id) REFERENCES chill_ticket.ticket (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.person_history ADD CONSTRAINT FK_F29692463174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.ticket ADD CONSTRAINT FK_B0A5F7233174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.ticket ADD CONSTRAINT FK_B0A5F72365FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP SEQUENCE chill_ticket.addressee_history_id_seq CASCADE');
|
||||
$this->addSql('DROP SEQUENCE chill_ticket.comment_id_seq CASCADE');
|
||||
$this->addSql('DROP SEQUENCE chill_ticket.input_history_id_seq CASCADE');
|
||||
$this->addSql('DROP SEQUENCE chill_ticket.motive_id_seq CASCADE');
|
||||
$this->addSql('DROP SEQUENCE chill_ticket.motives_history_id_seq CASCADE');
|
||||
$this->addSql('DROP SEQUENCE chill_ticket.person_history_id_seq CASCADE');
|
||||
$this->addSql('DROP SEQUENCE chill_ticket.ticket_id_seq CASCADE');
|
||||
$this->addSql('ALTER TABLE chill_ticket.addressee_history DROP CONSTRAINT FK_434EBDBD4D06F00C');
|
||||
$this->addSql('ALTER TABLE chill_ticket.addressee_history DROP CONSTRAINT FK_434EBDBD776D9A84');
|
||||
$this->addSql('ALTER TABLE chill_ticket.addressee_history DROP CONSTRAINT FK_434EBDBD700047D2');
|
||||
$this->addSql('ALTER TABLE chill_ticket.addressee_history DROP CONSTRAINT FK_434EBDBD3174800F');
|
||||
$this->addSql('ALTER TABLE chill_ticket.addressee_history DROP CONSTRAINT FK_434EBDBD65FF1AEC');
|
||||
$this->addSql('ALTER TABLE chill_ticket.comment DROP CONSTRAINT FK_79EBD416700047D2');
|
||||
$this->addSql('ALTER TABLE chill_ticket.comment DROP CONSTRAINT FK_79EBD4163174800F');
|
||||
$this->addSql('ALTER TABLE chill_ticket.comment DROP CONSTRAINT FK_79EBD41665FF1AEC');
|
||||
$this->addSql('ALTER TABLE chill_ticket.input_history DROP CONSTRAINT FK_E2AA301F217BBB47');
|
||||
$this->addSql('ALTER TABLE chill_ticket.input_history DROP CONSTRAINT FK_E2AA301F3EA5CAB0');
|
||||
$this->addSql('ALTER TABLE chill_ticket.input_history DROP CONSTRAINT FK_E2AA301FB8346CCF');
|
||||
$this->addSql('ALTER TABLE chill_ticket.input_history DROP CONSTRAINT FK_E2AA301F700047D2');
|
||||
$this->addSql('ALTER TABLE chill_ticket.motives_history DROP CONSTRAINT FK_48995CFF9658649C');
|
||||
$this->addSql('ALTER TABLE chill_ticket.motives_history DROP CONSTRAINT FK_48995CFF700047D2');
|
||||
$this->addSql('ALTER TABLE chill_ticket.motives_history DROP CONSTRAINT FK_48995CFF3174800F');
|
||||
$this->addSql('ALTER TABLE chill_ticket.person_history DROP CONSTRAINT FK_F2969246B8346CCF');
|
||||
$this->addSql('ALTER TABLE chill_ticket.person_history DROP CONSTRAINT FK_F2969246217BBB47');
|
||||
$this->addSql('ALTER TABLE chill_ticket.person_history DROP CONSTRAINT FK_F2969246700047D2');
|
||||
$this->addSql('ALTER TABLE chill_ticket.person_history DROP CONSTRAINT FK_F29692463174800F');
|
||||
$this->addSql('ALTER TABLE chill_ticket.ticket DROP CONSTRAINT FK_B0A5F7233174800F');
|
||||
$this->addSql('ALTER TABLE chill_ticket.ticket DROP CONSTRAINT FK_B0A5F72365FF1AEC');
|
||||
$this->addSql('DROP TABLE chill_ticket.addressee_history');
|
||||
$this->addSql('DROP TABLE chill_ticket.comment');
|
||||
$this->addSql('DROP TABLE chill_ticket.input_history');
|
||||
$this->addSql('DROP TABLE chill_ticket.motive');
|
||||
$this->addSql('DROP TABLE chill_ticket.motives_history');
|
||||
$this->addSql('DROP TABLE chill_ticket.person_history');
|
||||
$this->addSql('DROP TABLE chill_ticket.ticket');
|
||||
$this->addSql('DROP SCHEMA chill_ticket CASCADE');
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<?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\PersonBundle\Repository\PersonACLAwareRepositoryInterface;
|
||||
use Chill\TicketBundle\Action\Ticket\AssociateByPhonenumberCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\AssociateByPhonenumberCommandHandler;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class AssociateByPhonenumberCommandHandlerTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private function getHandler(
|
||||
PersonACLAwareRepositoryInterface $personACLAwareRepository,
|
||||
): AssociateByPhonenumberCommandHandler {
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
|
||||
return new AssociateByPhonenumberCommandHandler(
|
||||
$personACLAwareRepository,
|
||||
PhoneNumberUtil::getInstance(),
|
||||
new MockClock(),
|
||||
$entityManager->reveal()
|
||||
);
|
||||
}
|
||||
|
||||
public function testHandleWithPersonFoundByPhonenumber(): void
|
||||
{
|
||||
$person = new Person();
|
||||
|
||||
$personAclAwareRepository = $this->prophesize(PersonACLAwareRepositoryInterface::class);
|
||||
$personAclAwareRepository->findByPhone(Argument::any())->willReturn([$person]);
|
||||
|
||||
$handler = $this->getHandler($personAclAwareRepository->reveal());
|
||||
|
||||
$ticket = new Ticket();
|
||||
$handler($ticket, new AssociateByPhonenumberCommand('+3281136917'));
|
||||
|
||||
self::assertSame($person, $ticket->getPersons()[0]);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
<?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\TicketBundle\Action\Ticket\CreateTicketCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\CreateTicketCommandHandler;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class CreateTicketCommandHandlerTest extends TestCase
|
||||
{
|
||||
private function getHandler(): CreateTicketCommandHandler
|
||||
{
|
||||
return new CreateTicketCommandHandler();
|
||||
}
|
||||
|
||||
public function testHandleWithoutReference(): void
|
||||
{
|
||||
$command = new CreateTicketCommand();
|
||||
$actual = ($this->getHandler())($command);
|
||||
|
||||
self::assertInstanceOf(Ticket::class, $actual);
|
||||
self::assertEquals('', $actual->getExternalRef());
|
||||
}
|
||||
|
||||
public function testHandleWithReference(): void
|
||||
{
|
||||
$command = new CreateTicketCommand($ref = 'external-ref');
|
||||
$actual = ($this->getHandler())($command);
|
||||
|
||||
self::assertInstanceOf(Ticket::class, $actual);
|
||||
self::assertEquals($ref, $actual->getExternalRef());
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
<?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\TicketBundle\Action\Ticket\Handler\ReplaceMotiveCommandHandler;
|
||||
use Chill\TicketBundle\Action\Ticket\ReplaceMotiveCommand;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
final class ReplaceMotiveCommandHandlerTest extends KernelTestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private function buildHandler(
|
||||
EntityManagerInterface $entityManager,
|
||||
): ReplaceMotiveCommandHandler {
|
||||
$clock = new MockClock();
|
||||
|
||||
return new ReplaceMotiveCommandHandler($clock, $entityManager);
|
||||
}
|
||||
|
||||
public function testHandleOnTicketWithoutMotive(): void
|
||||
{
|
||||
$motive = new Motive();
|
||||
$ticket = new Ticket();
|
||||
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->persist(Argument::that(static function ($arg) use ($motive): bool {
|
||||
if (!$arg instanceof MotiveHistory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $arg->getMotive() === $motive;
|
||||
}))->shouldBeCalled();
|
||||
|
||||
$handler = $this->buildHandler($entityManager->reveal());
|
||||
|
||||
$handler->handle($ticket, new ReplaceMotiveCommand($motive));
|
||||
|
||||
self::assertSame($motive, $ticket->getMotive());
|
||||
}
|
||||
|
||||
public function testHandleReplaceMotiveOnTicketWithExistingMotive(): void
|
||||
{
|
||||
$motive = new Motive();
|
||||
$ticket = new Ticket();
|
||||
$history = new MotiveHistory(new Motive(), $ticket);
|
||||
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->persist(Argument::that(static function ($arg) use ($motive): bool {
|
||||
if (!$arg instanceof MotiveHistory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $arg->getMotive() === $motive;
|
||||
}))->shouldBeCalled();
|
||||
|
||||
$handler = $this->buildHandler($entityManager->reveal());
|
||||
|
||||
$handler->handle($ticket, new ReplaceMotiveCommand($motive));
|
||||
|
||||
self::assertSame($motive, $ticket->getMotive());
|
||||
self::assertCount(2, $ticket->getMotiveHistories());
|
||||
}
|
||||
|
||||
public function testHandleReplaceMotiveOnTicketWithSameMotive(): void
|
||||
{
|
||||
$motive = new Motive();
|
||||
$ticket = new Ticket();
|
||||
$history = new MotiveHistory($motive, $ticket);
|
||||
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->persist(Argument::that(static function ($arg) use ($motive): bool {
|
||||
if (!$arg instanceof MotiveHistory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $arg->getMotive() === $motive;
|
||||
}))->shouldNotBeCalled();
|
||||
|
||||
$handler = $this->buildHandler($entityManager->reveal());
|
||||
|
||||
$handler->handle($ticket, new ReplaceMotiveCommand($motive));
|
||||
|
||||
self::assertSame($motive, $ticket->getMotive());
|
||||
self::assertCount(1, $ticket->getMotiveHistories());
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
<?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\TicketBundle\Action\Ticket\Handler\ReplaceMotiveCommandHandler;
|
||||
use Chill\TicketBundle\Controller\ReplaceMotiveController;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class ReplaceMotiveControllerTest extends KernelTestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private SerializerInterface $serializer;
|
||||
|
||||
private ValidatorInterface $validator;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->serializer = self::getContainer()->get(SerializerInterface::class);
|
||||
$this->validator = self::getContainer()->get(ValidatorInterface::class);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
self::ensureKernelShutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateMotiveId
|
||||
*/
|
||||
public function testAddValidMotive(int $motiveId): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$payload = <<<JSON
|
||||
{"motive": {"type": "ticket_motive", "id": {$motiveId}}}
|
||||
JSON;
|
||||
|
||||
$request = new Request(content: $payload);
|
||||
|
||||
$controller = $this->buildController();
|
||||
|
||||
$response = $controller($ticket, $request);
|
||||
|
||||
self::assertEquals(201, $response->getStatusCode());
|
||||
}
|
||||
|
||||
private function buildController(): ReplaceMotiveController
|
||||
{
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->persist(Argument::type(MotiveHistory::class))->shouldBeCalled();
|
||||
$entityManager->flush()->shouldBeCalled();
|
||||
|
||||
$handler = new ReplaceMotiveCommandHandler(
|
||||
new MockClock(),
|
||||
$entityManager->reveal()
|
||||
);
|
||||
|
||||
return new ReplaceMotiveController(
|
||||
$security->reveal(),
|
||||
$handler,
|
||||
$this->serializer,
|
||||
$this->validator,
|
||||
$entityManager->reveal(),
|
||||
);
|
||||
}
|
||||
|
||||
public static function generateMotiveId(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||
|
||||
$motive = $em->createQuery('SELECT m FROM '.Motive::class.' m ')
|
||||
->setMaxResults(1)
|
||||
->getOneOrNullResult();
|
||||
|
||||
if (null === $motive) {
|
||||
throw new \RuntimeException('the motive table seems to be empty');
|
||||
}
|
||||
|
||||
self::ensureKernelShutdown();
|
||||
|
||||
yield [$motive->getId()];
|
||||
}
|
||||
}
|
61
src/Bundle/ChillTicketBundle/tests/Entity/TicketTest.php
Normal file
61
src/Bundle/ChillTicketBundle/tests/Entity/TicketTest.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?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\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\PersonHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class TicketTest extends KernelTestCase
|
||||
{
|
||||
public function testGetMotive(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$motive = new Motive();
|
||||
|
||||
self::assertNull($ticket->getMotive());
|
||||
|
||||
$history = new MotiveHistory($motive, $ticket);
|
||||
|
||||
self::assertSame($motive, $ticket->getMotive());
|
||||
self::assertCount(1, $ticket->getMotiveHistories());
|
||||
|
||||
// replace motive
|
||||
$motive2 = new Motive();
|
||||
$history->setEndDate(new \DateTimeImmutable());
|
||||
$history2 = new MotiveHistory($motive2, $ticket);
|
||||
|
||||
self::assertCount(2, $ticket->getMotiveHistories());
|
||||
self::assertSame($motive2, $ticket->getMotive());
|
||||
}
|
||||
|
||||
public function testGetPerson(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$person = new Person();
|
||||
|
||||
self::assertEquals([], $ticket->getPersons());
|
||||
|
||||
$history = new PersonHistory($person, $ticket, new \DateTimeImmutable('now'));
|
||||
|
||||
self::assertCount(1, $ticket->getPersons());
|
||||
self::assertSame($person, $ticket->getPersons()[0]);
|
||||
}
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
<?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\Serializer\Normalizer;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\PersonHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Serializer\Normalizer\TicketNormalizer;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class TicketNormalizerTest extends KernelTestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @dataProvider provideTickets
|
||||
*/
|
||||
public function testNormalize(Ticket $ticket, array $expected): void
|
||||
{
|
||||
$actual = $this->buildNormalizer()->normalize($ticket, 'json', ['groups' => 'read']);
|
||||
|
||||
self::assertEqualsCanonicalizing(array_keys($expected), array_keys($actual));
|
||||
|
||||
foreach (array_keys($expected) as $k) {
|
||||
if ('history' === $k) {
|
||||
continue;
|
||||
}
|
||||
self::assertEqualsCanonicalizing($expected[$k], $actual[$k], sprintf("assert the content of the '%s' key", $k));
|
||||
}
|
||||
|
||||
self::assertArrayHasKey('history', $actual);
|
||||
self::assertIsArray($actual['history']);
|
||||
|
||||
foreach ($actual['history'] as $k => $eventType) {
|
||||
self::assertEquals($expected['history'][$k]['event_type'], $eventType['event_type']);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildNormalizer(): TicketNormalizer
|
||||
{
|
||||
$normalizer = $this->prophesize(NormalizerInterface::class);
|
||||
|
||||
// empty array
|
||||
$normalizer->normalize(
|
||||
Argument::that(fn ($arg) => is_array($arg) && 0 === count($arg)),
|
||||
'json',
|
||||
Argument::type('array')
|
||||
)->willReturn([]);
|
||||
|
||||
// array of mixed objects
|
||||
$normalizer->normalize(
|
||||
Argument::that(fn ($arg) => is_array($arg) && 0 < count($arg) && is_object($arg[0])),
|
||||
'json',
|
||||
Argument::type('array')
|
||||
)->will(function ($args) {
|
||||
return array_fill(0, count($args[0]), 'embedded');
|
||||
});
|
||||
|
||||
// array of event type
|
||||
$normalizer->normalize(
|
||||
Argument::that(fn ($arg) => is_array($arg) && 0 < count($arg) && is_array($arg[0]) && array_key_exists('event_type', $arg[0])),
|
||||
'json',
|
||||
Argument::type('array')
|
||||
)->will(function ($args): array {
|
||||
$events = [];
|
||||
|
||||
foreach ($args[0] as $event) {
|
||||
$events[] = $event['event_type'];
|
||||
}
|
||||
|
||||
return $events;
|
||||
});
|
||||
|
||||
// datetime
|
||||
$normalizer->normalize(Argument::type(\DateTimeImmutable::class), 'json', Argument::type('array'))
|
||||
->will(function ($args) { return $args[0]->getTimestamp(); });
|
||||
$normalizer->normalize(Argument::type(Motive::class), 'json', Argument::type('array'))->willReturn(['type' => 'motive', 'id' => 0]);
|
||||
$normalizer->normalize(Argument::type(PersonHistory::class), 'json', Argument::type('array'))
|
||||
->willReturn(['personHistory']);
|
||||
$normalizer->normalize(Argument::type(MotiveHistory::class), 'json', Argument::type('array'))
|
||||
->willReturn(['motiveHistory']);
|
||||
$normalizer->normalize(null, 'json', Argument::type('array'))->willReturn(null);
|
||||
|
||||
$ticketNormalizer = new TicketNormalizer();
|
||||
$ticketNormalizer->setNormalizer($normalizer->reveal());
|
||||
|
||||
return $ticketNormalizer;
|
||||
}
|
||||
|
||||
public static function provideTickets(): iterable
|
||||
{
|
||||
yield [
|
||||
new Ticket(),
|
||||
[
|
||||
'type' => 'ticket_ticket',
|
||||
'id' => null,
|
||||
'externalRef' => '',
|
||||
'currentPersons' => [],
|
||||
'currentAddressees' => [],
|
||||
'currentInputs' => [],
|
||||
'currentMotive' => null,
|
||||
'history' => [],
|
||||
],
|
||||
];
|
||||
|
||||
$ticket = new Ticket();
|
||||
$ticket->setExternalRef('2134');
|
||||
$personHistory = new PersonHistory(new Person(), $ticket, new \DateTimeImmutable('2024-04-01T12:00:00'));
|
||||
$ticketHistory = new MotiveHistory(new Motive(), $ticket, new \DateTimeImmutable('2024-04-01T12:02:00'));
|
||||
|
||||
yield [
|
||||
$ticket,
|
||||
[
|
||||
'type' => 'ticket_ticket',
|
||||
'id' => null,
|
||||
'externalRef' => '2134',
|
||||
'currentPersons' => ['embedded'],
|
||||
'currentAddressees' => [],
|
||||
'currentInputs' => [],
|
||||
'currentMotive' => ['type' => 'motive', 'id' => 0],
|
||||
'history' => [['event_type' => 'add_person'], ['event_type' => 'set_motive']],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && (!isset($env
|
||||
(new Dotenv(false))->populate($env);
|
||||
} else {
|
||||
// load all the .env files
|
||||
(new Dotenv(false))->loadEnv(dirname(__DIR__).'/../../.env.test');
|
||||
(new Dotenv(false))->loadEnv(dirname(__DIR__).'/../../.env');
|
||||
}
|
||||
|
||||
$_SERVER += $_ENV;
|
||||
|
@ -44,5 +44,5 @@ return [
|
||||
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
||||
\Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
|
||||
Chill\EventBundle\ChillEventBundle::class => ['all' => true],
|
||||
|
||||
Chill\TicketBundle\ChillTicketBundle::class => ['all' => true],
|
||||
];
|
||||
|
@ -14,6 +14,7 @@ doctrine_migrations:
|
||||
'Chill\Migrations\DocGenerator': '@ChillDocGeneratorBundle/migrations'
|
||||
'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations'
|
||||
'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations'
|
||||
'Chill\Migrations\Ticket': '@ChillTicketBundle/migrations'
|
||||
|
||||
all_or_nothing:
|
||||
true
|
||||
|
Loading…
x
Reference in New Issue
Block a user