mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 09:18: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:
		| @@ -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,35 +68,37 @@ | ||||
|                     </div> | ||||
|                 {% endif %} | ||||
|  | ||||
|                 {% block content %} | ||||
|                     <div class="col-8 main_search"> | ||||
|                         {% if app.user.isAbsent %} | ||||
|                             <div class="d-flex flex-row mb-5 alert alert-warning" role="alert"> | ||||
|                                 <p class="m-2">{{'absence.You are marked as being absent'|trans }}</p> | ||||
|                                 <span class="ms-auto"> | ||||
|                                     <a class="btn btn-remove" title="Modifier" href="{{ path('chill_main_user_absence_index') }}">{{ 'absence.Unset absence'|trans }}</a> | ||||
|                                 </span> | ||||
|                             </div> | ||||
|                         {% endif %} | ||||
|                         <h2>{{ 'Search'|trans }}</h2> | ||||
|                 {% block wrapping_content %} | ||||
|                     {% block content %} | ||||
|                         <div class="col-8 main_search"> | ||||
|                             {% if app.user.isAbsent %} | ||||
|                                 <div class="d-flex flex-row mb-5 alert alert-warning" role="alert"> | ||||
|                                     <p class="m-2">{{'absence.You are marked as being absent'|trans }}</p> | ||||
|                                     <span class="ms-auto"> | ||||
|                                         <a class="btn btn-remove" title="Modifier" href="{{ path('chill_main_user_absence_index') }}">{{ 'absence.Unset absence'|trans }}</a> | ||||
|                                     </span> | ||||
|                                 </div> | ||||
|                             {% endif %} | ||||
|                             <h2>{{ 'Search'|trans }}</h2> | ||||
|  | ||||
|                         <form action="{{ path('chill_main_search') }}" method="get"> | ||||
|                             <input class="form-control form-control-lg" name="q" type="search" placeholder="{{ 'Search persons, ...'|trans }}" /> | ||||
|                             <div class="text-center"> | ||||
|                                 <button type="submit" class="btn btn-lg btn-warning mt-3"> | ||||
|                                     <i class="fa fa-fw fa-search"></i> {{ 'Search'|trans }} | ||||
|                                 </button> | ||||
|                                 <a class="btn btn-lg btn-misc mt-3" href="{{ path('chill_main_advanced_search_list') }}"> | ||||
|                                     <i class="fa fa-fw fa-search"></i> {{ 'Advanced search'|trans }} | ||||
|                                 </a> | ||||
|                             </div> | ||||
|                         </form> | ||||
|                     </div> | ||||
|                             <form action="{{ path('chill_main_search') }}" method="get"> | ||||
|                                 <input class="form-control form-control-lg" name="q" type="search" placeholder="{{ 'Search persons, ...'|trans }}" /> | ||||
|                                 <div class="text-center"> | ||||
|                                     <button type="submit" class="btn btn-lg btn-warning mt-3"> | ||||
|                                         <i class="fa fa-fw fa-search"></i> {{ 'Search'|trans }} | ||||
|                                     </button> | ||||
|                                     <a class="btn btn-lg btn-misc mt-3" href="{{ path('chill_main_advanced_search_list') }}"> | ||||
|                                         <i class="fa fa-fw fa-search"></i> {{ 'Advanced search'|trans }} | ||||
|                                     </a> | ||||
|                                 </div> | ||||
|                             </form> | ||||
|                         </div> | ||||
|  | ||||
|                     {#  DISABLED {{  chill_widget('homepage', {} ) }} #} | ||||
|                         {#  DISABLED {{  chill_widget('homepage', {} ) }} #} | ||||
|  | ||||
|                     {% include '@ChillMain/Homepage/index.html.twig' %} | ||||
|                         {% include '@ChillMain/Homepage/index.html.twig' %} | ||||
|  | ||||
|                     {% endblock %} | ||||
|                 {% endblock %} | ||||
|  | ||||
|             </div> | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user