diff --git a/CHANGELOG.md b/CHANGELOG.md index 689c7d338..49943e927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,7 @@ and this project adheres to * [household] bugfix if position of member is null, renderbox no longer throws an error (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/480) * [parcours] location cannot be removed if linked to a user (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/478) * [person] email added to twig personRenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/490) +* [activity] Only youngest descendant is kept for social issues and actions (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/471) * [person] Add link to current household in person banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/484) * [address] person badge in address history changed to open OnTheFly with all person info (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/489) * [person] Change 'personne' with 'usager' and '&' with 'ET' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/499) @@ -139,6 +140,9 @@ and this project adheres to * [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) +* [action] Only youngest descendant is kept for social issues and actions (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/471) +## Test releases + ### test release 2022-02-21 * [notifications] Word 'un' changed to number '1' for notifications in user menu (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/483) diff --git a/src/Bundle/ChillActivityBundle/Entity/Activity.php b/src/Bundle/ChillActivityBundle/Entity/Activity.php index 0a344236c..eabaa44d1 100644 --- a/src/Bundle/ChillActivityBundle/Entity/Activity.php +++ b/src/Bundle/ChillActivityBundle/Entity/Activity.php @@ -231,11 +231,22 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac { if (!$this->socialActions->contains($socialAction)) { $this->socialActions[] = $socialAction; + $this->ensureSocialActionConsistency(); } return $this; } + /** + * Add a social issue. + * + * Note: the social issue consistency (the fact that only yougest social issues + * are kept) is processed by an entity listener: + * + * @see{\Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodSocialIssueConsistencyEntityListener} + * + * @return $this + */ public function addSocialIssue(SocialIssue $socialIssue): self { if (!$this->socialIssues->contains($socialIssue)) { @@ -631,4 +642,13 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac return $this; } + + private function ensureSocialActionConsistency(): void + { + $ancestors = SocialAction::findAncestorSocialActions($this->getSocialActions()); + + foreach ($ancestors as $ancestor) { + $this->removeSocialAction($ancestor); + } + } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Entity/ActivityTest.php b/src/Bundle/ChillActivityBundle/Tests/Entity/ActivityTest.php new file mode 100644 index 000000000..7a68a36fc --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Entity/ActivityTest.php @@ -0,0 +1,114 @@ +addChild($child); + $grandChild = new SocialAction(); + $child->addChild($grandChild); + + $activity = new Activity(); + + $activity->addSocialAction($parent); + + $this->assertCount(1, $activity->getSocialActions()); + $this->assertContains($parent, $activity->getSocialActions()); + + $activity->addSocialAction($grandChild); + + $this->assertCount(1, $activity->getSocialActions()); + $this->assertContains($grandChild, $activity->getSocialActions()); + $this->assertNotContains($parent, $activity->getSocialActions()); + + $activity->addSocialAction($child); + + $this->assertCount(1, $activity->getSocialActions()); + $this->assertContains($grandChild, $activity->getSocialActions()); + $this->assertNotContains($parent, $activity->getSocialActions()); + $this->assertNotContains($child, $activity->getSocialActions()); + + $activity->addSocialAction($another = new SocialAction()); + + $this->assertCount(2, $activity->getSocialActions()); + $this->assertContains($grandChild, $activity->getSocialActions()); + $this->assertContains($another, $activity->getSocialActions()); + $this->assertNotContains($parent, $activity->getSocialActions()); + $this->assertNotContains($child, $activity->getSocialActions()); + } + + public function testHierarchySocialIssues(): void + { + $listener = new AccompanyingPeriodSocialIssueConsistencyEntityListener(); + $event = $this->prophesize(LifecycleEventArgs::class)->reveal(); + + $parent = new SocialIssue(); + $child = new SocialIssue(); + + $parent->addChild($child); + $grandChild = new SocialIssue(); + $child->addChild($grandChild); + + $activity = new Activity(); + $activity->setAccompanyingPeriod(new AccompanyingPeriod()); + + $activity->addSocialIssue($parent); + $listener->preUpdate($activity, $event); + + $this->assertCount(1, $activity->getSocialIssues()); + $this->assertContains($parent, $activity->getSocialIssues()); + + $activity->addSocialIssue($grandChild); + $listener->preUpdate($activity, $event); + + $this->assertCount(1, $activity->getSocialIssues()); + $this->assertContains($grandChild, $activity->getSocialIssues()); + $this->assertNotContains($parent, $activity->getSocialIssues()); + + $activity->addSocialIssue($child); + $listener->preUpdate($activity, $event); + + $this->assertCount(1, $activity->getSocialIssues()); + $this->assertContains($grandChild, $activity->getSocialIssues()); + $this->assertNotContains($parent, $activity->getSocialIssues()); + $this->assertNotContains($child, $activity->getSocialIssues()); + + $activity->addSocialIssue($another = new SocialIssue()); + $listener->preUpdate($activity, $event); + + $this->assertCount(2, $activity->getSocialIssues()); + $this->assertContains($grandChild, $activity->getSocialIssues()); + $this->assertContains($another, $activity->getSocialIssues()); + $this->assertNotContains($parent, $activity->getSocialIssues()); + $this->assertNotContains($child, $activity->getSocialIssues()); + } +} diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php index ec5e5a8e5..904950489 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php @@ -128,6 +128,44 @@ class SocialAction return $this; } + /** + * In a SocialIssues's collection, find the elements which are an ancestor of + * other elements. + * + * The difference of the given list (thus, the elements which are **not** kept + * in the returned collection) are the most-grand-child elements of the list. + * + * Removing those elements of the Collection (which is not done by this method) + * will ensure that only the most descendent elements are present in the collection, + * (any ancestor of another element are present). + * + * @param Collection|SocialAction[] $socialActions + * + * @return Collection|SocialAction[] a list with the elements of the given list which are parent of other elements in the given list + */ + public static function findAncestorSocialActions(Collection $socialActions): Collection + { + $ancestors = new ArrayCollection(); + + foreach ($socialActions as $candidateChild) { + if ($ancestors->contains($candidateChild)) { + continue; + } + + foreach ($socialActions as $candidateParent) { + if ($ancestors->contains($candidateParent)) { + continue; + } + + if ($candidateChild->isDescendantOf($candidateParent)) { + $ancestors->add($candidateParent); + } + } + } + + return $ancestors; + } + /** * @return Collection|self[] */ @@ -169,7 +207,7 @@ class SocialAction } /** - * @return Collection|self[] All the descendants with the current entity (this) + * @return Collection|self[] All the descendants including the current entity (this) */ public function getDescendantsWithThis(): Collection { @@ -233,6 +271,23 @@ class SocialAction return $this->getParent() instanceof self; } + /** + * Recursive method which return true if the current $action + * is a descendant of the $action given in parameter. + */ + public function isDescendantOf(SocialAction $action): bool + { + if (!$this->hasParent()) { + return false; + } + + if ($this->getParent() === $action) { + return true; + } + + return $this->getParent()->isDescendantOf($action); + } + public function removeChild(self $child): self { if ($this->children->removeElement($child)) { diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php index c735c0132..fb277f56c 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php @@ -202,7 +202,7 @@ class SocialIssue } /** - * @return Collection|self[] All the descendants with the current entity (this) + * @return Collection|self[] All the descendants including the current entity (this) */ public function getDescendantsWithThis(): Collection {