From 3d6745e535cd0e8708f3dde4f7df66c99e3529e4 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Mon, 28 Mar 2022 12:00:13 +0200 Subject: [PATCH 01/68] display of interlocuteurs changed to flex-table to prevent cut-off of information --- CHANGELOG.md | 1 + .../public/vuejs/AccompanyingCourse/components/Resources.vue | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56b958bb6..739a4a1f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ and this project adheres to * [parcours] Create document buttons made sticky (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/532) * [person] Trailing guillemet removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/530) * [notification] Display of social action within workflow notification set to display block (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/537) +* [parcours] Display of interlocuteurs changed to flex-table in parcours edit page to prevent cut-off of information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/535) ## Test releases diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue index e9b9695ab..7287437a3 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue @@ -10,7 +10,7 @@ -
+
Date: Mon, 28 Mar 2022 14:30:49 +0200 Subject: [PATCH 02/68] activity: add spacing between buttons + better alignment --- .../Resources/public/page/edit_activity/index.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Bundle/ChillActivityBundle/Resources/public/page/edit_activity/index.scss b/src/Bundle/ChillActivityBundle/Resources/public/page/edit_activity/index.scss index 16e66eadb..5d1ccf976 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/page/edit_activity/index.scss +++ b/src/Bundle/ChillActivityBundle/Resources/public/page/edit_activity/index.scss @@ -4,6 +4,13 @@ div.chill-dropzone__below-zone { } } +ul[data-collection-name="documents"] { + button.remove-entry { + margin: 0.5rem 0!important; + } +} + + // do it in js does not work // document.addEventListener('DOMContentLoaded', e => { // const dropzoneBelow = document.querySelectorAll('div.chill-dropzone__below-zone'); From 10fcd4f7329d543b65dc53b3468cffd3f623de92 Mon Sep 17 00:00:00 2001 From: nobohan Date: Mon, 28 Mar 2022 15:12:16 +0200 Subject: [PATCH 03/68] accompanying course: evaluation documents: align buttons --- .../components/FormEvaluation.vue | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue index eff62bbbb..5f4fdedec 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue @@ -138,12 +138,12 @@ @go-to-generate-document="submitBeforeGenerate" >
- -
    + +
    • From b361ab2d74f9cc4763f1915f2e945b19e911d254 Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 30 Mar 2022 11:10:23 +0200 Subject: [PATCH 04/68] workflow: add div for delete workflow form --- .../ChillMainBundle/Resources/views/Workflow/delete.html.twig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/delete.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/delete.html.twig index 64da4ea76..d11165fc6 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/delete.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/delete.html.twig @@ -3,6 +3,7 @@ {% block title 'workflow.Delete workflow ?'|trans %} {% block display_content %} +

      {{ handler.entityTitle(entityWorkflow) }}

      @@ -10,6 +11,7 @@ {% include handler.template(entityWorkflow) with handler.templateData(entityWorkflow)|merge({ 'display_action': false }) %} + {% endblock %} {% block content %} @@ -25,5 +27,5 @@ 'form' : delete_form } ) }}
      - +
{% endblock %} From f29ead4961a8201b3c80754bc9d7ad79fcec0b6e Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 30 Mar 2022 11:26:19 +0200 Subject: [PATCH 05/68] accompanying course: fix display bug in accompanying course resume --- .../_join_household.html.twig | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_join_household.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_join_household.html.twig index fda36da85..888ab8d43 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_join_household.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/_join_household.html.twig @@ -1,16 +1,19 @@
-
-
- +
+
+ + +

{{ 'Some peoples does not belong to any household currently. Add them to an household soon'|trans }}

- {{ 'Some peoples does not belong to any household currently. Add them to an household soon'|trans }} -
+
From df24d085ca9c4ca43c663d0f9a7858195d1240af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 11:53:06 +0200 Subject: [PATCH 06/68] Household members editor: leave household when repositionning to same household, not sharing household --- .../Household/MembersEditor.php | 6 +++ .../Tests/Household/MembersEditorTest.php | 41 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php index e327efbab..5ecf1c0bd 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -98,6 +98,12 @@ class MembersEditor $participation->setEndDate($membership->getStartDate()); } } + } else { + // if a members is moved to the same household than the one he belongs to, + // we should make it leave the household + if ($person->getCurrentHousehold($date) === $this->household) { + $this->leaveMovement($date, $person); + } } $this->membershipsAffected[] = $membership; diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php index 65199dabf..f1a622f5f 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Tests\Household; use Chill\PersonBundle\Entity\Household\Household; +use Chill\PersonBundle\Entity\Household\HouseholdMember; use Chill\PersonBundle\Entity\Household\Position; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent; @@ -39,6 +40,46 @@ final class MembersEditorTest extends TestCase $this->factory = $this->buildMembersEditorFactory(); } + /** + * We test here a move for a person:. + * + * * which was in a position "sharing household" + * * which move to the same household, in a position "not sharing household" + */ + public function testMoveFromSharingHouseholdToNotSharingHousehouldInSamehousehold() + { + $person = new Person(); + $household = new Household(); + $positionSharing = (new Position())->setShareHousehold(true); + $positionNotSharing = (new Position())->setShareHousehold(false); + $factory = $this->buildMembersEditorFactory(); + $editor = $factory->createEditor($household); + + // we add the member to the household + $editor->addMovement(new DateTimeImmutable('1 month ago'), $person, $positionSharing); + + // double check that the person is in the household + $this->assertContains($person, $household->getCurrentPersons()); + + // we do the move to the position not sharing household + $editor = $factory->createEditor($household); + $editor->addMovement(new DateTimeImmutable('yesterday'), $person, $positionNotSharing); + + $sharings = $household->getCurrentMembers()->filter(static function (HouseholdMember $m) { + return $m->getShareHousehold(); + }); + $notSharing = $household->getCurrentMembers()->filter(static function (HouseholdMember $m) { + return !$m->getShareHousehold(); + }); + + $this->assertCount(1, $notSharing); + $this->assertCount(0, $sharings); + + $getPerson = static function (HouseholdMember $m) { return $m->getPerson(); }; + + $this->assertContains($person, $notSharing->map($getPerson)); + } + public function testMovePersonWithoutSharedHousehold() { $person = new Person(); From e433b6a42be89bf0b019055281f5296e3de934f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 11:57:43 +0200 Subject: [PATCH 07/68] Do not dispatch PersonMoveEvent if moving to a position not sharing household --- .../Household/MembersEditor.php | 9 +++---- .../Tests/Household/MembersEditorTest.php | 24 ++++++++++++++++++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php index 5ecf1c0bd..fd2fcfbb2 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -61,8 +61,6 @@ class MembersEditor throw new LogicException('You must define a household first'); } - $event = new PersonAddressMoveEvent($person); - $membership = (new HouseholdMember()) ->setStartDate($date) ->setPerson($person) @@ -70,9 +68,13 @@ class MembersEditor ->setHolder($holder) ->setComment($comment); $this->household->addMember($membership); - $event->setNextMembership($membership); if ($position->getShareHousehold()) { + // launch event only if moving to a "share household" position + $event = new PersonAddressMoveEvent($person); + $event->setNextMembership($membership); + $this->events[] = $event; + foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) { if ($participation === $membership) { continue; @@ -108,7 +110,6 @@ class MembersEditor $this->membershipsAffected[] = $membership; $this->persistables[] = $membership; - $this->events[] = $event; return $this; } diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php index f1a622f5f..84ca9f32e 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php @@ -167,7 +167,7 @@ final class MembersEditorTest extends TestCase $this->assertEquals($date, $membership1->getEndDate()); } - public function testPostMove() + public function testPostMoveToAPositionNotSharingHousehold() { $person = new Person(); $position = (new Position()) @@ -175,6 +175,28 @@ final class MembersEditorTest extends TestCase $household1 = new Household(); $household2 = new Household(); $eventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $eventDispatcher + ->dispatch(Argument::type(PersonAddressMoveEvent::class)) + ->shouldNotBeCalled(); + $factory = $this->buildMembersEditorFactory( + $eventDispatcher->reveal(), + null + ); + $editor = $factory->createEditor($household1); + + $editor->addMovement(new DateTimeImmutable('now'), $person, $position); + + $editor->postMove(); + } + + public function testPostMoveToAPositionSharingHousehold() + { + $person = new Person(); + $position = (new Position()) + ->setShareHousehold(true); + $household1 = new Household(); + $household2 = new Household(); + $eventDispatcher = $this->prophesize(EventDispatcherInterface::class); $eventDispatcher ->dispatch(Argument::type(PersonAddressMoveEvent::class)) ->shouldBeCalled(); From 38e92ee981ee71de479058aab5ef8adb2212a8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 11:58:17 +0200 Subject: [PATCH 08/68] fix cs --- src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php index c5ab27440..d4b59ab37 100644 --- a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php +++ b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php @@ -109,7 +109,7 @@ class ThirdPartyApiSearch implements SearchApiInterface } $query - ->setSelectPertinence(implode(' + ', $pertinence).' + 1', array_merge( + ->setSelectPertinence(implode(' + ', $pertinence) . ' + 1', array_merge( [], ...$pertinenceArgs )) From 1d6d8dc002bb54e317326079efce28cd3f079253 Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 30 Mar 2022 12:01:35 +0200 Subject: [PATCH 09/68] accompanyingcourse work: add ACL rights for create and edit buttons --- .../views/AccompanyingCourseWork/_item.html.twig | 12 +++++++----- .../views/AccompanyingCourseWork/index.html.twig | 4 ++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig index 6287b158d..8ff8a0617 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_item.html.twig @@ -128,11 +128,13 @@ 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id, [], suppEvaluations) }} -
  • - -
  • + {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w) %} +
  • + +
  • + {% endif %} {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE', w) %}
  • {{ block('title') }} {% if works|length == 0 %} + {#% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE') %#}

    {{ 'accompanying_course_work.Any work'|trans }}

    + {#% endif %#} {% else %}
    @@ -35,6 +37,7 @@ {{ chill_pagination(paginator) }} + {#% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE') %#} + {#% endif %#}
    {% endblock %} From 1b567327b7a2f1aa6a918e744b10c63186b0d845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 12:37:18 +0200 Subject: [PATCH 10/68] fix normalisation for phonenumber in person entity --- .../ChillMainBundle/CRUD/Controller/ApiController.php | 6 +++--- .../Serializer/Normalizer/PersonJsonNormalizer.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php index cfbe811fa..10c530794 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php @@ -33,11 +33,11 @@ class ApiController extends AbstractCRUDController * Base method for handling api action. * * @param mixed $id - * @param mixed $_format + * @param string $_format * * @return void */ - public function entityApi(Request $request, $id, $_format): Response + public function entityApi(Request $request, $id, ?string $_format = 'json'): Response { switch ($request->getMethod()) { case Request::METHOD_GET: @@ -49,7 +49,7 @@ class ApiController extends AbstractCRUDController return $this->entityPut('_entity', $request, $id, $_format); case Request::METHOD_POST: - return $this->entityPostAction('_entity', $request, $id); + return $this->entityPostAction('_entity', $request, $id, $_format); case Request::METHOD_DELETE: return $this->entityDelete('_entity', $request, $id, $_format); diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php index ae95c9321..41c06e9ec 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonJsonNormalizer.php @@ -208,8 +208,8 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar 'birthdate' => $this->normalizer->normalize($person->getBirthdate(), $format, $context), 'deathdate' => $this->normalizer->normalize($person->getDeathdate(), $format, $context), 'age' => $this->normalizer->normalize($person->getAge(), $format, $context), - 'phonenumber' => $this->normalizer->normalize($person->getPhonenumber()), - 'mobilenumber' => $this->normalizer->normalize($person->getMobilenumber()), + 'phonenumber' => $this->normalizer->normalize($person->getPhonenumber(), $format, $context), + 'mobilenumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $context), 'email' => $person->getEmail(), 'gender' => $person->getGender(), ]; From 3b083c31e7463a6432c12356cb906d434ba67737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 12:57:16 +0200 Subject: [PATCH 11/68] add tests for moving to not share position in another household --- .../Tests/Household/MembersEditorTest.php | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php index 84ca9f32e..a5d34b1dc 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php @@ -40,6 +40,48 @@ final class MembersEditorTest extends TestCase $this->factory = $this->buildMembersEditorFactory(); } + /** + * We test here a move for a person:. + * + * * which was in a position "sharing household" + * * which move to the another household, in a position "not sharing household" + * + * The person should stays in the two households + */ + public function testMoveFromSharingHouseholdToNotSharingHousehouldInDifferentHousehold() + { + $person = new Person(); + $household = new Household(); + $positionSharing = (new Position())->setShareHousehold(true); + $positionNotSharing = (new Position())->setShareHousehold(false); + $factory = $this->buildMembersEditorFactory(); + $editor = $factory->createEditor($household); + + // we add the member to the household + $editor->addMovement(new DateTimeImmutable('1 month ago'), $person, $positionSharing); + + // double check that the person is in the household + $this->assertContains($person, $household->getCurrentPersons()); + + // we do the move to the position not sharing household + $editor = $factory->createEditor($household2 = new Household()); + $editor->addMovement(new DateTimeImmutable('yesterday'), $person, $positionNotSharing); + + $sharings = $household->getCurrentMembers()->filter(static function (HouseholdMember $m) { + return $m->getShareHousehold(); + }); + $notSharing = $household2->getCurrentMembers()->filter(static function (HouseholdMember $m) { + return !$m->getShareHousehold(); + }); + + $this->assertCount(1, $notSharing); + $this->assertCount(1, $sharings); + + $getPerson = static function (HouseholdMember $m) { return $m->getPerson(); }; + + $this->assertContains($person, $notSharing->map($getPerson)); + } + /** * We test here a move for a person:. * From 9812710cd0fde9fce782f845e8735d67e58310e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 15:08:34 +0200 Subject: [PATCH 12/68] do not show duplicate menu entry temporarily --- src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php index 80da956c4..ed54e948c 100644 --- a/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php @@ -88,7 +88,7 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface ->setExtras([ 'order' => 99999, ]); - + /* $menu->addChild($this->translator->trans('Person duplicate'), [ 'route' => 'chill_person_duplicate_view', 'routeParameters' => [ @@ -98,7 +98,7 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface ->setExtras([ 'order' => 99999, ]); - + */ if ( 'visible' === $this->showAccompanyingPeriod && $this->security->isGranted(AccompanyingPeriodVoter::SEE, $parameters['person']) From dae9d485748dd6b11477eb82f8b8c70af9e8e147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 15:26:45 +0200 Subject: [PATCH 13/68] really do not show thirdparty which are not active --- .../Repository/ThirdPartyACLAwareRepository.php | 6 ++++++ .../ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepository.php b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepository.php index 2a57a2f55..f67417767 100644 --- a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepository.php +++ b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyACLAwareRepository.php @@ -34,6 +34,12 @@ final class ThirdPartyACLAwareRepository implements ThirdPartyACLAwareRepository { $qb = $this->thirdPartyRepository->createQueryBuilder('tp'); + $qb->leftJoin('tp.parent', 'parent') + ->andWhere($qb->expr()->andX( + 'tp.active = \'TRUE\'', + $qb->expr()->orX($qb->expr()->isNull('parent'), 'parent.active = \'TRUE\'') + )); + if (null !== $filterString) { $qb->andWhere($qb->expr()->like('tp.canonicalized', 'LOWER(UNACCENT(:filterString))')) ->setParameter('filterString', '%' . $filterString . '%'); diff --git a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php index d4b59ab37..3c03b7f8a 100644 --- a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php +++ b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php @@ -113,7 +113,8 @@ class ThirdPartyApiSearch implements SearchApiInterface [], ...$pertinenceArgs )) - ->andWhereClause(implode(' AND ', $wheres), array_merge( + ->andWhereClause(implode(' AND ', $wheres) + .' AND tparty.active IS TRUE and (parent.active IS TRUE OR parent IS NULL)', array_merge( [], ...$whereArgs )); From 36b1f05524a327513a2843bdbd87dd9824f3a645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 16:42:57 +0200 Subject: [PATCH 14/68] do not launch PersonMoveEvent when moving to the same household --- .../ChillPersonBundle/Entity/Person.php | 9 +++ .../Household/MembersEditor.php | 19 +++++- .../Tests/Household/MembersEditorTest.php | 60 ++++++++++++++++++- 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index 880c2bd11..59570f956 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -328,6 +328,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI * targetEntity=HouseholdMember::class, * mappedBy="person" * ) + * + * @var Collection|HouseholdMember[] */ private Collection $householdParticipations; @@ -1117,6 +1119,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return $this->householdAddresses; } + /** + * @return Collection|HouseholdMember[] + */ public function getHouseholdParticipations(): Collection { return $this->householdParticipations; @@ -1126,6 +1131,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI * Get participation where the person does not share the household. * * Order by startDate, desc + * + * @return HouseholdMember[] */ public function getHouseholdParticipationsNotShareHousehold(): Collection { @@ -1146,6 +1153,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI * Get participation where the person does share the household. * * Order by startDate, desc + * + * @return Collection|HouseholdMember[] */ public function getHouseholdParticipationsShareHousehold(): Collection { diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php index fd2fcfbb2..812307a14 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -70,10 +70,12 @@ class MembersEditor $this->household->addMember($membership); if ($position->getShareHousehold()) { - // launch event only if moving to a "share household" position + // launch event only if moving to a "share household" position, + // and if the destination household is different than the previous one $event = new PersonAddressMoveEvent($person); $event->setNextMembership($membership); - $this->events[] = $event; + + $counter = 0; foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) { if ($participation === $membership) { @@ -84,14 +86,25 @@ class MembersEditor continue; } + ++$counter; + if ($participation->getEndDate() === null || $participation->getEndDate() > $date) { - $event->setPreviousMembership($participation); $participation->setEndDate($date); $this->membershipsAffected[] = $participation; $this->oldMembershipsHashes[] = spl_object_hash($participation); + + if ($participation->getHousehold() !== $this->household) { + $event->setPreviousMembership($participation); + $this->events[] = $event; + } } } + // send also the event if there was no participation before + if (0 === $counter) { + $this->events[] = $event; + } + foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) { if ($participation->getHousehold() === $this->household && $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate() diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php index a5d34b1dc..69603031a 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php @@ -231,13 +231,71 @@ final class MembersEditorTest extends TestCase $editor->postMove(); } - public function testPostMoveToAPositionSharingHousehold() + public function testPostMoveToAPositionSharingHouseholdAndSameHousehold() + { + $person = new Person(); + $position = (new Position()) + ->setShareHousehold(true); + $position2 = (new Position()) + ->setShareHousehold(true); + $household1 = new Household(); + + // set into the first household + $editor = $this->buildMembersEditorFactory() + ->createEditor($household1); + $editor->addMovement(new DateTimeImmutable('1 year ago'), $person, $position); + + // prepare for next move + $eventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $eventDispatcher + ->dispatch(Argument::type(PersonAddressMoveEvent::class)) + ->shouldNotBeCalled(); + $factory = $this->buildMembersEditorFactory( + $eventDispatcher->reveal(), + null + ); + $editor = $factory->createEditor($household1); + + $editor->addMovement(new DateTimeImmutable('now'), $person, $position2); + + $editor->postMove(); + } + + public function testPostMoveToAPositionSharingHouseholdFromDifferentHousehold() { $person = new Person(); $position = (new Position()) ->setShareHousehold(true); $household1 = new Household(); $household2 = new Household(); + + // set into the first household + $editor = $this->buildMembersEditorFactory() + ->createEditor($household1); + $editor->addMovement(new DateTimeImmutable('1 year ago'), $person, $position); + + // perform now the movement + $eventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $eventDispatcher + ->dispatch(Argument::type(PersonAddressMoveEvent::class)) + ->shouldBeCalled(); + $factory = $this->buildMembersEditorFactory( + $eventDispatcher->reveal(), + null + ); + $editor = $factory->createEditor($household2); + + $editor->addMovement(new DateTimeImmutable('now'), $person, $position); + + $editor->postMove(); + } + + public function testPostMoveToAPositionSharingHouseholdFromNoHousehold() + { + $person = new Person(); + $position = (new Position()) + ->setShareHousehold(true); + $household1 = new Household(); $eventDispatcher = $this->prophesize(EventDispatcherInterface::class); $eventDispatcher ->dispatch(Argument::type(PersonAddressMoveEvent::class)) From c477996acf485cadf0238677752c27e1eb618e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 16:43:16 +0200 Subject: [PATCH 15/68] fix cs --- src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php | 2 +- .../ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php index ed54e948c..103fd2b88 100644 --- a/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/PersonMenuBuilder.php @@ -98,7 +98,7 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface ->setExtras([ 'order' => 99999, ]); - */ + */ if ( 'visible' === $this->showAccompanyingPeriod && $this->security->isGranted(AccompanyingPeriodVoter::SEE, $parameters['person']) diff --git a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php index 3c03b7f8a..f43a6eda2 100644 --- a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php +++ b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php @@ -114,10 +114,10 @@ class ThirdPartyApiSearch implements SearchApiInterface ...$pertinenceArgs )) ->andWhereClause(implode(' AND ', $wheres) - .' AND tparty.active IS TRUE and (parent.active IS TRUE OR parent IS NULL)', array_merge( - [], - ...$whereArgs - )); + . ' AND tparty.active IS TRUE and (parent.active IS TRUE OR parent IS NULL)', array_merge( + [], + ...$whereArgs + )); return $query; } From 83dfe530e912a23628516ae3816287ce9953d812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 17:00:07 +0200 Subject: [PATCH 16/68] household editor: handle case when the person is repositionned in the same household, and the person is already in a position "without household" --- .../Household/MembersEditor.php | 14 ++++++++ .../Tests/Household/MembersEditorTest.php | 34 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php index 812307a14..1c7cbb538 100644 --- a/src/Bundle/ChillPersonBundle/Household/MembersEditor.php +++ b/src/Bundle/ChillPersonBundle/Household/MembersEditor.php @@ -119,6 +119,20 @@ class MembersEditor if ($person->getCurrentHousehold($date) === $this->household) { $this->leaveMovement($date, $person); } + + // if there are multiple belongings not sharing household, close the others + foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) { + if ($participation === $membership) { + continue; + } + + if ($participation->getHousehold() === $this->household + && ($participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()) + && $participation->getStartDate() <= $membership->getStartDate() + ) { + $participation->setEndDate($membership->getStartDate()); + } + } } $this->membershipsAffected[] = $membership; diff --git a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php index 69603031a..77eeed387 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Household/MembersEditorTest.php @@ -40,6 +40,40 @@ final class MembersEditorTest extends TestCase $this->factory = $this->buildMembersEditorFactory(); } + public function testAddingParticipationNotSharingHouseholdCloseTheOldOnes() + { + $person = new Person(); + $position = (new Position())->setShareHousehold(false); + $household = new Household(); + + // set a first time the person in position + $factory = $this->buildMembersEditorFactory(); + $editor = $factory->createEditor($household); + + $editor->addMovement($aMonthAgo = new DateTimeImmutable('1 month ago'), $person, $position); + + // set a second time the person in position + $factory = $this->buildMembersEditorFactory(); + $editor = $factory->createEditor($household); + + $editor->addMovement($yesterday = new DateTimeImmutable('yesterday'), $person, $position); + + $this->assertCount(2, $person->getHouseholdParticipationsNotShareHousehold()); + + $startDates = []; + $endDates = []; + + foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) { + $startDates[] = $participation->getStartDate(); + $endDates[] = $participation->getEndDate(); + } + + $this->assertContains($aMonthAgo, $startDates); + $this->assertContains($yesterday, $startDates); + $this->assertContains($yesterday, $endDates); + $this->assertContains(null, $endDates); + } + /** * We test here a move for a person:. * From bc43d8bae56e2cf2bf3b4c3ed4bcf0647f4ac2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 21:01:17 +0200 Subject: [PATCH 17/68] force type on Person entity --- src/Bundle/ChillPersonBundle/Entity/Person.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index 59570f956..4680581cd 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -284,7 +284,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI * max=255, * ) */ - private $firstName; + private string $firstName = ''; /** * fullname canonical. Read-only field, which is calculated by @@ -294,7 +294,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI * * @ORM\Column(type="text", nullable=true) */ - private $fullnameCanonical; + private string $fullnameCanonical = ''; /** * The person's gender. @@ -353,7 +353,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI * max=255, * ) */ - private $lastName; + private string $lastName = ''; /** * The marital status of the person. @@ -1583,9 +1583,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return $this; } - public function setFirstName(string $firstName): self + public function setFirstName(?string $firstName): self { - $this->firstName = $firstName; + $this->firstName = (string) $firstName; return $this; } @@ -1611,9 +1611,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI return $this; } - public function setLastName(string $lastName): self + public function setLastName(?string $lastName): self { - $this->lastName = $lastName; + $this->lastName = (string) $lastName; return $this; } From eb2bad0f4705245e38b2bfefb36c0d5fe2a010e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 21:35:02 +0200 Subject: [PATCH 18/68] docgen normalization budget: fix budget when person is null --- .../Service/Summary/SummaryBudget.php | 51 +++++++------- .../Summary/SummaryBudgetInterface.php | 25 +++++++ .../ChillPersonBundle/Entity/Person.php | 6 -- .../Normalizer/PersonDocGenNormalizer.php | 14 +++- .../Normalizer/PersonDocGenNormalizerTest.php | 66 +++++++++++++++---- 5 files changed, 120 insertions(+), 42 deletions(-) create mode 100644 src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudgetInterface.php diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php index 08a0fec77..8e46feb7b 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php @@ -23,7 +23,7 @@ use function count; /** * Helps to find a summary of the budget: the sum of resources and charges. */ -class SummaryBudget +class SummaryBudget implements SummaryBudgetInterface { private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, type FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; @@ -52,26 +52,6 @@ class SummaryBudget $this->translatableStringHelper = $translatableStringHelper; } - public function getEmptyChargeArray(): array - { - $keys = $this->configRepository->getChargesKeys(); - $labels = $this->chargeLabels; - - return array_combine($keys, array_map(function ($i) use ($labels) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i])]; - }, $keys)); - } - - public function getEmptyResourceArray(): array - { - $keys = $this->configRepository->getResourcesKeys(); - $labels = $this->resourcesLabels; - - return array_combine($keys, array_map(function ($i) use ($labels) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i])]; - }, $keys)); - } - public function getSummaryForHousehold(?Household $household): array { if (null === $household) { @@ -101,8 +81,15 @@ class SummaryBudget ]; } - public function getSummaryForPerson(Person $person): array + public function getSummaryForPerson(?Person $person): array { + if (null === $person) { + return [ + 'resources' => $this->getEmptyResourceArray(), + 'charges' => $this->getEmptyChargeArray(), + ]; + } + $rsm = $this->buildRsm(); $resources = $this->em->createNativeQuery(self::QUERY_RESOURCE_BY_PERSON, $rsm) @@ -128,6 +115,26 @@ class SummaryBudget return $rsm; } + private function getEmptyChargeArray(): array + { + $keys = $this->configRepository->getChargesKeys(); + $labels = $this->chargeLabels; + + return array_combine($keys, array_map(function ($i) use ($labels) { + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i])]; + }, $keys)); + } + + private function getEmptyResourceArray(): array + { + $keys = $this->configRepository->getResourcesKeys(); + $labels = $this->resourcesLabels; + + return array_combine($keys, array_map(function ($i) use ($labels) { + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i])]; + }, $keys)); + } + private function rowToArray(array $rows, string $kind): array { switch ($kind) { diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudgetInterface.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudgetInterface.php new file mode 100644 index 000000000..528c4626e --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudgetInterface.php @@ -0,0 +1,25 @@ +personRender = $personRender; $this->relationshipRepository = $relationshipRepository; @@ -214,6 +214,14 @@ class PersonDocGenNormalizer implements $data['relations'] = []; } + if ($context['docgen:person:with-budget'] ?? false) { + $data['budget']['person'] = $this->summaryBudget->getSummaryForPerson(null); + + if ($context['docgen:person:with-household'] ?? false) { + $data['budget']['household'] = $this->summaryBudget->getSummaryForHousehold(null); + } + } + return $data; } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonDocGenNormalizerTest.php b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonDocGenNormalizerTest.php index c19533e08..deef94525 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonDocGenNormalizerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonDocGenNormalizerTest.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Serializer\Normalizer; +use Chill\BudgetBundle\Service\Summary\SummaryBudgetInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\Household\Household; @@ -72,6 +73,17 @@ final class PersonDocGenNormalizerTest extends KernelTestCase $this->normalizer = self::$container->get(NormalizerInterface::class); } + public function dataGeneratorNormalizationNullOrNotNullHaveSameKeys(): iterable + { + yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read']]]; + + yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read'], 'docgen:person:with-household' => true]]; + + yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read'], 'docgen:person:with-relations' => true]]; + + yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read'], 'docgen:person:with-budget' => true]]; + } + public function generateData() { $person = new Person(); @@ -90,12 +102,16 @@ final class PersonDocGenNormalizerTest extends KernelTestCase yield [null, self::BLANK, 'normalization for a null person']; } - public function testNormalizationNullOrNotNullHaveSameKeys() + /** + * @dataProvider dataGeneratorNormalizationNullOrNotNullHaveSameKeys + * + * @param mixed $context + */ + public function testNormalizationNullOrNotNullHaveSameKeys($context) { - $this->markTestSkipped(); $period = new Person(); - $notNullData = $this->buildPersonNormalizer()->normalize($period, 'docgen', ['docgen:expects' => Person::class]); - $nullData = $this->buildPersonNormalizer()->normalize(null, 'docgen', ['docgen:expects' => Person::class]); + $notNullData = $this->buildPersonNormalizer()->normalize($period, 'docgen', $context); + $nullData = $this->buildPersonNormalizer()->normalize(null, 'docgen', $context); $this->assertEqualsCanonicalizing( array_keys($notNullData), @@ -131,7 +147,6 @@ final class PersonDocGenNormalizerTest extends KernelTestCase public function testNormalizePersonWithHousehold() { - $this->markTestSkipped(); $household = new Household(); $person = new Person(); $person @@ -172,7 +187,6 @@ final class PersonDocGenNormalizerTest extends KernelTestCase public function testNormalizePersonWithRelationships() { - $this->markTestSkipped(); $person = (new Person())->setFirstName('Renaud')->setLastName('megane'); $father = (new Person())->setFirstName('Clément')->setLastName('megane'); $mother = (new Person())->setFirstName('Mireille')->setLastName('Mathieu'); @@ -235,13 +249,25 @@ final class PersonDocGenNormalizerTest extends KernelTestCase ?RelationshipRepository $relationshipRepository = null, ?TranslatorInterface $translator = null, ?TranslatableStringHelper $translatableStringHelper = null, - ?NormalizerInterface $normalizer = null + ?NormalizerInterface $normalizer = null, + ?SummaryBudgetInterface $summaryBudget = null ): PersonDocGenNormalizer { + if (null === $summaryBudget) { + $summaryBudget = $this->prophesize(SummaryBudgetInterface::class); + $summaryBudget->getSummaryForHousehold(Argument::any())->willReturn( + ['resources' => [], 'charges' => []] + ); + $summaryBudget->getSummaryForPerson(Argument::any())->willReturn( + ['resources' => [], 'charges' => []] + ); + } + $personDocGenNormalizer = new PersonDocGenNormalizer( $personRender ?? self::$container->get(PersonRender::class), $relationshipRepository ?? self::$container->get(RelationshipRepository::class), $translator ?? self::$container->get(TranslatorInterface::class), - $translatableStringHelper ?? self::$container->get(TranslatableStringHelperInterface::class) + $translatableStringHelper ?? self::$container->get(TranslatableStringHelperInterface::class), + $summaryBudget->reveal(), ); if (null === $normalizer) { @@ -259,13 +285,31 @@ final class PersonDocGenNormalizerTest extends KernelTestCase ?PersonRender $personRender = null, ?RelationshipRepository $relationshipRepository = null, ?TranslatorInterface $translator = null, - ?TranslatableStringHelper $translatableStringHelper = null + ?TranslatableStringHelper $translatableStringHelper = null, + ?SummaryBudgetInterface $summaryBudget = null ): PersonDocGenNormalizer { + if (null === $relationshipRepository) { + $relationshipRepository = $this->prophesize(RelationshipRepository::class); + $relationshipRepository->findByPerson(Argument::type(Person::class))->willReturn([]); + $relationshipRepository = $relationshipRepository->reveal(); + } + + if (null === $summaryBudget) { + $summaryBudget = $this->prophesize(SummaryBudgetInterface::class); + $summaryBudget->getSummaryForHousehold(Argument::any())->willReturn( + ['resources' => [], 'charges' => []] + ); + $summaryBudget->getSummaryForPerson(Argument::any())->willReturn( + ['resources' => [], 'charges' => []] + ); + } + $normalizer = new PersonDocGenNormalizer( $personRender ?? self::$container->get(PersonRender::class), - $relationshipRepository ?? self::$container->get(RelationshipRepository::class), + $relationshipRepository, $translator ?? self::$container->get(TranslatorInterface::class), - $translatableStringHelper ?? self::$container->get(TranslatableStringHelperInterface::class) + $translatableStringHelper ?? self::$container->get(TranslatableStringHelperInterface::class), + $summaryBudget->reveal() ); $normalizerManager = $this->prophesize(NormalizerInterface::class); $normalizerManager->supportsNormalization(Argument::any(), 'docgen', Argument::any())->willReturn(true); From ae10a8bd1c309edbe64a3ad2c64325a0a35ae6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 21:51:39 +0200 Subject: [PATCH 19/68] fix post action on api controller: too many argument --- src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php index 10c530794..a780af78a 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php @@ -49,7 +49,7 @@ class ApiController extends AbstractCRUDController return $this->entityPut('_entity', $request, $id, $_format); case Request::METHOD_POST: - return $this->entityPostAction('_entity', $request, $id, $_format); + return $this->entityPostAction('_entity', $request, $id); case Request::METHOD_DELETE: return $this->entityDelete('_entity', $request, $id, $_format); From 63cdc97c47d561926cd487923d91052131551e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 21:52:43 +0200 Subject: [PATCH 20/68] fix phpstan errors on ThirdPartyRender --- phpstan-types.neon | 5 ----- src/Bundle/ChillPersonBundle/Form/PersonResourceType.php | 4 ++++ .../Templating/Entity/ThirdPartyRender.php | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/phpstan-types.neon b/phpstan-types.neon index 9671c05a5..d43fd8944 100644 --- a/phpstan-types.neon +++ b/phpstan-types.neon @@ -460,8 +460,3 @@ parameters: count: 1 path: src/Bundle/ChillThirdPartyBundle/Search/ThirdPartySearch.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php - diff --git a/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php b/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php index aeab09fea..31af0a649 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php @@ -29,8 +29,12 @@ use Symfony\Contracts\Translation\TranslatorInterface; final class PersonResourceType extends AbstractType { + private PersonRenderInterface $personRender; + private ResourceKindRender $resourceKindRender; + private ThirdPartyRender $thirdPartyRender; + private TranslatorInterface $translator; public function __construct(ResourceKindRender $resourceKindRender, PersonRenderInterface $personRender, ThirdPartyRender $thirdPartyRender, TranslatorInterface $translator) diff --git a/src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php b/src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php index cec9dee7d..4fe3da927 100644 --- a/src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php +++ b/src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php @@ -71,13 +71,13 @@ class ThirdPartyRender extends AbstractChillEntityRender $civility = ''; } - if (!empty($entity->getAcronym())) { + if ('' !== (string) $entity->getAcronym()) { $acronym = ' (' . $entity->getAcronym() . ')'; } else { $acronym = ''; } - $firstname = empty($entity->getFirstname()) ? '' : $entity->getFirstname(); + $firstname = ('' === $entity->getFirstname()) ? '' : $entity->getFirstname(); return $civility . $firstname . ' ' . $entity->getName() . $acronym; } From 034a41661229e79f8f9b1c98cbc2ad42a232a7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 30 Mar 2022 22:02:45 +0200 Subject: [PATCH 21/68] fix test which fails randomly --- .../Tests/Controller/HouseholdApiControllerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdApiControllerTest.php index 08034978e..94a0ac243 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdApiControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdApiControllerTest.php @@ -65,7 +65,7 @@ final class HouseholdApiControllerTest extends WebTestCase } $reference = $em->createQueryBuilder()->select('ar')->from(AddressReference::class, 'ar') - ->setFirstResult(random_int(0, $nbReference)) + ->setFirstResult(random_int(0, $nbReference - 1)) ->setMaxResults(1) ->getQuery()->getSingleResult(); From dcddf4b3f1d2c678eee24553d4cc0e5adcda298f Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Thu, 31 Mar 2022 10:24:58 +0200 Subject: [PATCH 22/68] trim email --- CHANGELOG.md | 1 + src/Bundle/ChillPersonBundle/Entity/Person.php | 2 ++ src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php | 1 + 3 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56b958bb6..94489398e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ and this project adheres to * [parcours] Create document buttons made sticky (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/532) * [person] Trailing guillemet removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/530) * [notification] Display of social action within workflow notification set to display block (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/537) +* [onthefly] trim trailing whitespace in email of person and thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/542) ## Test releases diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index 7bde8a524..de7946ad3 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -1572,6 +1572,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI $email = ''; } + $email = trim($email); + $this->email = $email; return $this; diff --git a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php index 16260ec97..9c3d6fd7e 100644 --- a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php +++ b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php @@ -762,6 +762,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface */ public function setEmail($email = null) { + $email = trim($email); $this->email = $email; return $this; From 6ddbb791571732b88b56203f5cc7d624ec9202c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 31 Mar 2022 12:43:16 +0200 Subject: [PATCH 23/68] format phonenumber in third party normalization --- .../Serializer/Normalizer/ThirdPartyNormalizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php b/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php index 87bfb6995..5eb8895b3 100644 --- a/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php +++ b/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php @@ -63,7 +63,7 @@ class ThirdPartyNormalizer implements NormalizerAwareInterface, NormalizerInterf }, $thirdParty->getTypesAndCategories()), 'profession' => $this->normalizer->normalize($thirdParty->getProfession(), $format, $context), 'address' => $this->normalizer->normalize($thirdParty->getAddress(), $format, ['address_rendering' => 'short']), - 'telephone' => $this->normalizer->normalize($thirdParty->getTelephone()), + 'telephone' => $this->normalizer->normalize($thirdParty->getTelephone(), $format, $context), 'email' => $thirdParty->getEmail(), 'isChild' => $thirdParty->isChild(), 'parent' => $this->normalizer->normalize($thirdParty->getParent(), $format, $context), From 51dc255be5f583c8dbc30867561ccc3b0b323ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 31 Mar 2022 12:45:52 +0200 Subject: [PATCH 24/68] fix phonenumber denormalization when null value is given --- .../Serializer/Normalizer/PhonenumberNormalizer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php index 7eb323754..f76b1e5c8 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/PhonenumberNormalizer.php @@ -32,7 +32,7 @@ class PhonenumberNormalizer implements ContextAwareNormalizerInterface, Denormal } /** - * @param mixed $data + * @param string|null $data * @param mixed $type * @param null|mixed $format * @@ -40,7 +40,7 @@ class PhonenumberNormalizer implements ContextAwareNormalizerInterface, Denormal */ public function denormalize($data, $type, $format = null, array $context = []) { - if ('' === trim($data)) { + if ('' === trim((string) $data)) { return null; } From f47fb17b8d4bd07f2fba604d6ef3e9407f14103e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 31 Mar 2022 12:46:13 +0200 Subject: [PATCH 25/68] fix denormalization of invalid dates --- .../Serializer/Normalizer/DateNormalizer.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php index 70958d5a9..81e953267 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php @@ -44,13 +44,21 @@ class DateNormalizer implements ContextAwareNormalizerInterface, DenormalizerInt switch ($type) { case DateTime::class: - return DateTime::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']); + $result = DateTime::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']); + break; case DateTimeInterface::class: case DateTimeImmutable::class: - return DateTimeImmutable::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']); + $result = DateTimeImmutable::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']); + break; } + if (false === $result) { + return null; + } + + return $result; + throw new UnexpectedValueException(); } From c7762dd6d27b2239aa0cd73d23ce71877c0cb939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 31 Mar 2022 12:51:53 +0200 Subject: [PATCH 26/68] fix trimming for email in person / thirdparty --- src/Bundle/ChillPersonBundle/Entity/Person.php | 8 +------- src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php | 3 +-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index de7946ad3..ef522f068 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -1568,13 +1568,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI public function setEmail(?string $email): self { - if (null === $email) { - $email = ''; - } - - $email = trim($email); - - $this->email = $email; + $this->email = trim((string) $email); return $this; } diff --git a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php index 9c3d6fd7e..f060c4bfb 100644 --- a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php +++ b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php @@ -762,8 +762,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface */ public function setEmail($email = null) { - $email = trim($email); - $this->email = $email; + $this->email = trim((string) $email); return $this; } From a4ece21f2b5ec4312298af7b56fee67e1e916329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 1 Apr 2022 15:06:00 +0200 Subject: [PATCH 27/68] in household, force validTo of address to be NULL --- src/Bundle/ChillPersonBundle/Entity/Household/Household.php | 2 ++ .../ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php index 2620ed19a..fed9f0af0 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php @@ -514,6 +514,8 @@ class Household if ($iterator->valid()) { $current->setValidTo($iterator->current()->getValidFrom()); + } else { + $current->setValidTo(null); } } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php index 6799f882b..7cf8af3e5 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Entity/Household/HouseholdTest.php @@ -75,6 +75,7 @@ final class HouseholdTest extends TestCase $lastAddress = new Address(); $lastAddress->setValidFrom($yesterday = new DateTime('yesterday')); + $lastAddress->setValidTo(new DateTime('tomorrow')); $household->addAddress($lastAddress); $this->assertNull($lastAddress->getValidTo()); @@ -82,6 +83,7 @@ final class HouseholdTest extends TestCase $previousAddress = new Address(); $previousAddress->setValidFrom($oneMonthAgo = new DateTime('1 month ago')); + $previousAddress->setValidTo(new DateTime('now')); $household->addAddress($previousAddress); $addresses = $household->getAddressesOrdered(); @@ -95,6 +97,7 @@ final class HouseholdTest extends TestCase $futureAddress = new Address(); $futureAddress->setValidFrom($tomorrow = new DateTime('tomorrow')); + $futureAddress->setValidTo(new DateTime('2150-01-01')); $household->addAddress($futureAddress); $addresses = $household->getAddressesOrdered(); From f2744fba43668698607d849a722fc0b91c8b47bd Mon Sep 17 00:00:00 2001 From: nobohan Date: Tue, 5 Apr 2022 22:28:35 +0200 Subject: [PATCH 28/68] notification toggle read: correct js syntax --- .../public/module/notification/toggle_read.js | 23 ++++++++++--------- .../Notification/NotificationReadToggle.vue | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/notification/toggle_read.js b/src/Bundle/ChillMainBundle/Resources/public/module/notification/toggle_read.js index 68b06b76a..6308f2f11 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/notification/toggle_read.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/notification/toggle_read.js @@ -8,30 +8,31 @@ window.addEventListener('DOMContentLoaded', function (e) { document.querySelectorAll('.notification_toggle_read_status') .forEach(function (el, i) { createApp({ - template: '', + template: ` + `, components: { NotificationReadToggle, }, data() { return { - notificationId: +el.dataset.notificationId, + notificationId: el.dataset.notificationId, buttonClass: el.dataset.buttonClass, buttonNoText: 'false' === el.dataset.buttonText, showUrl: el.dataset.showButtonUrl, - isRead: 1 === +el.dataset.notificationCurrentIsRead, + isRead: 1 === el.dataset.notificationCurrentIsRead, container: el.dataset.container } }, computed: { getContainer() { - return document.querySelectorAll('div.' + this.container); + return document.querySelectorAll(`div.${this.container}`); } }, methods: { diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue index c60124592..f9c60fa29 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue @@ -81,7 +81,7 @@ export default { /// [Option] showUrl is href for show page second button. // When passed, the component return a button-group with 2 buttons. isButtonGroup() { - return !!this.showUrl + return !this.showUrl; } }, methods: { From d6deaeb324ffd7f5e198a5502703c2f79614ecbf Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 6 Apr 2022 09:26:14 +0200 Subject: [PATCH 29/68] notification unread: correct class --- .../vuejs/_components/Notification/NotificationReadToggle.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue index f9c60fa29..892e905b6 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue @@ -81,7 +81,7 @@ export default { /// [Option] showUrl is href for show page second button. // When passed, the component return a button-group with 2 buttons. isButtonGroup() { - return !this.showUrl; + return this.showUrl; } }, methods: { From 88f377778cf0d551dee2cdbe6ade9e6538cc7aae Mon Sep 17 00:00:00 2001 From: nobohan Date: Wed, 6 Apr 2022 09:28:57 +0200 Subject: [PATCH 30/68] upd CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94489398e..6f67e3247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to ## Unreleased +* [main] notification toggle read: correct js syntax for compilation in production (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/548) + + * [person] Accompanying course evaluation documents: disable the WOPI edit link if mimetype not supported and if no keyInfos (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/585) From 37c04d3f125b2663477dac16168340432293bb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 6 Apr 2022 12:00:49 +0200 Subject: [PATCH 31/68] update changelog --- CHANGELOG.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 603feecc7..d80038b97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,15 @@ and this project adheres to ## Unreleased +* [parcours] Display of interlocuteurs changed to flex-table in parcours edit page to prevent cut-off of information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/535) + +## Test releases + +### continuous release in February and March + +* Creation of PickCivilityType, and implementation in PersonType and ThirdpartyType * [person] Accompanying course evaluation documents: disable the WOPI edit link if mimetype not supported and if no keyInfos (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/585) - * [activity] display error messages above the form in creating a new location (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/481) * [activity] show required field in activity edit/new by an asterix in the vuejs fields (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/494) * [ACL] fix allow to see the course, event if the scope'course does not contains the scope's user @@ -78,9 +84,6 @@ and this project adheres to * [person] Trailing guillemet removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/530) * [notification] Display of social action within workflow notification set to display block (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/537) * [onthefly] trim trailing whitespace in email of person and thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/542) -* [parcours] Display of interlocuteurs changed to flex-table in parcours edit page to prevent cut-off of information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/535) - -## Test releases ### test release 2022-02-21 @@ -108,8 +111,6 @@ and this project adheres to * [bug]: fix confidential toggle of address in thirdpartyrenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/460) -## Test releases -* Creation of PickCivilityType, and implementation in PersonType and ThirdpartyType ### test release 2022-02-14 From 066afc07a8e09a1f98ea8c5d6b8df9b4ce06ac65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 6 Apr 2022 12:08:44 +0200 Subject: [PATCH 32/68] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d80038b97..0aa10a5c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to * [parcours] Display of interlocuteurs changed to flex-table in parcours edit page to prevent cut-off of information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/535) +* [activity] espace entre les boutons pour supprimer les documents ## Test releases From 5477a70c8425c7b13d4b695df79e1c3561000bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 6 Apr 2022 12:41:50 +0200 Subject: [PATCH 33/68] add ACL around creation of work in course --- .../views/AccompanyingCourseWork/index.html.twig | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/index.html.twig index 50301f90c..6956a1497 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/index.html.twig @@ -18,15 +18,7 @@

    {{ block('title') }}

    {% if works|length == 0 %} - {#% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE') %#} -

    {{ 'accompanying_course_work.Any work'|trans }} - -

    - {#% endif %#} - +

    {{ 'accompanying_course_work.Any work'|trans }}

    {% else %}
    {% for w in works %} @@ -37,7 +29,7 @@ {{ chill_pagination(paginator) }} - {#% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE') %#} + {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE') %} - {#% endif %#} + {% endif %}
    {% endblock %} From 95b114a144d35678c00f64d6abc73452d3dc3e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 6 Apr 2022 12:42:12 +0200 Subject: [PATCH 34/68] do not show button to send notification on draft courses --- .../views/AccompanyingCourse/index.html.twig | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig index 0a021997d..7a4f18b1d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig @@ -184,25 +184,27 @@
  • {% endif %} - -
    -
    -

    {{ 'notification.Notifications'|trans }}

    - {% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) %} - {% if notif_counter.total > 0 %} -
    {% endblock %} - +
    {% set notifications = chill_list_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) %} {% if notifications is not empty %} From a4afe73efe3458631663583f2c8a49768d3bf22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 6 Apr 2022 13:35:24 +0200 Subject: [PATCH 35/68] take into account system notification in counter and fix hardcoded user id --- .../ChillMainBundle/Repository/NotificationRepository.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php b/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php index 9cfffb2cf..3f3b08f6c 100644 --- a/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php @@ -56,17 +56,18 @@ final class NotificationRepository implements ObjectRepository if (null === $this->notificationByRelatedEntityAndUserAssociatedStatement) { $sql = 'SELECT - SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau WHERE user_id = 1812 and cmnau.notification_id = cmn.id))::int) AS unread, - SUM((cmn.sender_id = 1812)::int) AS sent, + SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau JOIN chill_main_notification cmn ON cmnau.notification_id = cmn.id WHERE user_id = :userid and cmnau.notification_id = cmn.id and cmn.sender_id IS NOT NULL))::int) AS unread, + SUM((cmn.sender_id = :userid)::int) AS sent, COUNT(cmn.*) AS total FROM chill_main_notification cmn WHERE relatedentityclass = :relatedEntityClass AND relatedentityid = :relatedEntityId AND sender_id IS NOT NULL'; + $this->notificationByRelatedEntityAndUserAssociatedStatement = $this->em->getConnection()->prepare($sql); } $results = $this->notificationByRelatedEntityAndUserAssociatedStatement - ->executeQuery(['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId]); + ->executeQuery(['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId, 'userid' => $user->getId()]); $result = $results->fetchAssociative(); From 8edc68859ec1c5bafe6aa44143a14b0610151577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 6 Apr 2022 13:41:34 +0200 Subject: [PATCH 36/68] do not allow to freeze a document --- .../Workflow/AccompanyingCourseDocumentWorkflowHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php b/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php index 3d7b95c61..02cdf6fb3 100644 --- a/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php +++ b/src/Bundle/ChillDocStoreBundle/Workflow/AccompanyingCourseDocumentWorkflowHandler.php @@ -116,6 +116,6 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler public function supportsFreeze(EntityWorkflow $entityWorkflow, array $options = []): bool { - return true; + return false; } } From 612d0538929ce2025c58231fab635d3152d1ffae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 6 Apr 2022 13:59:05 +0200 Subject: [PATCH 37/68] fix phpstan and phpcs --- .../Serializer/Normalizer/DateNormalizer.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php index 81e953267..d3161fd4a 100644 --- a/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/DateNormalizer.php @@ -45,12 +45,17 @@ class DateNormalizer implements ContextAwareNormalizerInterface, DenormalizerInt switch ($type) { case DateTime::class: $result = DateTime::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']); + break; case DateTimeInterface::class: case DateTimeImmutable::class: $result = DateTimeImmutable::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']); + break; + + default: + throw new UnexpectedValueException(); } if (false === $result) { @@ -58,8 +63,6 @@ class DateNormalizer implements ContextAwareNormalizerInterface, DenormalizerInt } return $result; - - throw new UnexpectedValueException(); } public function normalize($date, $format = null, array $context = []) From 645549ae34768af8860f1cd69d94b7da15c38d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 6 Apr 2022 21:45:49 +0200 Subject: [PATCH 38/68] effectively filter on all words in third party search --- .../ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php index f43a6eda2..83a2add2d 100644 --- a/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php +++ b/src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php @@ -85,12 +85,12 @@ class ThirdPartyApiSearch implements SearchApiInterface $pertinenceArgs = []; foreach ($strs as $str) { - if (!empty($str)) { - $wheres[] = "(LOWER(UNACCENT(?)) <<% tparty.canonicalized OR + if ('' !== trim($str)) { + $wheres[] = "((LOWER(UNACCENT(?)) <<% tparty.canonicalized OR tparty.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%') OR (LOWER(UNACCENT(?)) <<% parent.canonicalized OR - parent.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%') + parent.canonicalized LIKE '%' || LOWER(UNACCENT(?)) || '%')) "; $whereArgs[] = [$str, $str, $str, $str]; $pertinence[] = 'GREATEST( From 095afb90c717a817c340d3ca6b1c087fa957e5a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 6 Apr 2022 21:47:01 +0200 Subject: [PATCH 39/68] AddPerson search: fix aborting query when the query is altered by user --- .../Resources/public/vuejs/_api/AddPersons.js | 2 +- .../public/vuejs/_components/AddPersons.vue | 27 +++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js index d72a42b15..453c2674a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js @@ -45,7 +45,7 @@ const searchPersons = ({ query, options }, signal) => { const searchEntities = ({ query, options }, signal) => { let queryStr = parametersToString({ query, options }); let url = `/api/1.0/search.json?${queryStr}`; - return fetch(url) + return fetch(url, { signal }) .then(response => { if (response.ok) { return response.json(); } throw Error('Error with request resource response'); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue index 37707155d..90505a1e2 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue @@ -199,15 +199,26 @@ export default { return; } if (query === this.search.query) { - if (this.currentSearchQueryController !== undefined) { - this.currentSearchQueryController.abort() + if (this.search.currentSearchQueryController !== null) { + this.search.currentSearchQueryController.abort(); } - this.currentSearchQueryController = new AbortController(); - searchEntities({ query, options: this.options }, this.currentSearchQueryController) - .then(suggested => new Promise((resolve, reject) => { - this.loadSuggestions(suggested.results); - resolve(); - })); + this.search.currentSearchQueryController = new AbortController(); + searchEntities({ query, options: this.options }, this.search.currentSearchQueryController.signal) + .then(suggested => new Promise((resolve, reject) => { + this.loadSuggestions(suggested.results); + resolve(); + })) + .catch(error => { + if (error instanceof DOMException) { + if (error.name === 'AbortError') { + console.log('request aborted due to user continue typing'); + return; + } + } + + throw error; + }) + ; } }.bind(this), query.length > 3 ? 300 : 700); }, From 934a06691053d29dffecb5141965963c9584d82c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 6 Apr 2022 22:40:09 +0200 Subject: [PATCH 40/68] remove error disappearing from phpstan --- phpstan-types.neon | 5 ----- 1 file changed, 5 deletions(-) diff --git a/phpstan-types.neon b/phpstan-types.neon index d43fd8944..dc3f67a73 100644 --- a/phpstan-types.neon +++ b/phpstan-types.neon @@ -450,11 +450,6 @@ parameters: count: 1 path: src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php - - - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 1 - path: src/Bundle/ChillThirdPartyBundle/Search/ThirdPartyApiSearch.php - - message: "#^Method Chill\\\\ThirdPartyBundle\\\\Search\\\\ThirdPartySearch\\:\\:renderResult\\(\\) should return string but return statement is missing\\.$#" count: 1 From 00a6ef0598edcae5d8a9ce248bc880e32a5b9642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Apr 2022 11:05:34 +0200 Subject: [PATCH 41/68] fix creating of accompanying period work --- .../Resources/views/AccompanyingCourseWork/index.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/index.html.twig index 6956a1497..3efaa2251 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/index.html.twig @@ -29,7 +29,7 @@ {{ chill_pagination(paginator) }} - {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE') %} + {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE', accompanyingCourse) %}
    -{% if display_action is defined and display_action == true %} - {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', evaluation.accompanyingPeriodWork) %} - {% endif %} {% endif %} diff --git a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php index d7ce1ed4b..578d8e195 100644 --- a/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php +++ b/src/Bundle/ChillPersonBundle/Workflow/AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler.php @@ -48,6 +48,12 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW { $doc = $this->getRelatedEntity($entityWorkflow); + if (null === $doc) { + return [ + 'persons' => [], + ]; + } + return [ 'persons' => $doc->getAccompanyingPeriodWorkEvaluation() ->getAccompanyingPeriodWork()->getPersons(), @@ -58,6 +64,10 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW { $doc = $this->getRelatedEntity($entityWorkflow); + if (null === $doc) { + return $this->translator->trans('workflow.doc for evaluation deleted'); + } + return $this->translator->trans( 'workflow.Doc for evaluation (n°%eval%)', ['%eval%' => $entityWorkflow->getRelatedEntityId()] @@ -98,7 +108,7 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW return [ 'entity_workflow' => $entityWorkflow, - 'evaluation' => $doc->getAccompanyingPeriodWorkEvaluation(), + 'evaluation' => null !== $doc ? $doc->getAccompanyingPeriodWorkEvaluation() : $doc, 'doc' => $doc, ]; } diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index ae7687324..ab8ab0c67 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -581,6 +581,7 @@ My accompanying periods in draft: Mes parcours brouillons workflow: Doc for evaluation (n°%eval%): Document de l'évaluation n°%eval% + doc for evaluation deleted: Document supprimé dans une évaluation period_by_user_list: Period by user: Parcours d'accompagnement par utilisateur From f64409e5e63988b3ba11d7af9c67b8f3de99916a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Apr 2022 16:06:26 +0200 Subject: [PATCH 44/68] allow every person which has part for a workflow to see the workflow page --- .../Security/Authorization/EntityWorkflowVoter.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php index d8d74fbb9..82dfc7681 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php @@ -65,7 +65,17 @@ class EntityWorkflowVoter extends Voter return true; } - return $this->security->isGranted($entityAttribute, $relatedEntity); + if ($this->security->isGranted($entityAttribute, $relatedEntity)) { + return true; + } + + foreach ($subject->getSteps() as $step) { + if ($step->getAllDestUser()->contains($token->getUser())) { + return true; + } + } + + return false; case self::DELETE: return $subject->getStep() === 'initial'; From 26a4577420f7a7d62599c72c3a489da82db2cf8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Apr 2022 21:08:11 +0200 Subject: [PATCH 45/68] order query for location and add pagination in list --- .../ChillMainBundle/Controller/LocationController.php | 6 ++++++ .../Resources/views/Location/index.html.twig | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/Bundle/ChillMainBundle/Controller/LocationController.php b/src/Bundle/ChillMainBundle/Controller/LocationController.php index 10087ec1e..97deac9c5 100644 --- a/src/Bundle/ChillMainBundle/Controller/LocationController.php +++ b/src/Bundle/ChillMainBundle/Controller/LocationController.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Controller; use Chill\MainBundle\CRUD\Controller\CRUDController; +use Chill\MainBundle\Pagination\PaginatorInterface; use Symfony\Component\HttpFoundation\Request; class LocationController extends CRUDController @@ -29,4 +30,9 @@ class LocationController extends CRUDController { $query->where('e.availableForUsers = true'); //TODO not working } + + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) + { + return $query->addOrderBy('e.name', 'DESC'); + } } diff --git a/src/Bundle/ChillMainBundle/Resources/views/Location/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Location/index.html.twig index a04ae73a9..aa883c603 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Location/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Location/index.html.twig @@ -47,6 +47,8 @@ + {{ chill_pagination(paginator) }} +
    {% endif %} -
    - {% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %} - {% if notif_counter.total > 0 %} -
    - {{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) }} + + {% if show_pinned_comment|default(false) and period.pinnedComment is not empty%} +
    +
    +

    {{ 'Pinned comment'|trans }}

    +
    + {{ period.pinnedComment.content|u.truncate(750, '…', false)|chill_markdown_to_html }} + {% if period.pinnedComment.content|length > 750 %} + {{ 'Read more'|trans }} + {% endif %} + +
    - {% endif %} - {% if itemMeta is defined %} +
    + {% endif %} + +
    + {% if itemMeta is not defined %} + {% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %} + {% if notif_counter.total > 0 %} +
    + {{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) }} +
    + {% endif %} + {% else %} {{ itemMeta }} {% endif %}
    From 7ffb3dc74ffe4486eb1366c7b3293f3714a5c1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Apr 2022 23:49:32 +0200 Subject: [PATCH 51/68] task: fix route name --- .../ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig index d0a906c9c..d7f923a46 100644 --- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig +++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig @@ -16,7 +16,7 @@
    • - + {{ 'Cancel'|trans }}
    • From 4257a918f30c05848c27f884c7f8463fa24bbb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 8 Apr 2022 00:11:08 +0200 Subject: [PATCH 52/68] fix error when search pattern is empty --- src/Bundle/ChillMainBundle/Controller/SearchController.php | 2 +- .../Repository/PersonACLAwareRepository.php | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Controller/SearchController.php b/src/Bundle/ChillMainBundle/Controller/SearchController.php index b9f5d19d8..41349336a 100644 --- a/src/Bundle/ChillMainBundle/Controller/SearchController.php +++ b/src/Bundle/ChillMainBundle/Controller/SearchController.php @@ -122,7 +122,7 @@ class SearchController extends AbstractController public function searchAction(Request $request, $_format) { - $pattern = $request->query->get('q', ''); + $pattern = trim($request->query->get('q', '')); if ('' === $pattern) { switch ($_format) { diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php index 5ed9762c8..d5dc3cd7d 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php @@ -136,8 +136,11 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac $andWhereSearchClause = []; $andWhereSearchClauseArgs = []; - if ('' !== $default) { + if ('' !== trim($default)) { foreach (explode(' ', $default) as $str) { + if ('' === trim($str)) { + continue; + } $pertinence[] = 'STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), person.fullnamecanonical) + ' . "(person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%')::int + " . From bb65909bfa66543eff7289d17997f504fcc48534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 16:52:11 +0200 Subject: [PATCH 53/68] add docgen context for a list of activities in a course --- .../Repository/ActivityACLAwareRepository.php | 89 ++++++ .../ActivityACLAwareRepositoryInterface.php | 13 + ...tActivitiesByAccompanyingPeriodContext.php | 276 ++++++++++++++++++ .../translations/messages.fr.yml | 2 + 4 files changed, 380 insertions(+) create mode 100644 src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php index 48f750c26..31b293a40 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -12,13 +12,21 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Repository; use Chill\ActivityBundle\Entity\Activity; +use Chill\ActivityBundle\Entity\ActivityPresence; +use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; +use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; +use Chill\MainBundle\Entity\Location; +use Chill\MainBundle\Entity\LocationType; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; +use Doctrine\DBAL\Types\Types; +use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Query\ResultSetMappingBuilder; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Security; @@ -72,6 +80,87 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte ->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy); } + public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array + { + $rsm = new ResultSetMappingBuilder($this->em); + + $sql = " + SELECT + a.id AS activity_id, + date, + CASE WHEN durationtime IS NOT NULL THEN (EXTRACT(EPOCH from durationtime) / 60)::int ELSE 0 END AS durationtimeminute, + attendee_id, + comment_comment, + emergency, + sentreceived, + CASE WHEN traveltime IS NOT NULL THEN (EXTRACT(EPOCH from traveltime) / 60)::int ELSE 0 END AS traveltimeminute, + t.id AS type_id, t.name as type_name, + p.id AS presence_id, p.name AS presence_name, + location.id AS location_id, location.address_id, location.name AS location_name, location.phonenumber1, location.phonenumber2, location.email, + location.locationtype_id, locationtype.title AS locationtype_title, + users.userids AS userids, + thirdparties.thirdpartyids, + persons.personids, + actions.socialactionids, + issues.socialissueids + + FROM activity a + LEFT JOIN chill_main_location location ON a.location_id = location.id + LEFT JOIN chill_main_location_type locationtype ON location.locationtype_id = locationtype.id + LEFT JOIN activitytpresence p ON a.attendee_id = p.id + LEFT JOIN activitytype t ON a.type_id = t.id + LEFT JOIN LATERAL (SELECT jsonb_agg(user_id) userids, activity_id FROM activity_user AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS users ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(thirdparty_id) thirdpartyids, activity_id FROM activity_thirdparty AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS thirdparties ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(person_id) personids, activity_id FROM activity_person AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS persons ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(socialaction_id) socialactionids, activity_id FROM postgres.public.chill_activity_activity_chill_person_socialaction AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS actions ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(socialissue_id) socialissueids, activity_id FROM postgres.public.chill_activity_activity_chill_person_socialissue AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS issues ON TRUE + + WHERE accompanyingperiod_id = ? + ORDER BY a.date DESC, a.id DESC + LIMIT ? + "; + + $rsm + ->addEntityResult(Activity::class, 'a') + ->addFieldResult('a', 'activity_id', 'id') + ->addFieldResult('a', 'date', 'date') + ->addFieldResult('a', 'comment', 'comment') + ->addFieldResult('a', 'sentreceived', 'sentReceived') + ->addFieldResult('a', 'emergency', 'emergency') + ->addJoinedEntityResult(Location::class, 'location', 'a', 'location') + ->addFieldResult('location', 'location_id', 'id') + ->addFieldResult('location', 'location_name', 'name') + ->addFieldResult('location', 'phonenumber1', 'phonenumber1') + ->addFieldResult('location', 'phonenumber2', 'phonenumber2') + ->addFieldResult('location', 'email', 'email') + ->addJoinedEntityResult(LocationType::class,'locationType', 'location', 'locationType' ) + ->addFieldResult('locationType', 'locationtype_id', 'id') + ->addFieldResult('locationType', 'locationtype_title', 'title') + ->addJoinedEntityResult(ActivityType::class, 'activityType', 'a', 'activityType') + ->addFieldResult('activityType', 'type_id', 'id') + ->addFieldResult('activityType', 'type_name', 'name') + ->addJoinedEntityResult(ActivityPresence::class, 'activityPresence', 'a', 'attendee') + ->addFieldResult('activityPresence', 'presence_id', 'id') + ->addFieldResult('activityPresence', 'presence_name', 'name') + + // results which cannot be mapped into entity + ->addScalarResult('comment_comment', 'comment', Types::TEXT) + ->addScalarResult('userids', 'userIds', Types::JSON) + ->addScalarResult('thirdpartyids', 'thirdPartyIds', Types::JSON) + ->addScalarResult('personids', 'personIds', Types::JSON) + ->addScalarResult('socialactionids', 'socialActionIds', Types::JSON) + ->addScalarResult('socialissueids', 'socialIssueIds', Types::JSON) + ->addScalarResult('durationtimeminute', 'durationTimeMinute', Types::INTEGER) + ->addScalarResult('traveltimeminute', 'travelTimeMinute', Types::INTEGER) + ; + + $nq = $this->em->createNativeQuery($sql, $rsm); + + $nq->setParameter(0, $period->getId())->setParameter(1, $limit); + + return $nq->getResult(AbstractQuery::HYDRATE_ARRAY); + } + /** * @param array $orderBy * diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php index 56fb112f9..a6f8fe934 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php @@ -21,6 +21,19 @@ interface ActivityACLAwareRepositoryInterface */ public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array; + /** + * Return a list of activities, simplified as array (not object). + * + * The aim of this method is to get a long list of activities and keep performance. + * + * @param AccompanyingPeriod $period + * @param string $role + * @param int|null $limit + * @param array|null $orderBy + * @return array an array of array, each item representing an activity + */ + public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array; + /** * @return Activity[]|array */ diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php new file mode 100644 index 000000000..3868545d9 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php @@ -0,0 +1,276 @@ +accompanyingPeriodContext = $accompanyingPeriodContext; + $this->activityACLAwareRepository = $activityACLAwareRepository; + $this->normalizer = $normalizer; + $this->personRepository = $personRepository; + $this->socialActionRepository = $socialActionRepository; + $this->socialIssueRepository = $socialIssueRepository; + $this->thirdPartyRepository = $thirdPartyRepository; + $this->translatableStringHelper = $translatableStringHelper; + $this->userRepository = $userRepository; + } + + public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array + { + $data = $this->accompanyingPeriodContext->getData($template, $entity, $contextGenerationData); + + $data['activities'] = $this->getActivitiesSimplified($entity); + + return $data; + } + + private function getActivitiesSimplified(AccompanyingPeriod $period) + { + $activities = + $this->activityACLAwareRepository->findByAccompanyingPeriodSimplified($period); + $results = []; + + foreach ($activities as $row) { + $activity = $row[0]; + + $activity['date'] = $this->normalizer->normalize($activity['date'], 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => \DateTime::class + ]); + + if (null === $activity['location']) { + $activity['location'] = $this->normalizer->normalize(null, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Location::class + ]); + $activity['location']['type'] = 'location'; + } else { + $activity['location']['isNull'] = false; + $activity['location']['type'] = 'location'; + foreach (['1', '2'] as $key) { + $activity['location']['phonenumber'.$key] = $this->normalizer->normalize( + $activity['location']['phonenumber'.$key], + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => PhoneNumber::class] + ); + } + } + + if (is_numeric($activity['location']['locationType']['id'])) { + $activity['location']['locationType']['title'] = $this->translatableStringHelper->localize( + $activity['location']['locationType']['title'] + ); + $activity['location']['locationType']['isNull'] = false; + $activity['location']['locationType']['type'] = 'locationType'; + } + + if (null !== $activity['activityType']) { + $activity['activityType']['name'] = $this->translatableStringHelper->localize( + $activity['activityType']['name'] + ); + $activity['activityType']['isNull'] = false; + $activity['activityType']['type'] = 'activityType'; + } else { + $activity['activityType'] = $this->normalizer->normalize(null, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityType::class + ]); + } + + if (null !== $activity['attendee']) { + $activity['attendee']['name'] = $this->translatableStringHelper->localize( + $activity['attendee']['name'] + ); + $activity['attendee']['isNull'] = false; + $activity['attendee']['type'] = 'activityPresence'; + } else { + $activity['attendee'] = $this->normalizer->normalize(null, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityPresence::class + ]); + } + + $activity['comment'] = (string) $row['comment']; + $activity['travelTimeMinute'] = $row['travelTimeMinute']; + $activity['durationTimeMinute'] = $row['durationTimeMinute']; + + if (null !== $row['userIds']) { + foreach ($row['userIds'] as $id) { + $activity['users'][] = $this->normalizer->normalize( + $this->userRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => User::class] + ); + } + } else { + $activity['users'] = []; + } + + if (null !== $row['personIds']) { + foreach ($row['personIds'] as $id) { + $activity['persons'][] = $this->normalizer->normalize( + $this->personRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Person::class] + ); + } + } else { + $activity['persons'] = []; + } + + if (null !== $row['thirdPartyIds']) { + foreach ($row['thirdPartyIds'] as $id) { + $activity['thirdParties'][] = $this->normalizer->normalize( + $this->thirdPartyRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ThirdParty::class] + ); + } + } else { + $activity['thirdParties'] = []; + } + + if (null !== $row['socialActionIds']) { + foreach ($row['socialActionIds'] as $id) { + $activity['socialActions'][] = $this->normalizer->normalize( + $this->socialActionRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => SocialAction::class] + ); + } + } else { + $activity['socialActions'] = []; + } + + if (null !== $row['socialIssueIds']) { + foreach ($row['socialIssueIds'] as $id) { + $activity['socialIssues'][] = $this->normalizer->normalize( + $this->socialIssueRepository->find($id), + 'docgen', + [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => SocialIssue::class] + ); + } + } else { + $activity['socialIssues'] = []; + } + + $results[] = $activity; + } + + return $results; + } + + public function getDescription(): string + { + return 'docgen.Accompanying period with a list of activities description'; + } + + public function getEntityClass(): string + { + return AccompanyingPeriod::class; + } + + public static function getKey(): string + { + return self::class; + } + + public function getName(): string + { + return 'docgen.Accompanying period with a list of activities'; + } + + public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void + { + $this->accompanyingPeriodContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData); + } + + public function adminFormReverseTransform(array $data): array + { + return $this->accompanyingPeriodContext->adminFormReverseTransform($data); + } + + public function adminFormTransform(array $data): array + { + return $this->accompanyingPeriodContext->adminFormTransform($data); + } + + public function buildAdminForm(FormBuilderInterface $builder): void + { + $this->accompanyingPeriodContext->buildAdminForm($builder); + } + + public function hasAdminForm(): bool + { + return $this->accompanyingPeriodContext->hasAdminForm(); + } + + public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void + { + $this->accompanyingPeriodContext->buildPublicForm($builder, $template, $entity); + } + + public function getFormData(DocGeneratorTemplate $template, $entity): array + { + return $this->accompanyingPeriodContext->getFormData($template, $entity); + } + + public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool + { + return $this->accompanyingPeriodContext->hasPublicForm($template, $entity); + } + +} diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml index 051497b69..49217dd5d 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -232,3 +232,5 @@ This is the minimal activity data: Activité n° docgen: Activity basic: Echange A basic context for activity: Contexte pour les échanges + Accompanying period with a list of activities: Parcours d'accompagnement avec liste des échanges + Accompanying period with a list of activities description: Ce contexte reprend les informations du parcours, et tous les échanges pour un parcours. Les échanges ne sont pas filtrés. From 9765bc5663402447fa25e842527d7a162d217082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 17:12:59 +0200 Subject: [PATCH 54/68] fix cs --- .../Repository/ActivityACLAwareRepository.php | 10 +- .../ActivityACLAwareRepositoryInterface.php | 4 - ...tActivitiesByAccompanyingPeriodContext.php | 154 ++++++++++-------- 3 files changed, 86 insertions(+), 82 deletions(-) diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php index 31b293a40..2778945eb 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -15,7 +15,6 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityPresence; use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; -use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\LocationType; use Chill\MainBundle\Entity\Scope; @@ -84,7 +83,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte { $rsm = new ResultSetMappingBuilder($this->em); - $sql = " + $sql = ' SELECT a.id AS activity_id, date, @@ -118,7 +117,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte WHERE accompanyingperiod_id = ? ORDER BY a.date DESC, a.id DESC LIMIT ? - "; + '; $rsm ->addEntityResult(Activity::class, 'a') @@ -133,7 +132,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte ->addFieldResult('location', 'phonenumber1', 'phonenumber1') ->addFieldResult('location', 'phonenumber2', 'phonenumber2') ->addFieldResult('location', 'email', 'email') - ->addJoinedEntityResult(LocationType::class,'locationType', 'location', 'locationType' ) + ->addJoinedEntityResult(LocationType::class, 'locationType', 'location', 'locationType') ->addFieldResult('locationType', 'locationtype_id', 'id') ->addFieldResult('locationType', 'locationtype_title', 'title') ->addJoinedEntityResult(ActivityType::class, 'activityType', 'a', 'activityType') @@ -151,8 +150,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte ->addScalarResult('socialactionids', 'socialActionIds', Types::JSON) ->addScalarResult('socialissueids', 'socialIssueIds', Types::JSON) ->addScalarResult('durationtimeminute', 'durationTimeMinute', Types::INTEGER) - ->addScalarResult('traveltimeminute', 'travelTimeMinute', Types::INTEGER) - ; + ->addScalarResult('traveltimeminute', 'travelTimeMinute', Types::INTEGER); $nq = $this->em->createNativeQuery($sql, $rsm); diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php index a6f8fe934..1fc6d22b1 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php @@ -26,10 +26,6 @@ interface ActivityACLAwareRepositoryInterface * * The aim of this method is to get a long list of activities and keep performance. * - * @param AccompanyingPeriod $period - * @param string $role - * @param int|null $limit - * @param array|null $orderBy * @return array an array of array, each item representing an activity */ public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array; diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php index 3868545d9..0a442cec7 100644 --- a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php +++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php @@ -1,5 +1,14 @@ userRepository = $userRepository; } + public function adminFormReverseTransform(array $data): array + { + return $this->accompanyingPeriodContext->adminFormReverseTransform($data); + } + + public function adminFormTransform(array $data): array + { + return $this->accompanyingPeriodContext->adminFormTransform($data); + } + + public function buildAdminForm(FormBuilderInterface $builder): void + { + $this->accompanyingPeriodContext->buildAdminForm($builder); + } + + public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void + { + $this->accompanyingPeriodContext->buildPublicForm($builder, $template, $entity); + } + public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array { $data = $this->accompanyingPeriodContext->getData($template, $entity, $contextGenerationData); @@ -81,6 +111,46 @@ class ListActivitiesByAccompanyingPeriodContext implements return $data; } + public function getDescription(): string + { + return 'docgen.Accompanying period with a list of activities description'; + } + + public function getEntityClass(): string + { + return AccompanyingPeriod::class; + } + + public function getFormData(DocGeneratorTemplate $template, $entity): array + { + return $this->accompanyingPeriodContext->getFormData($template, $entity); + } + + public static function getKey(): string + { + return self::class; + } + + public function getName(): string + { + return 'docgen.Accompanying period with a list of activities'; + } + + public function hasAdminForm(): bool + { + return $this->accompanyingPeriodContext->hasAdminForm(); + } + + public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool + { + return $this->accompanyingPeriodContext->hasPublicForm($template, $entity); + } + + public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void + { + $this->accompanyingPeriodContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData); + } + private function getActivitiesSimplified(AccompanyingPeriod $period) { $activities = @@ -91,20 +161,21 @@ class ListActivitiesByAccompanyingPeriodContext implements $activity = $row[0]; $activity['date'] = $this->normalizer->normalize($activity['date'], 'docgen', [ - AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => \DateTime::class + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => DateTime::class, ]); if (null === $activity['location']) { - $activity['location'] = $this->normalizer->normalize(null, 'docgen', [ - AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Location::class + $activity['location'] = $this->normalizer->normalize(null, 'docgen', [ + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Location::class, ]); $activity['location']['type'] = 'location'; } else { $activity['location']['isNull'] = false; $activity['location']['type'] = 'location'; + foreach (['1', '2'] as $key) { - $activity['location']['phonenumber'.$key] = $this->normalizer->normalize( - $activity['location']['phonenumber'.$key], + $activity['location']['phonenumber' . $key] = $this->normalizer->normalize( + $activity['location']['phonenumber' . $key], 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => PhoneNumber::class] ); @@ -127,7 +198,7 @@ class ListActivitiesByAccompanyingPeriodContext implements $activity['activityType']['type'] = 'activityType'; } else { $activity['activityType'] = $this->normalizer->normalize(null, 'docgen', [ - AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityType::class + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityType::class, ]); } @@ -139,7 +210,7 @@ class ListActivitiesByAccompanyingPeriodContext implements $activity['attendee']['type'] = 'activityPresence'; } else { $activity['attendee'] = $this->normalizer->normalize(null, 'docgen', [ - AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityPresence::class + AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityPresence::class, ]); } @@ -212,65 +283,4 @@ class ListActivitiesByAccompanyingPeriodContext implements return $results; } - - public function getDescription(): string - { - return 'docgen.Accompanying period with a list of activities description'; - } - - public function getEntityClass(): string - { - return AccompanyingPeriod::class; - } - - public static function getKey(): string - { - return self::class; - } - - public function getName(): string - { - return 'docgen.Accompanying period with a list of activities'; - } - - public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void - { - $this->accompanyingPeriodContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData); - } - - public function adminFormReverseTransform(array $data): array - { - return $this->accompanyingPeriodContext->adminFormReverseTransform($data); - } - - public function adminFormTransform(array $data): array - { - return $this->accompanyingPeriodContext->adminFormTransform($data); - } - - public function buildAdminForm(FormBuilderInterface $builder): void - { - $this->accompanyingPeriodContext->buildAdminForm($builder); - } - - public function hasAdminForm(): bool - { - return $this->accompanyingPeriodContext->hasAdminForm(); - } - - public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void - { - $this->accompanyingPeriodContext->buildPublicForm($builder, $template, $entity); - } - - public function getFormData(DocGeneratorTemplate $template, $entity): array - { - return $this->accompanyingPeriodContext->getFormData($template, $entity); - } - - public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool - { - return $this->accompanyingPeriodContext->hasPublicForm($template, $entity); - } - } From 7fbb3dfd07d10f03e16d513f264d208525368354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 17:43:31 +0200 Subject: [PATCH 55/68] docgen: add more persons choices in person1, person2 and mainPerson Allow to pick amongst: * requestor (if person) * resources of course (if person) * resources of person (if person) --- .../AccompanyingPeriodContext.php | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php b/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php index 495989b29..dc6e9c912 100644 --- a/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php +++ b/src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php @@ -152,8 +152,25 @@ class AccompanyingPeriodContext implements $options = $template->getOptions(); $persons = $entity->getCurrentParticipations()->map(static function (AccompanyingPeriodParticipation $p) { return $p->getPerson(); - }) - ->toArray(); + }); + + foreach ($entity->getCurrentParticipations() as $p) { + foreach ($p->getPerson()->getResources() as $r) { + if (null !== $r->getPerson() && !$persons->contains($r->getPerson())) { + $persons->add($r->getPerson()); + } + } + } + + if (null !== $entity->getRequestorPerson() && !$persons->contains($entity->getRequestorPerson())) { + $persons->add($entity->getRequestorPerson()); + } + + foreach ($entity->getResources() as $r) { + if (null !== $r->getPerson() && !$persons->contains($r->getPerson())) { + $persons->add($r->getPerson()); + } + } foreach (['mainPerson', 'person1', 'person2'] as $key) { if ($options[$key] ?? false) { From 910245f8557abaffe020936ec02a84cc38494512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 17:45:57 +0200 Subject: [PATCH 56/68] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index abaa3fdd8..dcf61d70e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to ## Unreleased +* [docgen] add more persons choices in docgen for course: amongst requestor (if person), resources of course (if person), and PersonResource (if person); +* [docgen] add a new context with a list of activities in course ## Test releases From 69b2dce7ee8b289f52c53769099bf28fdbf69712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 18:10:10 +0200 Subject: [PATCH 57/68] docgen/budget: add comment for each budget line (resource and charge) The comments are cocatenated by lines of each type, and separated by a `|` --- CHANGELOG.md | 1 + .../Service/Summary/SummaryBudget.php | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcf61d70e..a61c63a73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to * [docgen] add more persons choices in docgen for course: amongst requestor (if person), resources of course (if person), and PersonResource (if person); * [docgen] add a new context with a list of activities in course +* [docgen] add a comment in budget lines ## Test releases diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php index 8e46feb7b..63cb69ad5 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php @@ -25,13 +25,13 @@ use function count; */ class SummaryBudget implements SummaryBudgetInterface { - private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, type FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; - private const QUERY_CHARGE_BY_PERSON = 'select SUM(amount) AS sum, type FROM chill_budget.charge WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_CHARGE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.charge WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; - private const QUERY_RESOURCE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, type FROM chill_budget.resource WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_RESOURCE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.resource WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; - private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, type FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; private array $chargeLabels; @@ -110,7 +110,9 @@ class SummaryBudget implements SummaryBudgetInterface $rsm = new ResultSetMapping(); $rsm ->addScalarResult('sum', 'sum') - ->addScalarResult('type', 'type'); + ->addScalarResult('type', 'type') + ->addScalarResult('comment', 'comment') + ; return $rsm; } @@ -121,7 +123,7 @@ class SummaryBudget implements SummaryBudgetInterface $labels = $this->chargeLabels; return array_combine($keys, array_map(function ($i) use ($labels) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i])]; + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), 'comment' => '']; }, $keys)); } @@ -131,7 +133,7 @@ class SummaryBudget implements SummaryBudgetInterface $labels = $this->resourcesLabels; return array_combine($keys, array_map(function ($i) use ($labels) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i])]; + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), 'comment' => '']; }, $keys)); } @@ -158,6 +160,7 @@ class SummaryBudget implements SummaryBudgetInterface $result[$row['type']] = [ 'sum' => (float) $row['sum'], 'label' => $this->translatableStringHelper->localize($label[$row['type']]), + 'comment' => (string) $row['comment'], ]; } From 01c571ab06e610427b2bcec36c8d85c2e7737122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 11 Apr 2022 21:04:36 +0200 Subject: [PATCH 58/68] fix path to table --- .../Repository/ActivityACLAwareRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php index 2778945eb..d960a0c21 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -111,8 +111,8 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte LEFT JOIN LATERAL (SELECT jsonb_agg(user_id) userids, activity_id FROM activity_user AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS users ON TRUE LEFT JOIN LATERAL (SELECT jsonb_agg(thirdparty_id) thirdpartyids, activity_id FROM activity_thirdparty AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS thirdparties ON TRUE LEFT JOIN LATERAL (SELECT jsonb_agg(person_id) personids, activity_id FROM activity_person AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS persons ON TRUE - LEFT JOIN LATERAL (SELECT jsonb_agg(socialaction_id) socialactionids, activity_id FROM postgres.public.chill_activity_activity_chill_person_socialaction AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS actions ON TRUE - LEFT JOIN LATERAL (SELECT jsonb_agg(socialissue_id) socialissueids, activity_id FROM postgres.public.chill_activity_activity_chill_person_socialissue AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS issues ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(socialaction_id) socialactionids, activity_id FROM chill_activity_activity_chill_person_socialaction AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS actions ON TRUE + LEFT JOIN LATERAL (SELECT jsonb_agg(socialissue_id) socialissueids, activity_id FROM chill_activity_activity_chill_person_socialissue AS au WHERE a.id = au.activity_id GROUP BY activity_id) AS issues ON TRUE WHERE accompanyingperiod_id = ? ORDER BY a.date DESC, a.id DESC From ccf7c885bbde5d318a9157c399cffc7e5dc6b2e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 09:44:21 +0200 Subject: [PATCH 59/68] fix normalization for phonenumber on person when phonenumber is null --- .../Service/Summary/SummaryBudget.php | 3 +- .../Normalizer/PhonenumberNormalizerTest.php | 54 +++++++++++++++++++ .../Normalizer/PersonDocGenNormalizer.php | 8 +-- 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/PhonenumberNormalizerTest.php diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php index 63cb69ad5..243cd4749 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php @@ -111,8 +111,7 @@ class SummaryBudget implements SummaryBudgetInterface $rsm ->addScalarResult('sum', 'sum') ->addScalarResult('type', 'type') - ->addScalarResult('comment', 'comment') - ; + ->addScalarResult('comment', 'comment'); return $rsm; } diff --git a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/PhonenumberNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/PhonenumberNormalizerTest.php new file mode 100644 index 000000000..1e0d4ef29 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/PhonenumberNormalizerTest.php @@ -0,0 +1,54 @@ +parse('+32486123465'), 'docgen', ['docgen:expects' => PhoneNumber::class], '0486 12 34 65']; + + yield [null, 'docgen', ['docgen:expects' => PhoneNumber::class], '']; + } + + /** + * @dataProvider dataProviderNormalizePhonenumber + * + * @param mixed $format + * @param mixed $context + * @param mixed $expected + */ + public function testNormalize(?Phonenumber $phonenumber, $format, $context, $expected) + { + $parameterBag = $this->prophesize(ParameterBagInterface::class); + $parameterBag->get(Argument::exact('chill_main'))->willReturn(['phone_helper' => ['default_carrier_code' => 'BE']]); + $normalizer = new PhonenumberNormalizer($parameterBag->reveal()); + + $this->assertEquals($expected, $normalizer->normalize($phonenumber, $format, $context)); + } +} diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php index 490a7e2af..89f01bf94 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonDocGenNormalizer.php @@ -23,6 +23,7 @@ use Chill\PersonBundle\Repository\Relationships\RelationshipRepository; use Chill\PersonBundle\Templating\Entity\PersonRenderInterface; use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; +use libphonenumber\PhoneNumber; use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; @@ -71,6 +72,7 @@ class PersonDocGenNormalizer implements $dateContext = $context; $dateContext['docgen:expects'] = DateTimeInterface::class; $addressContext = array_merge($context, ['docgen:expects' => Address::class]); + $phonenumberContext = array_merge($context, ['docgen:expects' => PhoneNumber::class]); $personResourceContext = array_merge($context, [ 'docgen:expects' => Person\PersonResource::class, // we simplify the list of attributes for the embedded persons @@ -113,9 +115,9 @@ class PersonDocGenNormalizer implements 'maritalStatus' => null !== ($ms = $person->getMaritalStatus()) ? $this->translatableStringHelper->localize($ms->getName()) : '', 'maritalStatusDate' => $this->normalizer->normalize($person->getMaritalStatusDate(), $format, $dateContext), 'email' => $person->getEmail(), - 'firstPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber() ?? $person->getMobilenumber(), $format, $context), - 'fixPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber(), $format, $context), - 'mobilePhoneNumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $context), + 'firstPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber() ?? $person->getMobilenumber(), $format, $phonenumberContext), + 'fixPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber(), $format, $phonenumberContext), + 'mobilePhoneNumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $phonenumberContext), 'nationality' => null !== ($c = $person->getNationality()) ? $this->translatableStringHelper->localize($c->getName()) : '', 'placeOfBirth' => $person->getPlaceOfBirth(), 'memo' => $person->getMemo(), From ef9fd80ad59144327fc76ed68d4aa80760b6ad0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 18:02:11 +0200 Subject: [PATCH 60/68] update schema to send to emails --- .../ChillMainBundle/Entity/Notification.php | 13 +++++++ .../migrations/Version20220413154743.php | 39 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20220413154743.php diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index b2fe40c60..5257a7a90 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -105,12 +105,25 @@ class Notification implements TrackUpdateInterface */ private ?User $updatedBy; + /** + * a list of destinee which will receive notifications + * @var array|string[] + * @ORM\Column(type="json") + */ + public array $adressesEmails = []; + + /** + * @ORM\Column(type="text", nullable=false) + */ + private string $accessKey; + public function __construct() { $this->addressees = new ArrayCollection(); $this->unreadBy = new ArrayCollection(); $this->comments = new ArrayCollection(); $this->setDate(new DateTimeImmutable()); + $this->accessKey = bin2hex(openssl_random_pseudo_bytes(24)); } public function addAddressee(User $addressee): self diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php b/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php new file mode 100644 index 000000000..88a31c92c --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php @@ -0,0 +1,39 @@ +addSql('ALTER TABLE chill_main_notification ADD adressesEmails JSON NOT NULL DEFAULT \'[]\';'); + $this->addSql('ALTER TABLE chill_main_notification ADD accessKey TEXT DEFAULT NULL'); + $this->addSql('WITH randoms AS (select + n.id, + string_agg(substr(characters, (random() * length(characters) + 0.5)::integer, 1), \'\') as random_word + from (values(\'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\')) as symbols(characters) + -- length of word + join generate_series(1, 16) on 1 = 1 + JOIN chill_main_notification n ON true + GROUP BY n.id) + UPDATE chill_main_notification SET accessKey = randoms.random_word FROM randoms WHERE chill_main_notification.id = randoms.id'); + $this->addSql('ALTER TABLE chill_main_notification ALTER accessKey DROP DEFAULT'); + $this->addSql('ALTER TABLE chill_main_notification ALTER accessKey SET NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_notification DROP adressesEmails'); + $this->addSql('ALTER TABLE chill_main_notification DROP accessKey'); + } +} From a8db07a3836d92bcec9bd46292d47437f59abe4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 21:33:18 +0200 Subject: [PATCH 61/68] notification / add email: fix entity Notification --- .../ChillMainBundle/Entity/Notification.php | 49 ++++++++++++++++++- .../Tests/Entity/NotificationTest.php | 18 +++++++ .../migrations/Version20220413154743.php | 2 +- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index 5257a7a90..9b2dded40 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -110,7 +110,14 @@ class Notification implements TrackUpdateInterface * @var array|string[] * @ORM\Column(type="json") */ - public array $adressesEmails = []; + private array $addressesEmails = []; + + /** + * a list of emails adresses which were added to the notification + * + * @var array|string[] + */ + private array $addressesEmailsAdded = []; /** * @ORM\Column(type="text", nullable=false) @@ -126,6 +133,46 @@ class Notification implements TrackUpdateInterface $this->accessKey = bin2hex(openssl_random_pseudo_bytes(24)); } + /** + * @return array|string[] + */ + public function getAddressesEmails(): array + { + return $this->addressesEmails; + } + + /** + * @return array|string[] + */ + public function getAddressesEmailsAdded(): array + { + return $this->addressesEmailsAdded; + } + + /** + * @return string + */ + public function getAccessKey(): string + { + return $this->accessKey; + } + + public function addAddressesEmail(string $email) + { + if (!in_array($email, $this->addressesEmails)) { + $this->addressesEmails[] = $email; + $this->addressesEmailsAdded[] = $email; + } + } + + public function removeAddressesEmail(string $email) + { + if (in_array($email, $this->addressesEmails)) { + $this->addressesEmails = array_filter($this->addressesEmails, fn ($e) => $e !== $email); + $this->addressesEmailsAdded = array_filter($this->addressesEmailsAdded, fn ($e) => $e !== $email); + } + } + public function addAddressee(User $addressee): self { if (!$this->addressees->contains($addressee)) { diff --git a/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php b/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php index 8110fa11d..9fc0d6cb2 100644 --- a/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php @@ -122,4 +122,22 @@ final class NotificationTest extends KernelTestCase $this->assertContains($addresseeId, $unreadIds); } } + + public function testAddressesEmail(): void + { + $notification = new Notification(); + + $notification->addAddressesEmail('test'); + $notification->addAddressesEmail('other'); + + $this->assertContains('test', $notification->getAddressesEmails()); + $this->assertContains('other', $notification->getAddressesEmails()); + $this->assertContains('test', $notification->getAddressesEmailsAdded()); + $this->assertContains('other', $notification->getAddressesEmailsAdded()); + + $notification->removeAddressesEmail('other'); + + $this->assertNotContains('other', $notification->getAddressesEmails()); + $this->assertNotContains('other', $notification->getAddressesEmailsAdded()); + } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php b/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php index 88a31c92c..87da6a7f8 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php @@ -16,7 +16,7 @@ final class Version20220413154743 extends AbstractMigration public function up(Schema $schema): void { - $this->addSql('ALTER TABLE chill_main_notification ADD adressesEmails JSON NOT NULL DEFAULT \'[]\';'); + $this->addSql('ALTER TABLE chill_main_notification ADD addressesEmails JSON NOT NULL DEFAULT \'[]\';'); $this->addSql('ALTER TABLE chill_main_notification ADD accessKey TEXT DEFAULT NULL'); $this->addSql('WITH randoms AS (select n.id, From 4425f2ad4998bf97d174f2f86a8d4a397f623b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 21:43:41 +0200 Subject: [PATCH 62/68] fix type for Notification email addresses --- src/Bundle/ChillMainBundle/Entity/Notification.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index 9b2dded40..62994497e 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -159,7 +159,7 @@ class Notification implements TrackUpdateInterface public function addAddressesEmail(string $email) { - if (!in_array($email, $this->addressesEmails)) { + if (!in_array($email, $this->addressesEmails, true)) { $this->addressesEmails[] = $email; $this->addressesEmailsAdded[] = $email; } @@ -167,7 +167,7 @@ class Notification implements TrackUpdateInterface public function removeAddressesEmail(string $email) { - if (in_array($email, $this->addressesEmails)) { + if (in_array($email, $this->addressesEmails, true)) { $this->addressesEmails = array_filter($this->addressesEmails, fn ($e) => $e !== $email); $this->addressesEmailsAdded = array_filter($this->addressesEmailsAdded, fn ($e) => $e !== $email); } From 24d28b0a5252201fc7ab6ed4dccdf07ab0653816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 22:11:01 +0200 Subject: [PATCH 63/68] notification: alter form type to add and remove email addresses --- .../ChillMainBundle/Entity/Notification.php | 17 +++++++++++++- .../ChillMainBundle/Form/NotificationType.php | 23 +++++++++++++++++++ .../views/Notification/create.html.twig | 4 +++- .../views/Notification/edit.html.twig | 2 ++ .../translations/messages.fr.yml | 5 ++++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index 62994497e..9dd3694b1 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -18,6 +18,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @ORM\Entity @@ -36,7 +37,6 @@ class Notification implements TrackUpdateInterface /** * @ORM\ManyToMany(targetEntity=User::class) * @ORM\JoinTable(name="chill_main_notification_addresses_user") - * @Assert\Count(min="1", minMessage="notification.At least one addressee") */ private Collection $addressees; @@ -400,4 +400,19 @@ class Notification implements TrackUpdateInterface return $this; } + + /** + * @Assert\Callback() + * @param ExecutionContextInterface $context + * @param array $payload + * @return void + */ + public function assertCountAddresses(ExecutionContextInterface $context, $payload): void + { + if (0 === (count($this->getAddressesEmails()) + count($this->getAddressees()))) { + $context->buildViolation('notification.At least one addressee') + ->atPath('addressees') + ->addViolation(); + } + } } diff --git a/src/Bundle/ChillMainBundle/Form/NotificationType.php b/src/Bundle/ChillMainBundle/Form/NotificationType.php index b24513524..d0c8bc2cd 100644 --- a/src/Bundle/ChillMainBundle/Form/NotificationType.php +++ b/src/Bundle/ChillMainBundle/Form/NotificationType.php @@ -12,12 +12,17 @@ declare(strict_types=1); namespace Chill\MainBundle\Form; use Chill\MainBundle\Entity\Notification; +use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\PickUserDynamicType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\Email; +use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\NotNull; class NotificationType extends AbstractType { @@ -30,9 +35,27 @@ class NotificationType extends AbstractType ]) ->add('addressees', PickUserDynamicType::class, [ 'multiple' => true, + 'required' => false, ]) ->add('message', ChillTextareaType::class, [ 'required' => false, + ]) + ->add('addressesEmails', ChillCollectionType::class, [ + 'label' => 'notification.dest by email', + 'help' => 'notification.dest by email help', + 'by_reference' => false, + 'allow_add' => true, + 'allow_delete' => true, + 'entry_type' => EmailType::class, + 'button_add_label' => 'notification.Add an email', + 'button_remove_label' => 'notification.Remove an email', + 'empty_collection_explain' => 'notification.Any email', + 'entry_options' => [ + 'constraints' => [ + new NotNull(), new NotBlank(), new Email(['checkMX' => true]), + ], + 'label' => 'Email', + ], ]); } diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/create.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/create.html.twig index f15380083..4dfd340b6 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Notification/create.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/create.html.twig @@ -20,7 +20,9 @@ {{ form_row(form.title, { 'label': 'notification.subject'|trans }) }} {{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }} - + + {{ form_row(form.addressesEmails) }} + {% include handler.template(notification) with handler.templateData(notification) %}
      diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/edit.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/edit.html.twig index b51cc4dab..fd3b3b6bf 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Notification/edit.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/edit.html.twig @@ -21,6 +21,8 @@ {{ form_row(form.title, { 'label': 'notification.subject'|trans }) }} {{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }} + {{ form_row(form.addressesEmails) }} + {% include handler.template(notification) with handler.templateData(notification) %}
      diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 685a0c373..6e9f0374a 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -450,4 +450,9 @@ notification: subject: Objet see_comments_thread: Voir le fil de commentaires associé object_prefix: "[CHILL] notification - " + dest by email: Lien d'accès par email + Any email: Aucun email + Add an email: Ajouter un email + dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Un compte utilisateur sera toujours nécessaire. + Remove an email: Supprimer l'adresse email From a41d6cf7440870c63b4dd626e3181c5a683ef8d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 22:50:32 +0200 Subject: [PATCH 64/68] notification: send an email to addressesEmails --- .../Controller/NotificationController.php | 11 ++++++ .../ChillMainBundle/Entity/Notification.php | 8 ++++ .../Notification/Email/NotificationMailer.php | 38 +++++++++++++++++++ ...m_notification_content_to_email.fr.md.twig | 20 ++++++++++ .../config/services/notification.yaml | 9 +++++ 5 files changed, 86 insertions(+) create mode 100644 src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content_to_email.fr.md.twig diff --git a/src/Bundle/ChillMainBundle/Controller/NotificationController.php b/src/Bundle/ChillMainBundle/Controller/NotificationController.php index af9e1b2b1..0e02c0a82 100644 --- a/src/Bundle/ChillMainBundle/Controller/NotificationController.php +++ b/src/Bundle/ChillMainBundle/Controller/NotificationController.php @@ -65,6 +65,17 @@ class NotificationController extends AbstractController $this->translator = $translator; } + /** + * @Route("/{id}/access_key", name="chill_main_notification_grant_access_by_access_key") + */ + public function getAccessByAccessKey(Notification $notification, Request $request): Response + { + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); + + return new Response('Invalid access key'); + + } + /** * @Route("/create", name="chill_main_notification_create") */ diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index 9dd3694b1..d3d4dd0eb 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -183,6 +183,14 @@ class Notification implements TrackUpdateInterface return $this; } + /** + * @return array + */ + public function getAddedAddresses(): array + { + return $this->addedAddresses; + } + public function addComment(NotificationComment $comment): self { if (!$this->comments->contains($comment)) { diff --git a/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php b/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php index ce5587ed9..69fcf66f0 100644 --- a/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php +++ b/src/Bundle/ChillMainBundle/Notification/Email/NotificationMailer.php @@ -73,6 +73,17 @@ class NotificationMailer * Send a email after a notification is persisted. */ public function postPersistNotification(Notification $notification, LifecycleEventArgs $eventArgs): void + { + $this->sendNotificationEmailsToAddresses($notification); + $this->sendNotificationEmailsToAddressesEmails($notification); + } + + public function postUpdateNotification(Notification $notification, LifecycleEventArgs $eventArgs): void + { + $this->sendNotificationEmailsToAddressesEmails($notification); + } + + private function sendNotificationEmailsToAddresses(Notification $notification): void { foreach ($notification->getAddressees() as $addressee) { if (null === $addressee->getEmail()) { @@ -108,4 +119,31 @@ class NotificationMailer } } } + + private function sendNotificationEmailsToAddressesEmails(Notification $notification): void + { + foreach ($notification->getAddressesEmailsAdded() as $emailAddress) { + $email = new TemplatedEmail(); + $email + ->textTemplate('@ChillMain/Notification/email_non_system_notification_content_to_email.fr.md.twig') + ->context([ + 'notification' => $notification, + 'dest' => $emailAddress, + ]); + + $email + ->subject($notification->getTitle()) + ->to($emailAddress); + + try { + $this->mailer->send($email); + } catch (TransportExceptionInterface $e) { + $this->logger->warning('[NotificationMailer] could not send an email notification', [ + 'to' => $emailAddress, + 'error_message' => $e->getMessage(), + 'error_trace' => $e->getTraceAsString(), + ]); + } + } + } } diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content_to_email.fr.md.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content_to_email.fr.md.twig new file mode 100644 index 000000000..9a32f0c15 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/email_non_system_notification_content_to_email.fr.md.twig @@ -0,0 +1,20 @@ +{{ dest }}, + +{{ notification.sender.label }} a créé une notification pour vous: + +> {{ notification.title }} +> +> +{%- for line in notification.message|split("\n") %} +> {{ line }} +{%- if not loop.last %} +> +{%- endif %} +{%- endfor %} + +Vous pouvez cliquer sur ce lien pour obtenir un accès permanent à la notification: + +{{ absolute_url(path('chill_main_notification_grant_access_by_access_key', {'_locale': 'fr', 'id': notification.id, 'accessKey': notification.accessKey, 'email': dest})) }} + +-- +Le logiciel Chill diff --git a/src/Bundle/ChillMainBundle/config/services/notification.yaml b/src/Bundle/ChillMainBundle/config/services/notification.yaml index b972dced7..544f5544c 100644 --- a/src/Bundle/ChillMainBundle/config/services/notification.yaml +++ b/src/Bundle/ChillMainBundle/config/services/notification.yaml @@ -61,6 +61,15 @@ services: # set the 'lazy' option to TRUE to only instantiate listeners when they are used lazy: true method: 'postPersistNotification' + + - + name: 'doctrine.orm.entity_listener' + event: 'postUpdate' + entity: 'Chill\MainBundle\Entity\Notification' + # set the 'lazy' option to TRUE to only instantiate listeners when they are used + lazy: true + method: 'postUpdateNotification' + - name: 'doctrine.orm.entity_listener' event: 'postPersist' From e7f0cd50c9f712a748ebef7b0567cb421dd42368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 23:05:38 +0200 Subject: [PATCH 65/68] controller to grant access to notification by access key --- .../Controller/NotificationController.php | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Controller/NotificationController.php b/src/Bundle/ChillMainBundle/Controller/NotificationController.php index 0e02c0a82..5c1508321 100644 --- a/src/Bundle/ChillMainBundle/Controller/NotificationController.php +++ b/src/Bundle/ChillMainBundle/Controller/NotificationController.php @@ -22,6 +22,7 @@ use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Repository\NotificationRepository; use Chill\MainBundle\Security\Authorization\NotificationVoter; use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -39,6 +40,10 @@ class NotificationController extends AbstractController { private EntityManagerInterface $em; + private LoggerInterface $chillLogger; + + private LoggerInterface $logger; + private NotificationHandlerManager $notificationHandlerManager; private NotificationRepository $notificationRepository; @@ -51,6 +56,8 @@ class NotificationController extends AbstractController public function __construct( EntityManagerInterface $em, + LoggerInterface $chillLogger, + LoggerInterface $logger, Security $security, NotificationRepository $notificationRepository, NotificationHandlerManager $notificationHandlerManager, @@ -58,6 +65,8 @@ class NotificationController extends AbstractController TranslatorInterface $translator ) { $this->em = $em; + $this->logger = $logger; + $this->chillLogger = $chillLogger; $this->security = $security; $this->notificationRepository = $notificationRepository; $this->notificationHandlerManager = $notificationHandlerManager; @@ -72,8 +81,40 @@ class NotificationController extends AbstractController { $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); - return new Response('Invalid access key'); + if (!$this->security->getUser() instanceof User) { + throw new AccessDeniedHttpException('You must be authenticated and a user to create a notification'); + } + foreach (['accessKey', 'email'] as $param) { + if (!$request->query->has($param)) { + throw new BadRequestHttpException("Missing $param parameter"); + } + } + + if ($notification->getAccessKey() !== $request->query->getAlnum('accessKey')) { + throw new AccessDeniedHttpException('access key is invalid'); + } + + if (!in_array($request->query->get('email'), $notification->getAddressesEmails())) { + return (new Response('The email address is no more associated with this notification')) + ->setStatusCode(Response::HTTP_FORBIDDEN); + } + + $notification->addAddressee($this->security->getUser()); + + $this->getDoctrine()->getManager()->flush(); + + $logMsg = '[Notification] a user is granted access to notification trough an access key'; + $context = [ + 'notificationId' => $notification->getId(), + 'email' => $request->query->get('email'), + 'user' => $this->security->getUser()->getId(), + ]; + + $this->logger->info($logMsg, $context); + $this->chillLogger->info($logMsg, $context); + + return $this->redirectToRoute('chill_main_notification_show', ['id' => $notification->getId()]); } /** From 2a53fb9341cea1a873aa3c2ee9b016c0f208c6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 23:16:02 +0200 Subject: [PATCH 66/68] show email adresses on notification list --- .../Resources/views/Notification/_list_item.html.twig | 5 +++++ src/Bundle/ChillMainBundle/translations/messages.fr.yml | 1 + 2 files changed, 6 insertions(+) diff --git a/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig index 02a02b4d0..1024d56b3 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Notification/_list_item.html.twig @@ -40,6 +40,11 @@ {{ a|chill_entity_render_string }} {% endfor %} + {% for a in c.notification.addressesEmails %} + + {{ a }} + + {% endfor %}
    • {% endif %}
    diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 6e9f0374a..452ad306c 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -455,4 +455,5 @@ notification: Add an email: Ajouter un email dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Un compte utilisateur sera toujours nécessaire. Remove an email: Supprimer l'adresse email + Email with access link: Adresse email ayant reçu un lien d'accès From 35c7d55b8c9d45640254296af34ff6c817724fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 23:17:16 +0200 Subject: [PATCH 67/68] fix cs --- .../Controller/NotificationController.php | 91 ++++++----- .../ChillMainBundle/Entity/Notification.php | 154 +++++++++--------- .../ChillMainBundle/Form/NotificationType.php | 2 +- .../Tests/Entity/NotificationTest.php | 36 ++-- .../migrations/Version20220413154743.php | 19 ++- 5 files changed, 153 insertions(+), 149 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Controller/NotificationController.php b/src/Bundle/ChillMainBundle/Controller/NotificationController.php index 5c1508321..f40c85ffb 100644 --- a/src/Bundle/ChillMainBundle/Controller/NotificationController.php +++ b/src/Bundle/ChillMainBundle/Controller/NotificationController.php @@ -32,16 +32,17 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Security; use Symfony\Contracts\Translation\TranslatorInterface; +use function in_array; /** * @Route("/{_locale}/notification") */ class NotificationController extends AbstractController { - private EntityManagerInterface $em; - private LoggerInterface $chillLogger; + private EntityManagerInterface $em; + private LoggerInterface $logger; private NotificationHandlerManager $notificationHandlerManager; @@ -74,49 +75,6 @@ class NotificationController extends AbstractController $this->translator = $translator; } - /** - * @Route("/{id}/access_key", name="chill_main_notification_grant_access_by_access_key") - */ - public function getAccessByAccessKey(Notification $notification, Request $request): Response - { - $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); - - if (!$this->security->getUser() instanceof User) { - throw new AccessDeniedHttpException('You must be authenticated and a user to create a notification'); - } - - foreach (['accessKey', 'email'] as $param) { - if (!$request->query->has($param)) { - throw new BadRequestHttpException("Missing $param parameter"); - } - } - - if ($notification->getAccessKey() !== $request->query->getAlnum('accessKey')) { - throw new AccessDeniedHttpException('access key is invalid'); - } - - if (!in_array($request->query->get('email'), $notification->getAddressesEmails())) { - return (new Response('The email address is no more associated with this notification')) - ->setStatusCode(Response::HTTP_FORBIDDEN); - } - - $notification->addAddressee($this->security->getUser()); - - $this->getDoctrine()->getManager()->flush(); - - $logMsg = '[Notification] a user is granted access to notification trough an access key'; - $context = [ - 'notificationId' => $notification->getId(), - 'email' => $request->query->get('email'), - 'user' => $this->security->getUser()->getId(), - ]; - - $this->logger->info($logMsg, $context); - $this->chillLogger->info($logMsg, $context); - - return $this->redirectToRoute('chill_main_notification_show', ['id' => $notification->getId()]); - } - /** * @Route("/create", name="chill_main_notification_create") */ @@ -202,6 +160,49 @@ class NotificationController extends AbstractController ]); } + /** + * @Route("/{id}/access_key", name="chill_main_notification_grant_access_by_access_key") + */ + public function getAccessByAccessKey(Notification $notification, Request $request): Response + { + $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED'); + + if (!$this->security->getUser() instanceof User) { + throw new AccessDeniedHttpException('You must be authenticated and a user to create a notification'); + } + + foreach (['accessKey', 'email'] as $param) { + if (!$request->query->has($param)) { + throw new BadRequestHttpException("Missing {$param} parameter"); + } + } + + if ($notification->getAccessKey() !== $request->query->getAlnum('accessKey')) { + throw new AccessDeniedHttpException('access key is invalid'); + } + + if (!in_array($request->query->get('email'), $notification->getAddressesEmails(), true)) { + return (new Response('The email address is no more associated with this notification')) + ->setStatusCode(Response::HTTP_FORBIDDEN); + } + + $notification->addAddressee($this->security->getUser()); + + $this->getDoctrine()->getManager()->flush(); + + $logMsg = '[Notification] a user is granted access to notification trough an access key'; + $context = [ + 'notificationId' => $notification->getId(), + 'email' => $request->query->get('email'), + 'user' => $this->security->getUser()->getId(), + ]; + + $this->logger->info($logMsg, $context); + $this->chillLogger->info($logMsg, $context); + + return $this->redirectToRoute('chill_main_notification_show', ['id' => $notification->getId()]); + } + /** * @Route("/inbox", name="chill_main_notification_my") */ diff --git a/src/Bundle/ChillMainBundle/Entity/Notification.php b/src/Bundle/ChillMainBundle/Entity/Notification.php index d3d4dd0eb..8dad39d12 100644 --- a/src/Bundle/ChillMainBundle/Entity/Notification.php +++ b/src/Bundle/ChillMainBundle/Entity/Notification.php @@ -19,6 +19,8 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; +use function count; +use function in_array; /** * @ORM\Entity @@ -32,6 +34,11 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; */ class Notification implements TrackUpdateInterface { + /** + * @ORM\Column(type="text", nullable=false) + */ + private string $accessKey; + private array $addedAddresses = []; /** @@ -40,6 +47,21 @@ class Notification implements TrackUpdateInterface */ private Collection $addressees; + /** + * a list of destinee which will receive notifications. + * + * @var array|string[] + * @ORM\Column(type="json") + */ + private array $addressesEmails = []; + + /** + * a list of emails adresses which were added to the notification. + * + * @var array|string[] + */ + private array $addressesEmailsAdded = []; + private ?ArrayCollection $addressesOnLoad = null; /** @@ -105,25 +127,6 @@ class Notification implements TrackUpdateInterface */ private ?User $updatedBy; - /** - * a list of destinee which will receive notifications - * @var array|string[] - * @ORM\Column(type="json") - */ - private array $addressesEmails = []; - - /** - * a list of emails adresses which were added to the notification - * - * @var array|string[] - */ - private array $addressesEmailsAdded = []; - - /** - * @ORM\Column(type="text", nullable=false) - */ - private string $accessKey; - public function __construct() { $this->addressees = new ArrayCollection(); @@ -133,46 +136,6 @@ class Notification implements TrackUpdateInterface $this->accessKey = bin2hex(openssl_random_pseudo_bytes(24)); } - /** - * @return array|string[] - */ - public function getAddressesEmails(): array - { - return $this->addressesEmails; - } - - /** - * @return array|string[] - */ - public function getAddressesEmailsAdded(): array - { - return $this->addressesEmailsAdded; - } - - /** - * @return string - */ - public function getAccessKey(): string - { - return $this->accessKey; - } - - public function addAddressesEmail(string $email) - { - if (!in_array($email, $this->addressesEmails, true)) { - $this->addressesEmails[] = $email; - $this->addressesEmailsAdded[] = $email; - } - } - - public function removeAddressesEmail(string $email) - { - if (in_array($email, $this->addressesEmails, true)) { - $this->addressesEmails = array_filter($this->addressesEmails, fn ($e) => $e !== $email); - $this->addressesEmailsAdded = array_filter($this->addressesEmailsAdded, fn ($e) => $e !== $email); - } - } - public function addAddressee(User $addressee): self { if (!$this->addressees->contains($addressee)) { @@ -183,12 +146,12 @@ class Notification implements TrackUpdateInterface return $this; } - /** - * @return array - */ - public function getAddedAddresses(): array + public function addAddressesEmail(string $email) { - return $this->addedAddresses; + if (!in_array($email, $this->addressesEmails, true)) { + $this->addressesEmails[] = $email; + $this->addressesEmailsAdded[] = $email; + } } public function addComment(NotificationComment $comment): self @@ -210,6 +173,30 @@ class Notification implements TrackUpdateInterface return $this; } + /** + * @Assert\Callback + * + * @param array $payload + */ + public function assertCountAddresses(ExecutionContextInterface $context, $payload): void + { + if (0 === (count($this->getAddressesEmails()) + count($this->getAddressees()))) { + $context->buildViolation('notification.At least one addressee') + ->atPath('addressees') + ->addViolation(); + } + } + + public function getAccessKey(): string + { + return $this->accessKey; + } + + public function getAddedAddresses(): array + { + return $this->addedAddresses; + } + /** * @return Collection|User[] */ @@ -223,6 +210,22 @@ class Notification implements TrackUpdateInterface return $this->addressees; } + /** + * @return array|string[] + */ + public function getAddressesEmails(): array + { + return $this->addressesEmails; + } + + /** + * @return array|string[] + */ + public function getAddressesEmailsAdded(): array + { + return $this->addressesEmailsAdded; + } + public function getComments(): Collection { return $this->comments; @@ -339,6 +342,14 @@ class Notification implements TrackUpdateInterface return $this; } + public function removeAddressesEmail(string $email) + { + if (in_array($email, $this->addressesEmails, true)) { + $this->addressesEmails = array_filter($this->addressesEmails, static fn ($e) => $e !== $email); + $this->addressesEmailsAdded = array_filter($this->addressesEmailsAdded, static fn ($e) => $e !== $email); + } + } + public function removeComment(NotificationComment $comment): self { $this->comments->removeElement($comment); @@ -408,19 +419,4 @@ class Notification implements TrackUpdateInterface return $this; } - - /** - * @Assert\Callback() - * @param ExecutionContextInterface $context - * @param array $payload - * @return void - */ - public function assertCountAddresses(ExecutionContextInterface $context, $payload): void - { - if (0 === (count($this->getAddressesEmails()) + count($this->getAddressees()))) { - $context->buildViolation('notification.At least one addressee') - ->atPath('addressees') - ->addViolation(); - } - } } diff --git a/src/Bundle/ChillMainBundle/Form/NotificationType.php b/src/Bundle/ChillMainBundle/Form/NotificationType.php index d0c8bc2cd..22fd19baf 100644 --- a/src/Bundle/ChillMainBundle/Form/NotificationType.php +++ b/src/Bundle/ChillMainBundle/Form/NotificationType.php @@ -40,7 +40,7 @@ class NotificationType extends AbstractType ->add('message', ChillTextareaType::class, [ 'required' => false, ]) - ->add('addressesEmails', ChillCollectionType::class, [ + ->add('addressesEmails', ChillCollectionType::class, [ 'label' => 'notification.dest by email', 'help' => 'notification.dest by email help', 'by_reference' => false, diff --git a/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php b/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php index 9fc0d6cb2..0575fa52a 100644 --- a/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php @@ -88,6 +88,24 @@ final class NotificationTest extends KernelTestCase $this->assertNotContains($user1, $notification->getUnreadBy()->toArray()); } + public function testAddressesEmail(): void + { + $notification = new Notification(); + + $notification->addAddressesEmail('test'); + $notification->addAddressesEmail('other'); + + $this->assertContains('test', $notification->getAddressesEmails()); + $this->assertContains('other', $notification->getAddressesEmails()); + $this->assertContains('test', $notification->getAddressesEmailsAdded()); + $this->assertContains('other', $notification->getAddressesEmailsAdded()); + + $notification->removeAddressesEmail('other'); + + $this->assertNotContains('other', $notification->getAddressesEmails()); + $this->assertNotContains('other', $notification->getAddressesEmailsAdded()); + } + /** * @dataProvider generateNotificationData */ @@ -122,22 +140,4 @@ final class NotificationTest extends KernelTestCase $this->assertContains($addresseeId, $unreadIds); } } - - public function testAddressesEmail(): void - { - $notification = new Notification(); - - $notification->addAddressesEmail('test'); - $notification->addAddressesEmail('other'); - - $this->assertContains('test', $notification->getAddressesEmails()); - $this->assertContains('other', $notification->getAddressesEmails()); - $this->assertContains('test', $notification->getAddressesEmailsAdded()); - $this->assertContains('other', $notification->getAddressesEmailsAdded()); - - $notification->removeAddressesEmail('other'); - - $this->assertNotContains('other', $notification->getAddressesEmails()); - $this->assertNotContains('other', $notification->getAddressesEmailsAdded()); - } } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php b/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php index 87da6a7f8..174419cda 100644 --- a/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php +++ b/src/Bundle/ChillMainBundle/migrations/Version20220413154743.php @@ -1,5 +1,12 @@ addSql('ALTER TABLE chill_main_notification DROP adressesEmails'); + $this->addSql('ALTER TABLE chill_main_notification DROP accessKey'); + } + public function getDescription(): string { return 'add access keys and emails dest to notifications'; @@ -30,10 +43,4 @@ final class Version20220413154743 extends AbstractMigration $this->addSql('ALTER TABLE chill_main_notification ALTER accessKey DROP DEFAULT'); $this->addSql('ALTER TABLE chill_main_notification ALTER accessKey SET NOT NULL'); } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE chill_main_notification DROP adressesEmails'); - $this->addSql('ALTER TABLE chill_main_notification DROP accessKey'); - } } From 8770188d5487ec368959512d7f233ee3ddceacff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Apr 2022 23:18:50 +0200 Subject: [PATCH 68/68] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a61c63a73..451a672ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to * [docgen] add more persons choices in docgen for course: amongst requestor (if person), resources of course (if person), and PersonResource (if person); * [docgen] add a new context with a list of activities in course * [docgen] add a comment in budget lines +* [notifications] allow to send a notification to an email address. The address receive an access link ## Test releases