diff --git a/CHANGELOG.md b/CHANGELOG.md index da1e32cbb..4923eb877 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to ## Unreleased +* [person] add validator for accompanying period with a test on social issues (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/76) +* [activity] fix visibility for location ## Test releases diff --git a/src/Bundle/ChillActivityBundle/Entity/ActivityType.php b/src/Bundle/ChillActivityBundle/Entity/ActivityType.php index f0e412a8c..5ad692bf4 100644 --- a/src/Bundle/ChillActivityBundle/Entity/ActivityType.php +++ b/src/Bundle/ChillActivityBundle/Entity/ActivityType.php @@ -163,16 +163,6 @@ class ActivityType */ private int $personVisible = self::FIELD_REQUIRED; - /** - * @ORM\Column(type="string", nullable=false, options={"default": ""}) - */ - private string $placeLabel = ''; - - /** - * @ORM\Column(type="smallint", nullable=false, options={"default": 1}) - */ - private int $placeVisible = self::FIELD_OPTIONAL; - /** * @ORM\Column(type="string", nullable=false, options={"default": ""}) */ @@ -406,16 +396,6 @@ class ActivityType return $this->personVisible; } - public function getPlaceLabel(): string - { - return $this->placeLabel; - } - - public function getPlaceVisible(): int - { - return $this->placeVisible; - } - public function getReasonsLabel(): string { return $this->reasonsLabel; @@ -688,20 +668,6 @@ class ActivityType return $this; } - public function setPlaceLabel(string $placeLabel): self - { - $this->placeLabel = $placeLabel; - - return $this; - } - - public function setPlaceVisible(int $placeVisible): self - { - $this->placeVisible = $placeVisible; - - return $this; - } - public function setReasonsLabel(string $reasonsLabel): self { $this->reasonsLabel = $reasonsLabel; diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityType.php b/src/Bundle/ChillActivityBundle/Form/ActivityType.php index 7ed80ab00..8578b0a8a 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityType.php @@ -143,7 +143,7 @@ class ActivityType extends AbstractType return array_map( fn (string $id): ?SocialIssue => $this->om->getRepository(SocialIssue::class)->findOneBy(['id' => (int) $id]), - explode(',', (string) $socialIssuesAsString) + explode(',', $socialIssuesAsString) ); } )); @@ -169,7 +169,7 @@ class ActivityType extends AbstractType return array_map( fn (string $id): ?SocialAction => $this->om->getRepository(SocialAction::class)->findOneBy(['id' => (int) $id]), - explode(',', (string) $socialActionsAsString) + explode(',', $socialActionsAsString) ); } )); @@ -260,6 +260,10 @@ class ActivityType extends AbstractType return implode(',', $personIds); }, function (?string $personsAsString): array { + if (null === $personsAsString) { + return []; + } + return array_map( fn (string $id): ?Person => $this->om->getRepository(Person::class)->findOneBy(['id' => (int) $id]), explode(',', (string) $personsAsString) @@ -282,6 +286,10 @@ class ActivityType extends AbstractType return implode(',', $thirdpartyIds); }, function (?string $thirdpartyAsString): array { + if (null === $thirdpartyAsString) { + return []; + } + return array_map( fn (string $id): ?ThirdParty => $this->om->getRepository(ThirdParty::class)->findOneBy(['id' => (int) $id]), explode(',', (string) $thirdpartyAsString) @@ -315,6 +323,10 @@ class ActivityType extends AbstractType return implode(',', $userIds); }, function (?string $usersAsString): array { + if (null === $usersAsString) { + return []; + } + return array_map( fn (string $id): ?User => $this->om->getRepository(User::class)->findOneBy(['id' => (int) $id]), explode(',', (string) $usersAsString) diff --git a/src/Bundle/ChillActivityBundle/Form/ActivityTypeType.php b/src/Bundle/ChillActivityBundle/Form/ActivityTypeType.php index 26148c340..28947d72b 100644 --- a/src/Bundle/ChillActivityBundle/Form/ActivityTypeType.php +++ b/src/Bundle/ChillActivityBundle/Form/ActivityTypeType.php @@ -55,7 +55,7 @@ class ActivityTypeType extends AbstractType ]); $fields = [ - 'persons', 'user', 'date', 'place', 'persons', + 'persons', 'user', 'date', 'location', 'persons', 'thirdParties', 'durationTime', 'travelTime', 'attendee', 'reasons', 'comment', 'sentReceived', 'documents', 'emergency', 'socialIssues', 'socialActions', 'users', diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php index 1493a45ae..ebdc0a3c8 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityRepository.php @@ -31,6 +31,7 @@ class ActivityRepository extends ServiceEntityRepository } /** + * @deprecated use @link{ActivityACLAwareRepositoryInterface::findByAccompanyingPeriod} * @return Activity[] */ public function findByAccompanyingPeriod(AccompanyingPeriod $period, array $scopes, ?bool $allowNullScope = false, ?int $limit = 100, ?int $offset = 0, array $orderBy = ['date' => 'desc']): array diff --git a/src/Bundle/ChillActivityBundle/migrations/Version20211207152023.php b/src/Bundle/ChillActivityBundle/migrations/Version20211207152023.php new file mode 100644 index 000000000..034fee71a --- /dev/null +++ b/src/Bundle/ChillActivityBundle/migrations/Version20211207152023.php @@ -0,0 +1,34 @@ +throwIrreversibleMigrationException('placevisible and placelabel could not be created'); + } + + public function getDescription(): string + { + return 'DROP place visible and place label, which are replaced by location'; + } + + public function up(Schema $schema): void + { + $this->addSql('ALTER TABLE activitytype DROP placevisible'); + $this->addSql('ALTER TABLE activitytype DROP placelabel'); + } +} diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml index 3de1cc600..00715d283 100644 --- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml @@ -146,8 +146,8 @@ User visible: Visibilité du champ Utilisateur User label: Libellé du champ Utilisateur Date visible: Visibilité du champ Date Date label: Libellé du champ Date -Place visible: Visibilité du champ Lieu -Place label: Libellé du champ Lieu +Location visible: Visibilité du champ Lieu +Location label: Libellé du champ Lieu Third parties visible: Visibilité du champ Tiers Third parties label: Libellé du champ Tiers Duration time visible: Visibilité du champ Durée diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index dcd3c2df0..849983187 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -25,6 +25,7 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; +use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\AccompanyingPeriodValidity; use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ParticipationOverlap; use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ResourceDuplicateCheck; use Chill\ThirdPartyBundle\Entity\ThirdParty; @@ -58,6 +59,8 @@ use const SORT_REGULAR; * "this.isConfidential and this.getUser === NULL", * message="If the accompanying course is confirmed and confidential, a referrer must remain assigned." * ) + * + * @AccompanyingPeriodValidity(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED}) */ class AccompanyingPeriod implements GroupSequenceProviderInterface, diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php index 84d47796b..b8a9141e4 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php @@ -121,6 +121,27 @@ class SocialIssue return $ancestors; } + /** + * get all the ancestors of the social issue + * + * @param bool $includeThis if the array in the result must include the present SocialIssue + */ + public function getAncestors(bool $includeThis = true): array + { + $ancestors = []; + + if ($includeThis) { + $ancestors[] = $this; + } + + $current = $this; + while ($current->hasParent()) { + $ancestors[] = $current = $current->getParent(); + } + + return $ancestors; + } + /** * @return Collection|self[] */ diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/SocialWork/SocialIssueTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/SocialWork/SocialIssueTest.php index f8f2e3b89..491a2fffb 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Entity/SocialWork/SocialIssueTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Entity/SocialWork/SocialIssueTest.php @@ -61,4 +61,20 @@ final class SocialIssueTest extends TestCase $this->assertFalse($child->isDescendantOf($grandChild)); } + + public function testGetAncestors() + { + $parent = new SocialIssue(); + $child = (new SocialIssue())->setParent($parent); + $grandChild = (new SocialIssue())->setParent($child); + $grandGrandChild = (new SocialIssue())->setParent($grandChild); + $unrelated = new SocialIssue(); + + $this->assertContains($parent, $grandGrandChild->getAncestors(true)); + $this->assertContains($child, $grandGrandChild->getAncestors(true)); + $this->assertContains($grandChild, $grandGrandChild->getAncestors(true)); + $this->assertContains($grandGrandChild, $grandGrandChild->getAncestors(true)); + $this->assertNotContains($grandGrandChild, $grandGrandChild->getAncestors(false)); + $this->assertCount(0, $unrelated->getAncestors(false)); + } } diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/AccompanyingPeriodValidity.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/AccompanyingPeriodValidity.php new file mode 100644 index 000000000..c4bd30ed3 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/AccompanyingPeriodValidity.php @@ -0,0 +1,27 @@ +activityRepository = $activityRepository; + $this->socialIssueRender = $socialIssueRender; + } + + public function validate($period, Constraint $constraint) + { + if (!$constraint instanceof AccompanyingPeriodValidity) { + throw new UnexpectedTypeException($constraint, AccompanyingPeriodValidity::class); + } + + if (!$period instanceof AccompanyingPeriod) { + throw new UnexpectedValueException($period, AccompanyingPeriod::class); + } + + $socialIssues = []; + + $activities = $this->activityRepository->findBy(['accompanyingPeriod' => $period]); + + foreach ($activities as $activity) { + $socialIssues = $activity->getSocialIssues()->toArray(); + } + + foreach ($period->getWorks() as $work) { + $socialIssues[] = $work->getSocialAction()->getIssue(); + } + + $socialIssuesByKey = []; + foreach ($socialIssues as $si) { + $socialIssuesByKey[$si->getId()] = $si; + } + + $periodIssuesWithAncestors = []; + foreach ($period->getSocialIssues() as $si) { + /** @var SocialIssue $si */ + $periodIssuesWithAncestors = array_merge($periodIssuesWithAncestors, \array_map( + function (SocialIssue $si) { return $si->getId(); }, + $si->getAncestors(true)) + ); + } + + foreach ($socialIssuesByKey as $key => $si) { + if (!in_array($key, $periodIssuesWithAncestors)) { + $this->context + ->buildViolation( + $constraint->messageSocialIssueCannotBeDeleted + ) + ->setParameter('%name%', $this->socialIssueRender->renderString($si, [])) + ->addViolation(); + } + } + } +} diff --git a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml index c733a935c..b421e9f43 100644 --- a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml @@ -46,3 +46,4 @@ household_membership: '{{ name }} is already associated to this accompanying course.': '{{ name }} est déjà associé à ce parcours.' A course must contains at least one social issue: 'Un parcours doit être associé à au moins une problématique sociale' A course must be associated to at least one scope: 'Un parcours doit être associé à au moins un service' +The social %name% issue cannot be deleted because it is associated with an activity or an action: 'La problématique sociale "%name%" ne peut pas être supprimée car elle est associée à une activité ou une action'