From 33101e05f40489e3c7193c07628f31b77100b0ee Mon Sep 17 00:00:00 2001 From: Mathieu MARCHOIS Date: Thu, 10 Oct 2024 11:48:52 +0200 Subject: [PATCH] Regulation order visas (#997) --- .../SaveRegulationGeneralInfoCommand.xml | 14 ++ .../DuplicateRegulationCommandHandler.php | 2 + .../SaveRegulationGeneralInfoCommand.php | 4 + ...aveRegulationGeneralInfoCommandHandler.php | 4 + src/Domain/Regulation/RegulationOrder.php | 6 +- .../Form/Regulation/GeneralInfoFormType.php | 33 ++++ .../regulation/_general_info_form.html.twig | 143 +++++++++++++++--- .../AddRegulationControllerTest.php | 25 +-- ...aveRegulationGeneralInfoControllerTest.php | 73 ++++++--- .../DuplicateRegulationCommandHandlerTest.php | 12 ++ .../Domain/Regulation/RegulationOrderTest.php | 2 +- translations/messages.fr.xlf | 36 +++++ 12 files changed, 296 insertions(+), 58 deletions(-) diff --git a/config/validator/Application/Regulation/Command/SaveRegulationGeneralInfoCommand.xml b/config/validator/Application/Regulation/Command/SaveRegulationGeneralInfoCommand.xml index 298ee1f36..40bae2321 100644 --- a/config/validator/Application/Regulation/Command/SaveRegulationGeneralInfoCommand.xml +++ b/config/validator/Application/Regulation/Command/SaveRegulationGeneralInfoCommand.xml @@ -66,6 +66,20 @@ + + + + + + + + + + diff --git a/src/Application/Regulation/Command/DuplicateRegulationCommandHandler.php b/src/Application/Regulation/Command/DuplicateRegulationCommandHandler.php index 922bf881a..a065abd59 100644 --- a/src/Application/Regulation/Command/DuplicateRegulationCommandHandler.php +++ b/src/Application/Regulation/Command/DuplicateRegulationCommandHandler.php @@ -55,6 +55,8 @@ private function duplicateRegulationOrderRecord( $generalInfo->description = $originalRegulationOrder->getDescription(); $generalInfo->startDate = $originalRegulationOrder->getStartDate(); $generalInfo->endDate = $originalRegulationOrder->getEndDate(); + $generalInfo->additionalVisas = $originalRegulationOrder->getAdditionalVisas(); + $generalInfo->additionalReasons = $originalRegulationOrder->getAdditionalReasons(); return $this->commandBus->handle($generalInfo); } diff --git a/src/Application/Regulation/Command/SaveRegulationGeneralInfoCommand.php b/src/Application/Regulation/Command/SaveRegulationGeneralInfoCommand.php index a4b704d75..36b16fb98 100644 --- a/src/Application/Regulation/Command/SaveRegulationGeneralInfoCommand.php +++ b/src/Application/Regulation/Command/SaveRegulationGeneralInfoCommand.php @@ -20,6 +20,8 @@ final class SaveRegulationGeneralInfoCommand implements CommandInterface public ?Organization $organization; public ?\DateTimeInterface $startDate; public ?\DateTimeInterface $endDate = null; + public array $additionalVisas = []; + public array $additionalReasons = []; public function __construct( public readonly ?RegulationOrderRecord $regulationOrderRecord = null, @@ -40,6 +42,8 @@ public static function create( $command->description = $regulationOrder?->getDescription(); $command->startDate = $startDate ?? $regulationOrder?->getStartDate(); $command->endDate = $regulationOrder?->getEndDate(); + $command->additionalVisas = $regulationOrder?->getAdditionalVisas() ?? []; + $command->additionalReasons = $regulationOrder?->getAdditionalReasons() ?? []; return $command; } diff --git a/src/Application/Regulation/Command/SaveRegulationGeneralInfoCommandHandler.php b/src/Application/Regulation/Command/SaveRegulationGeneralInfoCommandHandler.php index dd6bdd0be..f02ed7e6c 100644 --- a/src/Application/Regulation/Command/SaveRegulationGeneralInfoCommandHandler.php +++ b/src/Application/Regulation/Command/SaveRegulationGeneralInfoCommandHandler.php @@ -36,6 +36,8 @@ public function __invoke(SaveRegulationGeneralInfoCommand $command): RegulationO startDate: $command->startDate, endDate: $command->endDate, otherCategoryText: $command->otherCategoryText, + additionalVisas: $command->additionalVisas, + additionalReasons: $command->additionalReasons, ), ); @@ -61,6 +63,8 @@ public function __invoke(SaveRegulationGeneralInfoCommand $command): RegulationO startDate: $command->startDate, endDate: $command->endDate, otherCategoryText: $command->otherCategoryText, + additionalVisas: $command->additionalVisas, + additionalReasons: $command->additionalReasons, ); return $command->regulationOrderRecord; diff --git a/src/Domain/Regulation/RegulationOrder.php b/src/Domain/Regulation/RegulationOrder.php index e7b80ad73..d5ae628d9 100644 --- a/src/Domain/Regulation/RegulationOrder.php +++ b/src/Domain/Regulation/RegulationOrder.php @@ -95,7 +95,7 @@ public function getAdditionalVisas(): ?array return $this->additionalVisas; } - public function getaAdditionalReasons(): ?array + public function getAdditionalReasons(): ?array { return $this->additionalReasons; } @@ -107,6 +107,8 @@ public function update( \DateTimeInterface $startDate, ?\DateTimeInterface $endDate = null, ?string $otherCategoryText = null, + array $additionalVisas = [], + array $additionalReasons = [], ): void { $this->identifier = $identifier; $this->category = $category; @@ -114,5 +116,7 @@ public function update( $this->startDate = $startDate; $this->endDate = $endDate; $this->otherCategoryText = $otherCategoryText; + $this->additionalVisas = $additionalVisas; + $this->additionalReasons = $additionalReasons; } } diff --git a/src/Infrastructure/Form/Regulation/GeneralInfoFormType.php b/src/Infrastructure/Form/Regulation/GeneralInfoFormType.php index 51979490b..763244b87 100644 --- a/src/Infrastructure/Form/Regulation/GeneralInfoFormType.php +++ b/src/Infrastructure/Form/Regulation/GeneralInfoFormType.php @@ -11,6 +11,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\CollectionType; use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; @@ -93,6 +94,38 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'help' => 'regulation.general_info.description.help', ], ) + ->add( + 'additionalVisas', + CollectionType::class, + options: [ + 'entry_type' => TextareaType::class, + 'label' => null, + 'prototype_name' => '__visa_name__', + 'entry_options' => [ + 'label' => 'regulation.general_info.visa', + ], + 'allow_add' => true, + 'allow_delete' => true, + 'keep_as_list' => true, + 'error_bubbling' => false, + ], + ) + ->add( + 'additionalReasons', + CollectionType::class, + options: [ + 'entry_type' => TextareaType::class, + 'label' => null, + 'prototype_name' => '__reason_name__', + 'entry_options' => [ + 'label' => 'regulation.general_info.reason', + ], + 'allow_add' => true, + 'allow_delete' => true, + 'keep_as_list' => true, + 'error_bubbling' => false, + ], + ) ->add( 'save', SubmitType::class, diff --git a/templates/regulation/_general_info_form.html.twig b/templates/regulation/_general_info_form.html.twig index ee9949c10..df1cc69aa 100644 --- a/templates/regulation/_general_info_form.html.twig +++ b/templates/regulation/_general_info_form.html.twig @@ -1,33 +1,128 @@ +{% macro collection_item(form, index) %} +
  • +
    + {{ form_row(form, {group_class: 'fr-input-group', attr: {class: 'fr-input'}}) }} +
    +
    + +
    +
  • +{% endmacro %} +
    -
    - -

    - {{ form.description.vars.value|default('regulation.general_info'|trans)|u.truncate(36, '...', false) }} -

    -
    {{ form_start(form) }} - {{ form_row(form.identifier, {group_class: 'fr-input-group', widget_class: 'fr-input'}) }} - {{ form_row(form.organization, {group_class: 'fr-select-group', widget_class: 'fr-select'}) }} -
    -
    - {{ form_row(form.category, { - group_class: 'fr-select-group', - widget_class: 'fr-select', - attr: { - 'data-controller': 'condition', - 'data-condition-equals-value': 'other', - 'data-action': 'change->condition#dispatchFromInputChange condition:yes->form-reveal#open condition:no->form-reveal#close', - } - }) }} +
    +
      +
    • + +
    • +
    • + +
    • +
    +
    + {{ form_row(form.identifier, {group_class: 'fr-input-group', widget_class: 'fr-input'}) }} + {{ form_row(form.organization, {group_class: 'fr-select-group', widget_class: 'fr-select'}) }} +
    +
    + {{ form_row(form.category, { + group_class: 'fr-select-group', + widget_class: 'fr-select', + attr: { + 'data-controller': 'condition', + 'data-condition-equals-value': 'other', + 'data-action': 'change->condition#dispatchFromInputChange condition:yes->form-reveal#open condition:no->form-reveal#close', + } + }) }} +
    +
    + {{ form_row(form.otherCategoryText, { group_class: 'fr-input-group', widget_class: 'fr-input', attr: { 'data-form-reveal-target': 'form-control' } }) }} +
    +
    + {{ form_row(form.description, {group_class: 'fr-input-group', attr: {class: 'fr-input'}, help_attr: {class: 'fr-hint-text'}}) }} + {{ form_row(form.startDate, {group_class: 'fr-input-group', widget_class: 'fr-input', row_attr: {class: 'fr-col-12 fr-col-sm-6 fr-col-lg-5'}}) }} + {{ form_row(form.endDate, {group_class: 'fr-input-group', widget_class: 'fr-input', row_attr: {class: 'fr-col-12 fr-col-sm-6 fr-col-lg-5'}}) }}
    -
    - {{ form_row(form.otherCategoryText, { group_class: 'fr-input-group', widget_class: 'fr-input', attr: { 'data-form-reveal-target': 'form-control' } }) }} +
    +

    {{ 'regulation.general_info.visas_and_reasons.description'|trans }}

    +
    +

    {{ 'regulation.general_info.visas'|trans }}

    +

    {{ 'regulation.general_info.visas.help'|trans }}

    + {{ form_errors(form.additionalVisas) }} +
      + {% for item in form.additionalVisas %} + {{ _self.collection_item(item) }} + {% else %} + {% do form.additionalVisas.setRendered %} + {% endfor %} +
    + +
    +
    +

    {{ 'regulation.general_info.reasons'|trans }}

    +

    {{ 'regulation.general_info.reasons.help'|trans }}

    + {{ form_errors(form.additionalReasons) }} +
      + {% for item in form.additionalReasons %} + {{ _self.collection_item(item) }} + {% else %} + {% do form.additionalReasons.setRendered %} + {% endfor %} +
    + +
    - {{ form_row(form.description, {group_class: 'fr-input-group', attr: {class: 'fr-input'}, help_attr: {class: 'fr-hint-text'}}) }} - {{ form_row(form.startDate, {group_class: 'fr-input-group', widget_class: 'fr-input', row_attr: {class: 'fr-col-12 fr-col-sm-6 fr-col-lg-5'}}) }} - {{ form_row(form.endDate, {group_class: 'fr-input-group', widget_class: 'fr-input', row_attr: {class: 'fr-col-12 fr-col-sm-6 fr-col-lg-5'}}) }} {{ "common.cancel"|trans }} diff --git a/tests/Integration/Infrastructure/Controller/Regulation/AddRegulationControllerTest.php b/tests/Integration/Infrastructure/Controller/Regulation/AddRegulationControllerTest.php index 707f51b7e..1ba3742a1 100644 --- a/tests/Integration/Infrastructure/Controller/Regulation/AddRegulationControllerTest.php +++ b/tests/Integration/Infrastructure/Controller/Regulation/AddRegulationControllerTest.php @@ -23,7 +23,6 @@ public function testAdd(): void $this->assertSecurityHeaders(); $this->assertSame('Nouvel arrêté', $crawler->filter('h2')->text()); $this->assertMetaTitle('Nouvel arrêté - DiaLog', $crawler); - $this->assertSame('Informations générales', $crawler->filter('h3')->text()); $this->assertSame('Brouillon', $crawler->filter('[data-testid="status-badge"]')->text()); $this->assertSame('', $crawler->selectButton('Publier')->attr('disabled')); @@ -34,21 +33,29 @@ public function testAdd(): void $saveButton = $crawler->selectButton('Continuer'); $form = $saveButton->form(); $this->assertSame('2023-05-10', $form->get('general_info_form[startDate]')->getValue()); // Init with tomorrow date - $form['general_info_form[identifier]'] = 'F022023'; - $form['general_info_form[organization]'] = OrganizationFixture::MAIN_ORG_ID; - $form['general_info_form[description]'] = 'Interdiction de circuler dans Paris'; - $form['general_info_form[startDate]'] = '2023-02-14'; - $form['general_info_form[category]'] = RegulationOrderCategoryEnum::OTHER->value; - $form['general_info_form[otherCategoryText]'] = 'Trou en formation'; /** @var UserRepositoryInterface */ $userRepository = static::getContainer()->get(UserRepositoryInterface::class); $this->assertNull($userRepository->findOneByEmail($email)->getLastActiveAt()); - $client->submit($form); + + // Get the raw values. + $values = $form->getPhpValues(); + $values['general_info_form']['identifier'] = 'F022023'; + $values['general_info_form']['organization'] = OrganizationFixture::MAIN_ORG_ID; + $values['general_info_form']['description'] = 'Interdiction de circuler dans Paris'; + $values['general_info_form']['startDate'] = '2023-02-14'; + $values['general_info_form']['category'] = RegulationOrderCategoryEnum::OTHER->value; + $values['general_info_form']['otherCategoryText'] = 'Trou en formation'; + $values['general_info_form']['additionalVisas'][0] = 'Vu 1'; + $values['general_info_form']['additionalVisas'][1] = 'Vu 2'; + $values['general_info_form']['additionalReasons'][0] = 'Motif 1'; + $values['general_info_form']['additionalReasons'][1] = 'Motif 2'; + $crawler = $client->request($form->getMethod(), $form->getUri(), $values, $form->getPhpFiles()); + $this->assertResponseStatusCodeSame(303); $this->assertEquals(new \DateTimeImmutable('2023-06-09'), $userRepository->findOneByEmail($email)->getLastActiveAt()); - $client->followRedirect(); + $this->assertResponseStatusCodeSame(200); $this->assertRouteSame('app_regulation_detail'); } diff --git a/tests/Integration/Infrastructure/Controller/Regulation/Fragments/SaveRegulationGeneralInfoControllerTest.php b/tests/Integration/Infrastructure/Controller/Regulation/Fragments/SaveRegulationGeneralInfoControllerTest.php index 818245292..6dcb1712c 100644 --- a/tests/Integration/Infrastructure/Controller/Regulation/Fragments/SaveRegulationGeneralInfoControllerTest.php +++ b/tests/Integration/Infrastructure/Controller/Regulation/Fragments/SaveRegulationGeneralInfoControllerTest.php @@ -20,31 +20,30 @@ public function testEditWithAnAlreadyExistingIdentifier(): void $this->assertResponseStatusCodeSame(200); $this->assertSecurityHeaders(); - $this->assertSame('Description 3', $crawler->filter('h3')->text()); $saveButton = $crawler->selectButton('Valider'); $form = $saveButton->form(); $identifier = RegulationOrderFixture::TYPICAL_IDENTIFIER; - $form['general_info_form[identifier]'] = $identifier; - $form['general_info_form[organization]'] = OrganizationFixture::MAIN_ORG_ID; - $form['general_info_form[category]'] = RegulationOrderCategoryEnum::ROAD_MAINTENANCE->value; - $form['general_info_form[description]'] = 'Travaux'; - $form['general_info_form[startDate]'] = '2023-02-12'; - $form['general_info_form[endDate]'] = '2024-02-11'; - $crawler = $client->submit($form); + + // Get the raw values. + $values = $form->getPhpValues(); + $values['general_info_form']['identifier'] = $identifier; + $values['general_info_form']['organization'] = OrganizationFixture::MAIN_ORG_ID; + $values['general_info_form']['description'] = 'Interdiction de circuler dans Paris'; + $values['general_info_form']['startDate'] = '2023-02-12'; + $values['general_info_form']['endDate'] = '2024-02-11'; + $values['general_info_form']['category'] = RegulationOrderCategoryEnum::ROAD_MAINTENANCE->value; + $values['general_info_form']['otherCategoryText'] = 'Travaux'; + $values['general_info_form']['additionalVisas'][0] = 'Vu 1'; + $values['general_info_form']['additionalVisas'][1] = 'Vu 2'; + $values['general_info_form']['additionalReasons'][0] = 'Motif 1'; + $values['general_info_form']['additionalReasons'][1] = 'Motif 2'; + $crawler = $client->request($form->getMethod(), $form->getUri(), $values, $form->getPhpFiles()); + $this->assertResponseStatusCodeSame(422); $this->assertSame(\sprintf('Un arrêté avec l\'identifiant "%s" existe déjà. Veuillez saisir un autre identifiant.', $identifier), $crawler->filter('#general_info_form_identifier_error')->text()); } - public function testEditDescriptionTruncated(): void - { - $client = $this->login(); - $crawler = $client->request('GET', '/_fragment/regulations/general_info/form/' . RegulationOrderRecordFixture::UUID_LONG_DESCRIPTION); - $this->assertResponseStatusCodeSame(200); - $this->assertSecurityHeaders(); - $this->assertSame('Description 5 that is very long and...', $crawler->filter('h3')->text()); - } - public function testRegulationOrderRecordNotFound(): void { $client = $this->login(); @@ -62,12 +61,19 @@ public function testEditRegulationOrderWithNoStartDateYet(): void $saveButton = $crawler->selectButton('Valider'); $form = $saveButton->form(); - $form['general_info_form[identifier]'] = 'FIOIUS'; - $form['general_info_form[organization]'] = OrganizationFixture::OTHER_ORG_ID; - $form['general_info_form[description]'] = 'Interdiction de circuler dans Paris'; - $form['general_info_form[startDate]'] = '2023-02-14'; - $form['general_info_form[category]'] = RegulationOrderCategoryEnum::ROAD_MAINTENANCE->value; - $client->submit($form); + + // Get the raw values. + $values = $form->getPhpValues(); + $values['general_info_form']['identifier'] = 'FIOIUS'; + $values['general_info_form']['organization'] = OrganizationFixture::OTHER_ORG_ID; + $values['general_info_form']['description'] = 'Interdiction de circuler dans Paris'; + $values['general_info_form']['startDate'] = '2023-02-14'; + $values['general_info_form']['category'] = RegulationOrderCategoryEnum::ROAD_MAINTENANCE->value; + $values['general_info_form']['additionalVisas'][0] = 'Vu 1'; + $values['general_info_form']['additionalVisas'][1] = 'Vu 2'; + $values['general_info_form']['additionalReasons'][0] = 'Motif 1'; + $values['general_info_form']['additionalReasons'][1] = 'Motif 2'; + $crawler = $client->request($form->getMethod(), $form->getUri(), $values, $form->getPhpFiles()); $this->assertResponseStatusCodeSame(303); $client->followRedirect(); @@ -100,6 +106,27 @@ public function testFieldsTooLong(): void $this->assertSame('Cette chaîne est trop longue. Elle doit avoir au maximum 255 caractères.', $crawler->filter('#general_info_form_description_error')->text()); } + public function testEmptyReasonsAndVisas(): void + { + $client = $this->login(); + $crawler = $client->request('GET', '/_fragment/regulations/general_info/form/' . RegulationOrderRecordFixture::UUID_TYPICAL); + $this->assertResponseStatusCodeSame(200); + + $saveButton = $crawler->selectButton('Valider'); + $form = $saveButton->form(); + + // Get the raw values. + $values = $form->getPhpValues(); + $values['general_info_form']['additionalVisas'][0] = ''; + $values['general_info_form']['additionalReasons'][0] = ''; + + $crawler = $client->request($form->getMethod(), $form->getUri(), $values, $form->getPhpFiles()); + + $this->assertResponseStatusCodeSame(422); + $this->assertSame('Cette valeur ne doit pas être vide.', $crawler->filter('#general_info_form_additionalVisas_0_error')->text()); + $this->assertSame('Cette valeur ne doit pas être vide.', $crawler->filter('#general_info_form_additionalReasons_0_error')->text()); + } + public function testCannotAccessBecauseDifferentOrganization(): void { $client = $this->login(UserFixture::OTHER_ORG_USER_EMAIL); diff --git a/tests/Unit/Application/Regulation/Command/DuplicateRegulationCommandHandlerTest.php b/tests/Unit/Application/Regulation/Command/DuplicateRegulationCommandHandlerTest.php index af3ec2df1..f69d354c2 100644 --- a/tests/Unit/Application/Regulation/Command/DuplicateRegulationCommandHandlerTest.php +++ b/tests/Unit/Application/Regulation/Command/DuplicateRegulationCommandHandlerTest.php @@ -283,6 +283,16 @@ public function testRegulationFullyDuplicated(): void ->method('getEndDate') ->willReturn($endDate); + $this->originalRegulationOrder + ->expects(self::once()) + ->method('getAdditionalVisas') + ->willReturn(['Vu 1']); + + $this->originalRegulationOrder + ->expects(self::once()) + ->method('getAdditionalReasons') + ->willReturn(['Motif 1']); + $this->originalRegulationOrder ->expects(self::exactly(2)) ->method('getMeasures') @@ -308,6 +318,8 @@ public function testRegulationFullyDuplicated(): void $generalInfoCommand->startDate = $startDate; $generalInfoCommand->endDate = $endDate; $generalInfoCommand->organization = $originalOrganization; + $generalInfoCommand->additionalVisas = ['Vu 1']; + $generalInfoCommand->additionalReasons = ['Motif 1']; $vehicleSetCommand = new SaveVehicleSetCommand(); $vehicleSetCommand->allVehicles = true; diff --git a/tests/Unit/Domain/Regulation/RegulationOrderTest.php b/tests/Unit/Domain/Regulation/RegulationOrderTest.php index 90bf15cd0..f60cda99f 100644 --- a/tests/Unit/Domain/Regulation/RegulationOrderTest.php +++ b/tests/Unit/Domain/Regulation/RegulationOrderTest.php @@ -44,7 +44,7 @@ public function testGetters(): void $this->assertFalse($regulationOrder->isPermanent()); $this->assertSame($visaModel, $regulationOrder->getVisaModel()); $this->assertSame(['vu que 1'], $regulationOrder->getAdditionalVisas()); - $this->assertSame(['considérant que'], $regulationOrder->getaAdditionalReasons()); + $this->assertSame(['considérant que'], $regulationOrder->getAdditionalReasons()); } public function testUpdate(): void diff --git a/translations/messages.fr.xlf b/translations/messages.fr.xlf index 3af0d0840..2629c98ea 100644 --- a/translations/messages.fr.xlf +++ b/translations/messages.fr.xlf @@ -428,6 +428,42 @@ regulation.general_info Informations générales + + regulation.general_info.visas_and_reasons + Visas et motifs + + + regulation.general_info.visas + Visas + + + regulation.general_info.visas.help + Ajouter les visas que vous souhaitez voir dans votre arrêté. Par exemple, “Vu le code de la route...”. + + + regulation.general_info.reasons.help + Ajouter les motifs que vous souhaitez voir dans votre arrêté. Par exemple, “Considérant qu'en raison du déroulement des travaux de...”. + + + regulation.general_info.reasons.add + Ajouter un motif + + + regulation.general_info.visa + Visa (Vu ...) + + + regulation.general_info.reason + Motif (Considérant ...) + + + regulation.general_info.reasons + Motifs + + + regulation.general_info.visas_and_reasons.description + Vous pouvez exporter votre arrêté de circulation à partir de Dialog, pour cela vous devez spécifier les visas (Vu le ...) et les motifs (Considérant...). + regulation.general_info.identifier Identifiant