mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-30 10:29:42 +00:00
Compare commits
2 Commits
ticket/WP1
...
ticket/scr
Author | SHA1 | Date | |
---|---|---|---|
4f51ef81ad
|
|||
4637dc692c
|
8
resources/ticket_motives_import/README.md
Normal file
8
resources/ticket_motives_import/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
In this directory, you find an example of file for the command `chill:main:ticket_motives_import`.
|
||||||
|
|
||||||
|
This file contains a list of ticket motives to import into the system. Each entry is a dictionary with two keys: `code` and `label`. The `code` key contains the unique code for the ticket motive, and the `label` key contains the human-readable label for the ticket motive.
|
||||||
|
|
||||||
|
The `stored_objects` key contains the documents that will be associated with the tickets. They must be found in the same directory.
|
||||||
|
|
||||||
|
The command `chill:main:ticket_motives_import` uses this file to import the specified ticket motives into the system.
|
||||||
|
|
136
resources/ticket_motives_import/motives.yaml
Normal file
136
resources/ticket_motives_import/motives.yaml
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
- label:
|
||||||
|
fr: Appel famille pour annonce de décès
|
||||||
|
urgent: false
|
||||||
|
supplementary_informations:
|
||||||
|
- label:
|
||||||
|
fr: Date du décès
|
||||||
|
- label:
|
||||||
|
fr: lieu du décès (domicile ou hôpital)
|
||||||
|
- label:
|
||||||
|
fr: nom de l’hôpital
|
||||||
|
- label:
|
||||||
|
fr: service concerné
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 2_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 3_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 4_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 'Appel famille pour annonce absence : hospitalisation ou consultation'
|
||||||
|
urgent: false
|
||||||
|
supplementary_informations:
|
||||||
|
- label:
|
||||||
|
fr: Quel hôpital
|
||||||
|
- label:
|
||||||
|
fr: quel service
|
||||||
|
- label:
|
||||||
|
fr: pour quelles raisons
|
||||||
|
- label:
|
||||||
|
fr: 'consultation : date et heure'
|
||||||
|
- label:
|
||||||
|
fr: hospitalisation complète ou HDJ
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 5_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 6_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 7_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 'Appel famille pour annonce absence : interruption de prise en charge'
|
||||||
|
urgent: false
|
||||||
|
supplementary_informations:
|
||||||
|
- label:
|
||||||
|
fr: Pour quelles raisons ? Date
|
||||||
|
- label:
|
||||||
|
fr: durée
|
||||||
|
- label:
|
||||||
|
fr: accord médical ?
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 8_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 9_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 10_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 'Appel famille pour annonce absence : changement d’adresse'
|
||||||
|
urgent: false
|
||||||
|
supplementary_informations:
|
||||||
|
- label:
|
||||||
|
fr: Où
|
||||||
|
- label:
|
||||||
|
fr: Pourquoi ? Pour combien de temps ? Besoin d’un relais des soins ? Nouvelle adresse ?
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 11_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 12_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 13_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: Appel famille pour altération de l’état général du patient
|
||||||
|
urgent: true
|
||||||
|
supplementary_informations:
|
||||||
|
- label:
|
||||||
|
fr: Recherche des symptômes
|
||||||
|
- label:
|
||||||
|
fr: Attentes par rapport à la demande
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 14_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 15_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 16_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: Appel famille pour prise en charge de la douleur
|
||||||
|
urgent: true
|
||||||
|
supplementary_informations:
|
||||||
|
- label:
|
||||||
|
fr: Localisation douleur
|
||||||
|
- label:
|
||||||
|
fr: Horaire dernier passage
|
||||||
|
- label:
|
||||||
|
fr: Traitements en cours
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 17_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 18_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 19_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: Appel famille pour information sur la date de prise en charge
|
||||||
|
urgent: false
|
||||||
|
supplementary_informations: []
|
||||||
|
stored_objects:
|
||||||
|
- label:
|
||||||
|
fr: ☀️ De 07h à 21h
|
||||||
|
filename: 20_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🌙 De 21h à 07h du matin
|
||||||
|
filename: 21_doc_20250402_Pelotons flux externes consolidés.pdf
|
||||||
|
- label:
|
||||||
|
fr: 🗓️ Dimanches et jours fériés
|
||||||
|
filename: 22_doc_20250402_Pelotons flux externes consolidés.pdf
|
6
resources/translation_override/README.md
Normal file
6
resources/translation_override/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
In this directory, you find an example of file for the command `chill:main:override_translation`.
|
||||||
|
|
||||||
|
This file contains a list of translations to override in the translation catalogue. Each entry is a dictionary with two keys: `from` and `to`. The `from` key contains the original translation string, and the `to` key contains the replacement string.
|
||||||
|
|
||||||
|
The command `chill:main:override_translation` uses this file to generate a new translation catalogue with the specified overrides applied.
|
||||||
|
|
8
resources/translation_override/overrides.yaml
Normal file
8
resources/translation_override/overrides.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
- {from: "de l'usager", to: "du patient"}
|
||||||
|
- {from: "l'usager", to: "le patient"}
|
||||||
|
- {from: "L'usager", to: "Le patient"}
|
||||||
|
- {from: "d'usagers", to: "de patients"}
|
||||||
|
- {from: "usagers", to: "patients"}
|
||||||
|
- {from: "Usagers", to: "Patients"}
|
||||||
|
- {from: "usager", to: "patient"}
|
||||||
|
- {from: "Usager", to: "Patient"}
|
@@ -0,0 +1,115 @@
|
|||||||
|
<?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\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
use Symfony\Component\Translation\MessageCatalogue;
|
||||||
|
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
|
||||||
|
use Symfony\Component\Translation\Writer\TranslationWriterInterface;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class OverrideTranslationCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly TranslationReaderInterface $reader,
|
||||||
|
private readonly TranslationWriterInterface $writer,
|
||||||
|
) {
|
||||||
|
$this->setName('chill:main:override_translation');
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setDescription('Generate a translation catalogue with translation remplacements based on replacements provided in a YAML file.')
|
||||||
|
->addArgument('locale', InputArgument::REQUIRED, 'The locale to process (e.g. fr, en).')
|
||||||
|
->addArgument('overrides', InputArgument::REQUIRED, 'Path to the overrides YAML file (list of {from, to}).');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$locale = (string) $input->getArgument('locale');
|
||||||
|
$overridesPath = (string) $input->getArgument('overrides');
|
||||||
|
|
||||||
|
$catalogue = $this->loadCatalogue($locale);
|
||||||
|
$overrides = $this->loadOverrides($overridesPath);
|
||||||
|
|
||||||
|
|
||||||
|
$toOverrideCatalogue = new MessageCatalogue($locale);
|
||||||
|
foreach ($catalogue->getDomains() as $domain) {
|
||||||
|
// hack: we have to replace the suffix ".intl-icu" by "+intl-ic"
|
||||||
|
$domain = str_replace('.intl-icu', '+intl-icu', $domain);
|
||||||
|
foreach ($catalogue->all($domain) as $key => $translation) {
|
||||||
|
foreach ($overrides as $changes) {
|
||||||
|
$from = $changes['from'];
|
||||||
|
$to = $changes['to'];
|
||||||
|
|
||||||
|
if (is_string($translation) && str_contains($translation, $from)) {
|
||||||
|
$newTranslation = strtr($translation, [$from => $to]);
|
||||||
|
$toOverrideCatalogue->set($key, $newTranslation, $domain);
|
||||||
|
$translation = $newTranslation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var KernelInterface $kernel */
|
||||||
|
/* @phpstan-ignore-next-line */
|
||||||
|
$kernel = $this->getApplication()->getKernel();
|
||||||
|
$outputDir = rtrim($kernel->getProjectDir(), '/').'/translations';
|
||||||
|
if (!is_dir($outputDir)) {
|
||||||
|
@mkdir($outputDir, 0775, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer expects the 'path' option to be a directory; it will create the proper file name
|
||||||
|
$this->writer->write($toOverrideCatalogue, 'yaml', ['path' => $outputDir]);
|
||||||
|
|
||||||
|
$output->writeln(sprintf('Override catalogue written to %s (domain: messages, locale: %s).', $outputDir, $locale));
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<array{from: string, to: string}>
|
||||||
|
*/
|
||||||
|
private function loadOverrides(string $path): array
|
||||||
|
{
|
||||||
|
return Yaml::parseFile($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadCatalogue(string $locale): MessageCatalogue
|
||||||
|
{
|
||||||
|
/** @var KernelInterface $kernel */
|
||||||
|
/* @phpstan-ignore-next-line */
|
||||||
|
$kernel = $this->getApplication()->getKernel();
|
||||||
|
|
||||||
|
// collect path for translations
|
||||||
|
$transPaths = [];
|
||||||
|
foreach ($kernel->getBundles() as $bundle) {
|
||||||
|
$bundleDir = $bundle->getPath();
|
||||||
|
$transPaths[] = is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundle->getPath().'/translations';
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentCatalogue = new MessageCatalogue($locale);
|
||||||
|
foreach ($transPaths as $path) {
|
||||||
|
if (is_dir($path)) {
|
||||||
|
$this->reader->read($path, $currentCatalogue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $currentCatalogue;
|
||||||
|
}
|
||||||
|
}
|
@@ -42,38 +42,26 @@ services:
|
|||||||
- { name: console.command }
|
- { name: console.command }
|
||||||
|
|
||||||
Chill\MainBundle\Command\LoadAddressesFRFromBANOCommand:
|
Chill\MainBundle\Command\LoadAddressesFRFromBANOCommand:
|
||||||
autoconfigure: true
|
|
||||||
autowire: true
|
|
||||||
tags:
|
tags:
|
||||||
- { name: console.command }
|
- { name: console.command }
|
||||||
|
|
||||||
Chill\MainBundle\Command\LoadAddressesFRFromBANCommand:
|
Chill\MainBundle\Command\LoadAddressesFRFromBANCommand:
|
||||||
autoconfigure: true
|
|
||||||
autowire: true
|
|
||||||
tags:
|
tags:
|
||||||
- { name: console.command }
|
- { name: console.command }
|
||||||
|
|
||||||
Chill\MainBundle\Command\LoadAddressesBEFromBestAddressCommand:
|
Chill\MainBundle\Command\LoadAddressesBEFromBestAddressCommand:
|
||||||
autoconfigure: true
|
|
||||||
autowire: true
|
|
||||||
tags:
|
tags:
|
||||||
- { name: console.command }
|
- { name: console.command }
|
||||||
|
|
||||||
Chill\MainBundle\Command\LoadPostalCodeFR:
|
Chill\MainBundle\Command\LoadPostalCodeFR:
|
||||||
autoconfigure: true
|
|
||||||
autowire: true
|
|
||||||
tags:
|
tags:
|
||||||
- { name: console.command }
|
- { name: console.command }
|
||||||
|
|
||||||
Chill\MainBundle\Command\LoadAddressesLUFromBDAddressCommand:
|
Chill\MainBundle\Command\LoadAddressesLUFromBDAddressCommand:
|
||||||
autoconfigure: true
|
|
||||||
autowire: true
|
|
||||||
tags:
|
tags:
|
||||||
- { name: console.command }
|
- { name: console.command }
|
||||||
|
|
||||||
Chill\MainBundle\Command\ExecuteCronJobCommand:
|
Chill\MainBundle\Command\ExecuteCronJobCommand:
|
||||||
autoconfigure: true
|
|
||||||
autowire: true
|
|
||||||
tags:
|
tags:
|
||||||
- {name: console.command }
|
- {name: console.command }
|
||||||
|
|
||||||
@@ -81,6 +69,6 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- {name: console.command}
|
- {name: console.command}
|
||||||
|
|
||||||
Chill\MainBundle\Command\DumpListPermissionsCommand:
|
Chill\MainBundle\Command\DumpListPermissionsCommand: ~
|
||||||
autoconfigure: true
|
|
||||||
autowire: true
|
Chill\MainBundle\Command\OverrideTranslationCommand: ~
|
||||||
|
@@ -112,7 +112,7 @@ paths:
|
|||||||
- no
|
- no
|
||||||
- name: byMotives
|
- name: byMotives
|
||||||
in: query
|
in: query
|
||||||
description: the motives of the ticket. All the descendants of the motive are taken into account.
|
description: the motives of the ticket
|
||||||
required: false
|
required: false
|
||||||
style: form
|
style: form
|
||||||
explode: false
|
explode: false
|
||||||
|
@@ -13,7 +13,6 @@ namespace Chill\TicketBundle\Controller;
|
|||||||
|
|
||||||
use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectNormalizer;
|
use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectNormalizer;
|
||||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||||
use Chill\TicketBundle\Serializer\Normalizer\MotiveNormalizer;
|
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
@@ -22,13 +21,13 @@ final class MotiveApiController extends ApiController
|
|||||||
protected function customizeQuery(string $action, Request $request, $query): void
|
protected function customizeQuery(string $action, Request $request, $query): void
|
||||||
{
|
{
|
||||||
/* @var $query QueryBuilder */
|
/* @var $query QueryBuilder */
|
||||||
$query->andWhere('e.active = TRUE')->andWhere('e.parent IS NULL');
|
$query->andWhere('e.active = TRUE');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getContextForSerialization(string $action, Request $request, string $_format, $entity): array
|
protected function getContextForSerialization(string $action, Request $request, string $_format, $entity): array
|
||||||
{
|
{
|
||||||
return match ($request->getMethod()) {
|
return match ($request->getMethod()) {
|
||||||
Request::METHOD_GET => ['groups' => ['read', 'read:extended', StoredObjectNormalizer::DOWNLOAD_LINK_ONLY, MotiveNormalizer::GROUP_PARENT_TO_CHILDREN]],
|
Request::METHOD_GET => ['groups' => ['read', StoredObjectNormalizer::DOWNLOAD_LINK_ONLY]],
|
||||||
default => parent::getContextForSerialization($action, $request, $_format, $entity),
|
default => parent::getContextForSerialization($action, $request, $_format, $entity),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,7 @@ namespace Chill\TicketBundle\DataFixtures\ORM;
|
|||||||
|
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||||
|
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||||
use Chill\TicketBundle\Entity\Motive;
|
use Chill\TicketBundle\Entity\Motive;
|
||||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||||
use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface;
|
use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface;
|
||||||
@@ -34,7 +35,6 @@ final class LoadMotives extends Fixture implements FixtureGroupInterface
|
|||||||
['label' => '☀️ De 07h à 21h', 'path' => __DIR__.'/docs/peloton_2.pdf'],
|
['label' => '☀️ De 07h à 21h', 'path' => __DIR__.'/docs/peloton_2.pdf'],
|
||||||
['label' => 'Dimanche et jours fériés', 'path' => __DIR__.'/docs/schema_1.png'],
|
['label' => 'Dimanche et jours fériés', 'path' => __DIR__.'/docs/schema_1.png'],
|
||||||
];
|
];
|
||||||
$motivesByLabel = [];
|
|
||||||
|
|
||||||
foreach (explode("\n", self::MOTIVES) as $row) {
|
foreach (explode("\n", self::MOTIVES) as $row) {
|
||||||
if ('' === trim($row)) {
|
if ('' === trim($row)) {
|
||||||
@@ -46,29 +46,14 @@ final class LoadMotives extends Fixture implements FixtureGroupInterface
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$labels = explode(' > ', $data[0]);
|
|
||||||
$parent = null;
|
|
||||||
|
|
||||||
while (count($labels) > 0) {
|
|
||||||
$label = array_shift($labels);
|
|
||||||
dump($labels);
|
|
||||||
if (isset($motivesByLabel[$label])) {
|
|
||||||
$motive = $motivesByLabel[$label];
|
|
||||||
} else {
|
|
||||||
$motive = new Motive();
|
$motive = new Motive();
|
||||||
$motive->setLabel(['fr' => $label]);
|
$motive->setLabel(['fr' => trim((string) $data[0])]);
|
||||||
$motivesByLabel[$label] = $motive;
|
$motive->setMakeTicketEmergency(match ($data[1]) {
|
||||||
}
|
'true' => EmergencyStatusEnum::YES,
|
||||||
|
'false' => EmergencyStatusEnum::NO,
|
||||||
|
default => throw new \UnexpectedValueException('Unexpected value'),
|
||||||
|
});
|
||||||
|
|
||||||
if (null !== $parent) {
|
|
||||||
$motive->setParent($parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
$manager->persist($motive);
|
|
||||||
$parent = $motive;
|
|
||||||
|
|
||||||
if (0 === count($labels)) {
|
|
||||||
// this is the last one, we add data
|
|
||||||
$numberOfDocs = (int) $data[2];
|
$numberOfDocs = (int) $data[2];
|
||||||
for ($i = 1; $i <= $numberOfDocs; ++$i) {
|
for ($i = 1; $i <= $numberOfDocs; ++$i) {
|
||||||
$doc = $docs[$i - 1];
|
$doc = $docs[$i - 1];
|
||||||
@@ -93,18 +78,18 @@ final class LoadMotives extends Fixture implements FixtureGroupInterface
|
|||||||
$motive->addSupplementaryComment(['label' => trim((string) $supplementaryComment)]);
|
$motive->addSupplementaryComment(['label' => trim((string) $supplementaryComment)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
$manager->persist($motive);
|
||||||
}
|
}
|
||||||
$manager->flush();
|
$manager->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
private const MOTIVES = <<<'CSV'
|
private const MOTIVES = <<<'CSV'
|
||||||
"Motif administratif > Coordonnées",false,"3","Nouvelles coordonnées",
|
"Coordonnées",false,"3","Nouvelles coordonnées",
|
||||||
"Organisation > Horaire de passage",false,"0",
|
"Horaire de passage",false,"0",
|
||||||
"Organisation > Livraison > Retard de livraison",false,"0",
|
"Retard de livraison",false,"0",
|
||||||
"Organisation > Livraison > Erreur de livraison",false,"0",
|
"Erreur de livraison",false,"0",
|
||||||
"Organisation > Livraison > Colis incomplet",false,"0",
|
"Colis incomplet",false,"0",
|
||||||
"MATLOC",false,"0",
|
"MATLOC",false,"0",
|
||||||
"Retard DASRI",false,"1",
|
"Retard DASRI",false,"1",
|
||||||
"Planning d'astreintes",false,"0",
|
"Planning d'astreintes",false,"0",
|
||||||
@@ -131,7 +116,7 @@ final class LoadMotives extends Fixture implements FixtureGroupInterface
|
|||||||
"Mauvaise adresse",false,"0",
|
"Mauvaise adresse",false,"0",
|
||||||
"Patient absent",false,"0",
|
"Patient absent",false,"0",
|
||||||
"Annulation",false,"0",
|
"Annulation",false,"0",
|
||||||
"Organisation > Livraison > Colis perdu",false,"0",
|
"Colis perdu",false,"0",
|
||||||
"Changement de rendez-vous",false,"0",
|
"Changement de rendez-vous",false,"0",
|
||||||
"Coordination interservices",false,"0",
|
"Coordination interservices",false,"0",
|
||||||
"Problème de substitution produits",true,"0",
|
"Problème de substitution produits",true,"0",
|
||||||
|
@@ -15,7 +15,6 @@ use Chill\DocStoreBundle\Entity\StoredObject;
|
|||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\Common\Collections\ReadableCollection;
|
use Doctrine\Common\Collections\ReadableCollection;
|
||||||
use Doctrine\Common\Collections\Selectable;
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
|
||||||
@@ -27,15 +26,19 @@ class Motive
|
|||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: false)]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: false)]
|
||||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]'])]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]'])]
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
private array $label = [];
|
private array $label = [];
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, nullable: false, options: ['default' => true])]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, nullable: false, options: ['default' => true])]
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
private bool $active = true;
|
private bool $active = true;
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, nullable: true, enumType: EmergencyStatusEnum::class)]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, nullable: true, enumType: EmergencyStatusEnum::class)]
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
private ?EmergencyStatusEnum $makeTicketEmergency = null;
|
private ?EmergencyStatusEnum $makeTicketEmergency = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,22 +49,12 @@ class Motive
|
|||||||
private Collection $storedObjects;
|
private Collection $storedObjects;
|
||||||
|
|
||||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['jsonb' => true, 'default' => '[]'])]
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['jsonb' => true, 'default' => '[]'])]
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
private array $supplementaryComments = [];
|
private array $supplementaryComments = [];
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: Motive::class, inversedBy: 'children')]
|
|
||||||
private ?Motive $parent = null;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Collection<int, Motive>&Selectable<int, Motive>
|
|
||||||
*/
|
|
||||||
#[ORM\OneToMany(targetEntity: Motive::class, mappedBy: 'parent')]
|
|
||||||
private Collection&Selectable $children;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->storedObjects = new ArrayCollection();
|
$this->storedObjects = new ArrayCollection();
|
||||||
$this->children = new ArrayCollection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addStoredObject(StoredObject $storedObject): void
|
public function addStoredObject(StoredObject $storedObject): void
|
||||||
@@ -76,6 +69,7 @@ class Motive
|
|||||||
$this->storedObjects->removeElement($storedObject);
|
$this->storedObjects->removeElement($storedObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
public function getStoredObjects(): ReadableCollection
|
public function getStoredObjects(): ReadableCollection
|
||||||
{
|
{
|
||||||
return $this->storedObjects;
|
return $this->storedObjects;
|
||||||
@@ -148,74 +142,4 @@ class Motive
|
|||||||
$this->supplementaryComments[$key] = $supplementaryComment;
|
$this->supplementaryComments[$key] = $supplementaryComment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isParent(): bool
|
|
||||||
{
|
|
||||||
return $this->children->count() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isChild(): bool
|
|
||||||
{
|
|
||||||
return null !== $this->parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setParent(?Motive $parent): void
|
|
||||||
{
|
|
||||||
if (null !== $parent) {
|
|
||||||
$parent->addChild($this);
|
|
||||||
} else {
|
|
||||||
$this->parent->removeChild($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->parent = $parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal use @see{setParent} instead
|
|
||||||
*/
|
|
||||||
public function addChild(Motive $child): void
|
|
||||||
{
|
|
||||||
if (!$this->children->contains($child)) {
|
|
||||||
$this->children->add($child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal use @see{setParent} with null as argument instead
|
|
||||||
*/
|
|
||||||
public function removeChild(Motive $child): void
|
|
||||||
{
|
|
||||||
$this->children->removeElement($child);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getChildren(): ReadableCollection&Selectable
|
|
||||||
{
|
|
||||||
return $this->children;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getParent(): ?Motive
|
|
||||||
{
|
|
||||||
return $this->parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the descendants of the current entity.
|
|
||||||
*
|
|
||||||
* This method collects all descendant entities recursively, starting from the current entity
|
|
||||||
* and including all of its children and their descendants.
|
|
||||||
*
|
|
||||||
* @return ReadableCollection&Selectable A collection containing the current entity and all its descendants
|
|
||||||
*/
|
|
||||||
public function getDescendants(): ReadableCollection&Selectable
|
|
||||||
{
|
|
||||||
$collection = new ArrayCollection([$this]);
|
|
||||||
|
|
||||||
foreach ($this->getChildren() as $child) {
|
|
||||||
foreach ($child->getDescendants() as $descendant) {
|
|
||||||
$collection->add($descendant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $collection;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -42,7 +42,7 @@ class MotiveHistory implements TrackCreationInterface
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
#[ORM\ManyToOne(targetEntity: Motive::class)]
|
#[ORM\ManyToOne(targetEntity: Motive::class)]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
#[Serializer\Groups(['read', 'read:children-to-parent'])]
|
#[Serializer\Groups(['read'])]
|
||||||
private Motive $motive,
|
private Motive $motive,
|
||||||
#[ORM\ManyToOne(targetEntity: Ticket::class)]
|
#[ORM\ManyToOne(targetEntity: Ticket::class)]
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
@@ -113,11 +113,10 @@ final readonly class TicketACLAwareRepository implements TicketACLAwareRepositor
|
|||||||
if (array_key_exists('byMotives', $params)) {
|
if (array_key_exists('byMotives', $params)) {
|
||||||
$byMotives = $qb->expr()->orX();
|
$byMotives = $qb->expr()->orX();
|
||||||
foreach ($params['byMotives'] as $motive) {
|
foreach ($params['byMotives'] as $motive) {
|
||||||
$motivesWithDescendants = $motive->getDescendants()->toArray();
|
|
||||||
$byMotives->add(
|
$byMotives->add(
|
||||||
$qb->expr()->exists(sprintf(
|
$qb->expr()->exists(sprintf(
|
||||||
'SELECT 1 FROM %s tp_motive_%d WHERE tp_motive_%d.ticket = t
|
'SELECT 1 FROM %s tp_motive_%d WHERE tp_motive_%d.ticket = t
|
||||||
AND tp_motive_%d.motive IN (:motives_%d) AND tp_motive_%d.endDate IS NULL
|
AND tp_motive_%d.motive = :motive_%d AND tp_motive_%d.endDate IS NULL
|
||||||
',
|
',
|
||||||
MotiveHistory::class,
|
MotiveHistory::class,
|
||||||
++$i,
|
++$i,
|
||||||
@@ -127,7 +126,7 @@ final readonly class TicketACLAwareRepository implements TicketACLAwareRepositor
|
|||||||
$i,
|
$i,
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
$qb->setParameter(sprintf('motives_%d', $i), $motivesWithDescendants);
|
$qb->setParameter(sprintf('motive_%d', $i), $motive);
|
||||||
}
|
}
|
||||||
$qb->andWhere($byMotives);
|
$qb->andWhere($byMotives);
|
||||||
}
|
}
|
||||||
|
@@ -8,32 +8,14 @@ import { Person } from "ChillPersonAssets/types";
|
|||||||
import { Thirdparty } from "../../../../ChillThirdPartyBundle/Resources/public/types";
|
import { Thirdparty } from "../../../../ChillThirdPartyBundle/Resources/public/types";
|
||||||
import { StoredObject } from "ChillDocStoreAssets/types";
|
import { StoredObject } from "ChillDocStoreAssets/types";
|
||||||
|
|
||||||
interface MotiveBase {
|
export interface Motive {
|
||||||
type: "ticket_motive";
|
type: "ticket_motive";
|
||||||
id: number;
|
id: number;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
label: TranslatableString;
|
label: TranslatableString;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represent a motive with basic information and parent motive.
|
|
||||||
*
|
|
||||||
* Match the "read" and "read:children-to-parent" serializer groups.
|
|
||||||
*/
|
|
||||||
export interface MotiveWithParent extends MotiveBase {
|
|
||||||
parent: MotiveWithParent|null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a motive for a ticket, including details like emergency status, stored objects, and supplementary comments.
|
|
||||||
*
|
|
||||||
* Match the "read:extended" serializer group in MotiveNormalizer.
|
|
||||||
*/
|
|
||||||
export interface Motive extends MotiveBase {
|
|
||||||
makeTicketEmergency: TicketEmergencyState;
|
makeTicketEmergency: TicketEmergencyState;
|
||||||
storedObjects: StoredObject[];
|
storedObjects: StoredObject[];
|
||||||
supplementaryComments: { label: string }[];
|
supplementaryComments: { label: string }[];
|
||||||
children: Motive[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TicketState = "open" | "closed" | "close";
|
export type TicketState = "open" | "closed" | "close";
|
||||||
@@ -63,7 +45,7 @@ export interface MotiveHistory {
|
|||||||
id: number;
|
id: number;
|
||||||
startDate: null;
|
startDate: null;
|
||||||
endDate: null | DateTime;
|
endDate: null | DateTime;
|
||||||
motive: MotiveWithParent;
|
motive: Motive;
|
||||||
createdBy: User | null;
|
createdBy: User | null;
|
||||||
createdAt: DateTime | null;
|
createdAt: DateTime | null;
|
||||||
}
|
}
|
||||||
@@ -158,7 +140,7 @@ interface BaseTicket<
|
|||||||
createdAt: DateTime | null;
|
createdAt: DateTime | null;
|
||||||
currentAddressees: UserGroupOrUser[];
|
currentAddressees: UserGroupOrUser[];
|
||||||
currentPersons: Person[];
|
currentPersons: Person[];
|
||||||
currentMotive: null | MotiveWithParent;
|
currentMotive: null | Motive;
|
||||||
currentState: TicketState | null;
|
currentState: TicketState | null;
|
||||||
emergency: TicketEmergencyState | null;
|
emergency: TicketEmergencyState | null;
|
||||||
caller: Person | Thirdparty | null;
|
caller: Person | Thirdparty | null;
|
||||||
|
@@ -1,103 +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\Serializer\Normalizer;
|
|
||||||
|
|
||||||
use Chill\TicketBundle\Entity\Motive;
|
|
||||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalizes a Motive object into an array format, supporting different serialization groups
|
|
||||||
* to customize the output depending on the context.
|
|
||||||
*
|
|
||||||
* There are several serialization groups available:
|
|
||||||
* - 'read': Basic information about the motive.
|
|
||||||
* - 'read:extended': Includes additional details like stored objects and supplementary comments.
|
|
||||||
* - 'read:parent-to-children': Normalizes children recursively without exposing parent to avoid cycles.
|
|
||||||
* - 'read:children-to-parent': Normalizes parent recursively without exposing children to avoid cycles.
|
|
||||||
*/
|
|
||||||
final class MotiveNormalizer implements NormalizerInterface, NormalizerAwareInterface
|
|
||||||
{
|
|
||||||
use NormalizerAwareTrait;
|
|
||||||
|
|
||||||
public const GROUP_PARENT_TO_CHILDREN = 'read:parent-to-children';
|
|
||||||
public const GROUP_CHILDREN_TO_PARENT = 'read:children-to-parent';
|
|
||||||
|
|
||||||
public function normalize($object, ?string $format = null, array $context = []): array
|
|
||||||
{
|
|
||||||
if (!$object instanceof Motive) {
|
|
||||||
throw new UnexpectedValueException('Expected instance of '.Motive::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
$groups = $context[AbstractNormalizer::GROUPS] ?? [];
|
|
||||||
if (is_string($groups)) {
|
|
||||||
$groups = [$groups];
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [];
|
|
||||||
|
|
||||||
if (in_array('read', $groups, true) || in_array('read:extended', $groups, true)) {
|
|
||||||
// Build base representation
|
|
||||||
$data = [
|
|
||||||
'type' => 'ticket_motive',
|
|
||||||
'id' => $object->getId(),
|
|
||||||
'label' => $object->getLabel(),
|
|
||||||
'active' => $object->isActive(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_array('read:extended', $groups, true)) {
|
|
||||||
$data['makeTicketEmergency'] = $object->getMakeTicketEmergency();
|
|
||||||
$data['supplementaryComments'] = $object->getSupplementaryComments();
|
|
||||||
// Normalize stored objects (delegated to their own normalizer when present)
|
|
||||||
$storedObjects = [];
|
|
||||||
foreach ($object->getStoredObjects() as $storedObject) {
|
|
||||||
$storedObjects[] = $this->normalizer->normalize($storedObject, $format, $context);
|
|
||||||
}
|
|
||||||
$data['storedObjects'] = $storedObjects;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_array(self::GROUP_PARENT_TO_CHILDREN, $groups, true)) {
|
|
||||||
// Normalize children recursively (but we do not expose parent to avoid cycles)
|
|
||||||
$children = [];
|
|
||||||
foreach ($object->getChildren() as $child) {
|
|
||||||
$children[] = $this->normalizer->normalize($child, $format, $context);
|
|
||||||
}
|
|
||||||
$data['children'] = $children;
|
|
||||||
} elseif (in_array(self::GROUP_CHILDREN_TO_PARENT, $groups, true)) {
|
|
||||||
$data['parent'] = $this->normalizer->normalize($object->getParent(), $format, $context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
|
|
||||||
{
|
|
||||||
return $data instanceof Motive;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optimization hint for the Serializer (available since Symfony 5.3+).
|
|
||||||
*
|
|
||||||
* @return array<class-string, bool>
|
|
||||||
*/
|
|
||||||
public function getSupportedTypes(?string $format): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
Motive::class => true,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@@ -51,7 +51,7 @@ final class TicketNormalizer implements NormalizerInterface, NormalizerAwareInte
|
|||||||
]),
|
]),
|
||||||
'currentAddressees' => $this->normalizer->normalize($object->getCurrentAddressee(), $format, ['groups' => 'read']),
|
'currentAddressees' => $this->normalizer->normalize($object->getCurrentAddressee(), $format, ['groups' => 'read']),
|
||||||
'currentInputs' => $this->normalizer->normalize($object->getCurrentInputs(), $format, ['groups' => 'read']),
|
'currentInputs' => $this->normalizer->normalize($object->getCurrentInputs(), $format, ['groups' => 'read']),
|
||||||
'currentMotive' => $this->normalizer->normalize($object->getMotive(), $format, ['groups' => ['read', MotiveNormalizer::GROUP_CHILDREN_TO_PARENT]]),
|
'currentMotive' => $this->normalizer->normalize($object->getMotive(), $format, ['groups' => 'read']),
|
||||||
'currentState' => $object->getState()?->value ?? 'open',
|
'currentState' => $object->getState()?->value ?? 'open',
|
||||||
'emergency' => $object->getEmergencyStatus()?->value ?? 'no',
|
'emergency' => $object->getEmergencyStatus()?->value ?? 'no',
|
||||||
'caller' => $this->normalizer->normalize($object->getCaller(), $format, ['groups' => 'read']),
|
'caller' => $this->normalizer->normalize($object->getCaller(), $format, ['groups' => 'read']),
|
||||||
|
@@ -1,37 +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\Migrations\Ticket;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
final class Version20250924124214 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return 'Add parent to motive';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$this->addSql('ALTER TABLE chill_ticket.motive ADD parent_id INT DEFAULT NULL');
|
|
||||||
$this->addSql('ALTER TABLE chill_ticket.motive ADD CONSTRAINT FK_DE298BF8727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_ticket.motive (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
|
||||||
$this->addSql('CREATE INDEX IDX_DE298BF8727ACA70 ON chill_ticket.motive (parent_id)');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$this->addSql('ALTER TABLE chill_ticket.motive DROP CONSTRAINT FK_DE298BF8727ACA70');
|
|
||||||
$this->addSql('DROP INDEX chill_ticket.IDX_DE298BF8727ACA70');
|
|
||||||
$this->addSql('ALTER TABLE chill_ticket.motive DROP parent_id');
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,63 +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\Tests\Entity;
|
|
||||||
|
|
||||||
use Chill\TicketBundle\Entity\Motive;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @covers \Chill\TicketBundle\Entity\Motive::getWithDescendants
|
|
||||||
*/
|
|
||||||
final class MotiveTest extends TestCase
|
|
||||||
{
|
|
||||||
public function testGetDescendantsOnLeafReturnsSelfOnly(): void
|
|
||||||
{
|
|
||||||
$leaf = new Motive();
|
|
||||||
$leaf->setLabel(['fr' => 'Feuille']);
|
|
||||||
|
|
||||||
$collection = $leaf->getDescendants();
|
|
||||||
|
|
||||||
self::assertCount(1, $collection);
|
|
||||||
self::assertSame($leaf, $collection->first());
|
|
||||||
self::assertContains($leaf, $collection->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testGetWithDescendantsReturnsSelfAndAllDescendants(): void
|
|
||||||
{
|
|
||||||
$parent = new Motive();
|
|
||||||
$parent->setLabel(['fr' => 'Parent']);
|
|
||||||
|
|
||||||
$childA = new Motive();
|
|
||||||
$childA->setLabel(['fr' => 'Enfant A']);
|
|
||||||
$childA->setParent($parent);
|
|
||||||
|
|
||||||
$childB = new Motive();
|
|
||||||
$childB->setLabel(['fr' => 'Enfant B']);
|
|
||||||
$childB->setParent($parent);
|
|
||||||
|
|
||||||
$grandChildA1 = new Motive();
|
|
||||||
$grandChildA1->setLabel(['fr' => 'Petit-enfant A1']);
|
|
||||||
$grandChildA1->setParent($childA);
|
|
||||||
|
|
||||||
$descendants = $parent->getDescendants();
|
|
||||||
$asArray = $descendants->toArray();
|
|
||||||
|
|
||||||
// It should contain the parent itself, both children and the grand child
|
|
||||||
self::assertCount(4, $descendants, 'Expected parent + 2 children + 1 grandchild');
|
|
||||||
self::assertContains($parent, $asArray);
|
|
||||||
self::assertContains($childA, $asArray);
|
|
||||||
self::assertContains($childB, $asArray);
|
|
||||||
self::assertContains($grandChildA1, $asArray);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,154 +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\Tests\Serializer\Normalizer;
|
|
||||||
|
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
|
||||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
|
||||||
use Chill\TicketBundle\Entity\Motive;
|
|
||||||
use Chill\TicketBundle\Serializer\Normalizer\MotiveNormalizer;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @covers \Chill\TicketBundle\Serializer\Normalizer\MotiveNormalizer
|
|
||||||
*/
|
|
||||||
final class MotiveNormalizerTest extends TestCase
|
|
||||||
{
|
|
||||||
public function testNormalizeReadBasic(): void
|
|
||||||
{
|
|
||||||
$motive = new Motive();
|
|
||||||
$motive->setLabel(['fr' => 'Logement', 'en' => 'Housing']);
|
|
||||||
// active is true by default
|
|
||||||
|
|
||||||
$normalizer = new MotiveNormalizer();
|
|
||||||
$normalizer->setNormalizer($this->buildDummyNormalizer());
|
|
||||||
|
|
||||||
$actual = $normalizer->normalize($motive, 'json', ['groups' => 'read']);
|
|
||||||
|
|
||||||
self::assertSame('ticket_motive', $actual['type']);
|
|
||||||
self::assertNull($actual['id']);
|
|
||||||
self::assertSame(['fr' => 'Logement', 'en' => 'Housing'], $actual['label']);
|
|
||||||
self::assertTrue($actual['active']);
|
|
||||||
// no extended fields here
|
|
||||||
self::assertArrayNotHasKey('makeTicketEmergency', $actual);
|
|
||||||
self::assertArrayNotHasKey('supplementaryComments', $actual);
|
|
||||||
self::assertArrayNotHasKey('storedObjects', $actual);
|
|
||||||
self::assertArrayNotHasKey('children', $actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testNormalizeExtended(): void
|
|
||||||
{
|
|
||||||
$motive = new Motive();
|
|
||||||
$motive->setLabel(['fr' => 'Financier']);
|
|
||||||
$motive->setMakeTicketEmergency(EmergencyStatusEnum::YES);
|
|
||||||
$motive->addSupplementaryComment(['label' => 'Justifier le revenu']);
|
|
||||||
$motive->addStoredObject(new StoredObject('pending'));
|
|
||||||
|
|
||||||
$normalizer = new MotiveNormalizer();
|
|
||||||
$normalizer->setNormalizer($this->buildDummyNormalizer());
|
|
||||||
|
|
||||||
$actual = $normalizer->normalize($motive, 'json', ['groups' => ['read', 'read:extended']]);
|
|
||||||
|
|
||||||
self::assertSame('ticket_motive', $actual['type']);
|
|
||||||
self::assertSame(['fr' => 'Financier'], $actual['label']);
|
|
||||||
self::assertSame(EmergencyStatusEnum::YES, $actual['makeTicketEmergency']);
|
|
||||||
self::assertSame([
|
|
||||||
['label' => 'Justifier le revenu'],
|
|
||||||
], $actual['supplementaryComments']);
|
|
||||||
self::assertSame([
|
|
||||||
['stored_object'],
|
|
||||||
], $actual['storedObjects']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testNormalizeParentToChildren(): void
|
|
||||||
{
|
|
||||||
$parent = new Motive();
|
|
||||||
$parent->setLabel(['fr' => 'Parent']);
|
|
||||||
$child1 = new Motive();
|
|
||||||
$child1->setLabel(['fr' => 'Enfant 1']);
|
|
||||||
$child2 = new Motive();
|
|
||||||
$child2->setLabel(['fr' => 'Enfant 2']);
|
|
||||||
|
|
||||||
// build relation
|
|
||||||
$child1->setParent($parent);
|
|
||||||
$child2->setParent($parent);
|
|
||||||
|
|
||||||
$normalizer = new MotiveNormalizer();
|
|
||||||
$normalizer->setNormalizer($this->buildDummyNormalizer());
|
|
||||||
|
|
||||||
$actual = $normalizer->normalize($parent, 'json', ['groups' => [MotiveNormalizer::GROUP_PARENT_TO_CHILDREN]]);
|
|
||||||
|
|
||||||
// children must be normalized by the injected normalizer and parent not exposed
|
|
||||||
self::assertArrayHasKey('children', $actual);
|
|
||||||
self::assertSame([
|
|
||||||
['motive' => 'normalized'],
|
|
||||||
['motive' => 'normalized'],
|
|
||||||
], $actual['children']);
|
|
||||||
self::assertArrayNotHasKey('parent', $actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testNormalizeChildrenToParent(): void
|
|
||||||
{
|
|
||||||
$parent = new Motive();
|
|
||||||
$parent->setLabel(['fr' => 'Parent']);
|
|
||||||
$child = new Motive();
|
|
||||||
$child->setLabel(['fr' => 'Enfant']);
|
|
||||||
$child->setParent($parent);
|
|
||||||
|
|
||||||
$normalizer = new MotiveNormalizer();
|
|
||||||
$normalizer->setNormalizer($this->buildDummyNormalizer());
|
|
||||||
|
|
||||||
$actual = $normalizer->normalize($child, 'json', ['groups' => [MotiveNormalizer::GROUP_CHILDREN_TO_PARENT]]);
|
|
||||||
|
|
||||||
// parent must be normalized by the injected normalizer and children not exposed
|
|
||||||
self::assertArrayHasKey('parent', $actual);
|
|
||||||
self::assertSame(['motive' => 'normalized'], $actual['parent']);
|
|
||||||
self::assertArrayNotHasKey('children', $actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSupportsAndSupportedTypes(): void
|
|
||||||
{
|
|
||||||
$motive = new Motive();
|
|
||||||
$normalizer = new MotiveNormalizer();
|
|
||||||
|
|
||||||
self::assertTrue($normalizer->supportsNormalization($motive, 'json'));
|
|
||||||
self::assertFalse($normalizer->supportsNormalization(new \stdClass(), 'json'));
|
|
||||||
|
|
||||||
$supported = $normalizer->getSupportedTypes('json');
|
|
||||||
self::assertArrayHasKey(Motive::class, $supported);
|
|
||||||
self::assertTrue($supported[Motive::class]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function buildDummyNormalizer(): NormalizerInterface
|
|
||||||
{
|
|
||||||
return new class () implements NormalizerInterface {
|
|
||||||
public function normalize($object, ?string $format = null, array $context = []): array
|
|
||||||
{
|
|
||||||
if ($object instanceof StoredObject) {
|
|
||||||
return ['stored_object'];
|
|
||||||
}
|
|
||||||
if ($object instanceof Motive) {
|
|
||||||
return ['motive' => 'normalized'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return ['normalized'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function supportsNormalization($data, ?string $format = null): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user