From c8135e074189269fc1ac4a706084af86511ef1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 12 Nov 2021 12:07:31 +0000 Subject: [PATCH] add validation to accompanying periods --- .../Tests/Util/DateRangeCoveringTest.php | 12 ++++ .../Util/DateRangeCovering.php | 61 ---------------- .../AccompanyingCourseApiController.php | 15 +++- .../ORM/LoadAccompanyingPeriodOrigin.php | 2 +- .../DataFixtures/ORM/LoadPeople.php | 4 +- .../Entity/AccompanyingPeriod.php | 38 +++++++++- .../Entity/AccompanyingPeriod/Resource.php | 8 ++- .../AccompanyingPeriodParticipation.php | 7 ++ .../public/vuejs/AccompanyingCourse/App.vue | 17 ++--- .../public/vuejs/AccompanyingCourse/api.js | 3 +- .../components/OriginDemand.vue | 16 ++--- .../public/vuejs/AccompanyingCourse/index.js | 3 + .../vuejs/AccompanyingCourse/store/index.js | 2 +- .../AccompanyingPeriod/LocationValidity.php | 2 +- .../ParticipationOverlap.php | 15 ++++ .../ParticipationOverlapValidator.php | 72 +++++++++++++++++++ .../ResourceDuplicateCheck.php | 16 +++++ .../ResourceDuplicateCheckValidator.php | 56 +++++++++++++++ .../config/services/validator.yaml | 6 ++ .../migrations/Version20211020131133.php | 31 ++++++++ .../migrations/Version20211021125359.php | 36 ++++++++++ .../translations/validators.fr.yml | 3 + 22 files changed, 337 insertions(+), 88 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ParticipationOverlap.php create mode 100644 src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ParticipationOverlapValidator.php create mode 100644 src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ResourceDuplicateCheck.php create mode 100644 src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ResourceDuplicateCheckValidator.php create mode 100644 src/Bundle/ChillPersonBundle/config/services/validator.yaml create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20211020131133.php create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20211021125359.php diff --git a/src/Bundle/ChillMainBundle/Tests/Util/DateRangeCoveringTest.php b/src/Bundle/ChillMainBundle/Tests/Util/DateRangeCoveringTest.php index c06b4a4f4..18af61849 100644 --- a/src/Bundle/ChillMainBundle/Tests/Util/DateRangeCoveringTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Util/DateRangeCoveringTest.php @@ -37,6 +37,18 @@ class DateRangeCoveringTest extends TestCase $this->assertNotContains(3, $cover->getIntersections()[0][2]); } + public function testCoveringWithMinCover1_NoCoveringWithNullDates() + { + $cover = new DateRangeCovering(1, new \DateTimeZone('Europe/Brussels')); + $cover + ->add(new \DateTime('2021-10-05'), new \DateTime('2021-10-18'), 521) + ->add(new \DateTime('2021-10-26'), null, 663) + ->compute() + ; + + $this->assertFalse($cover->hasIntersections()); + } + public function testCoveringWithMinCover1WithTwoIntersections() { $cover = new DateRangeCovering(1, new \DateTimeZone('Europe/Brussels')); diff --git a/src/Bundle/ChillMainBundle/Util/DateRangeCovering.php b/src/Bundle/ChillMainBundle/Util/DateRangeCovering.php index a10ebb883..3eb6d1433 100644 --- a/src/Bundle/ChillMainBundle/Util/DateRangeCovering.php +++ b/src/Bundle/ChillMainBundle/Util/DateRangeCovering.php @@ -140,67 +140,6 @@ class DateRangeCovering return $this; } - private function process(array $intersections): array - { - $result = []; - $starts = []; - $ends = []; - $metadatas = []; - - while (null !== ($current = \array_pop($intersections))) { - list($cStart, $cEnd, $cMetadata) = $current; - $n = count($cMetadata); - - foreach ($intersections as list($iStart, $iEnd, $iMetadata)) { - $start = max($cStart, $iStart); - $end = min($cEnd, $iEnd); - - if ($start <= $end) { - if (FALSE !== ($key = \array_search($start, $starts))) { - if ($ends[$key] === $end) { - $metadatas[$key] = \array_unique(\array_merge($metadatas[$key], $iMetadata)); - continue; - } - } - $starts[] = $start; - $ends[] = $end; - $metadatas[] = \array_unique(\array_merge($iMetadata, $cMetadata)); - } - } - } - - // recompose results - foreach ($starts as $k => $start) { - $result[] = [$start, $ends[$k], \array_unique($metadatas[$k])]; - } - - return $result; - } - - private function addToIntersections(array $intersections, array $intersection) - { - $foundExisting = false; - list($nStart, $nEnd, $nMetadata) = $intersection; - - \array_walk($intersections, - function(&$i, $key) use ($nStart, $nEnd, $nMetadata, $foundExisting) { - if ($foundExisting) { - return; - }; - if ($i[0] === $nStart && $i[1] === $nEnd) { - $foundExisting = true; - $i[2] = \array_merge($i[2], $nMetadata); - } - } - ); - - if (!$foundExisting) { - $intersections[] = $intersection; - } - - return $intersections; - } - public function hasIntersections(): bool { if (!$this->computed) { diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php index bce888876..ba033f562 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php @@ -54,10 +54,14 @@ class AccompanyingCourseApiController extends ApiController $accompanyingPeriod = $this->getEntity('participation', $id, $request); $this->checkACL('confirm', $request, $_format, $accompanyingPeriod); -$workflow = $this->registry->get($accompanyingPeriod); + $workflow = $this->registry->get($accompanyingPeriod); if (FALSE === $workflow->can($accompanyingPeriod, 'confirm')) { - throw new BadRequestException('It is not possible to confirm this period'); + // throw new BadRequestException('It is not possible to confirm this period'); + $errors = $this->validator->validate($accompanyingPeriod, null, [$accompanyingPeriod::STEP_CONFIRMED]); + if( count($errors) > 0 ){ + return $this->json($errors, 422); + } } $workflow->apply($accompanyingPeriod, 'confirm'); @@ -109,6 +113,13 @@ $workflow = $this->registry->get($accompanyingPeriod); public function resourceApi($id, Request $request, string $_format): Response { + $accompanyingPeriod = $this->getEntity('resource', $id, $request); + $errors = $this->validator->validate($accompanyingPeriod); + + if ($errors->count() > 0) { + return $this->json($errors, 422); + } + return $this->addRemoveSomething('resource', $id, $request, $_format, 'resource', Resource::class); } diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodOrigin.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodOrigin.php index cbec9c439..eef8d7b47 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodOrigin.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodOrigin.php @@ -40,7 +40,7 @@ class LoadAccompanyingPeriodOrigin extends AbstractFixture implements OrderedFix public function getOrder() { - return 10005; + return 9000; } private $phoneCall = ['en' => 'phone call', 'fr' => 'appel téléphonique']; diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php index adc37d5b5..b9b0b4946 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php @@ -247,7 +247,9 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con if (\random_int(0, 10) > 3) { // always add social scope: $accompanyingPeriod->addScope($this->getReference('scope_social')); - + $origin = $this->getReference(LoadAccompanyingPeriodOrigin::ACCOMPANYING_PERIOD_ORIGIN); + $accompanyingPeriod->setOrigin($origin); + $accompanyingPeriod->setIntensity('regular'); $accompanyingPeriod->setAddressLocation($this->createAddress()); $manager->persist($accompanyingPeriod->getAddressLocation()); $workflow = $this->workflowRegistry->get($accompanyingPeriod); diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 9ad0c75dd..dab1dcb9d 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -45,6 +45,9 @@ use Chill\MainBundle\Entity\User; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\GroupSequenceProviderInterface; +use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ParticipationOverlap; +use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ResourceDuplicateCheck; /** * AccompanyingPeriod Class @@ -54,9 +57,10 @@ use Symfony\Component\Validator\Constraints as Assert; * @DiscriminatorMap(typeProperty="type", mapping={ * "accompanying_period"=AccompanyingPeriod::class * }) + * @Assert\GroupSequenceProvider */ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface, - HasScopesInterface, HasCentersInterface + HasScopesInterface, HasCentersInterface, GroupSequenceProviderInterface { /** * Mark an accompanying period as "occasional" @@ -132,6 +136,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface * cascade={"persist", "remove"}, * orphanRemoval=true * ) + * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_DRAFT}) */ private $comments; @@ -147,9 +152,10 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface * @var Collection * * @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class, - * mappedBy="accompanyingPeriod", + * mappedBy="accompanyingPeriod", orphanRemoval=true, * cascade={"persist", "refresh", "remove", "merge", "detach"}) * @Groups({"read"}) + * @ParticipationOverlap(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED}) */ private $participations; @@ -188,6 +194,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface * @ORM\ManyToOne(targetEntity=Origin::class) * @ORM\JoinColumn(nullable=true) * @Groups({"read", "write"}) + * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED}) */ private $origin; @@ -195,8 +202,9 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface * @var string * @ORM\Column(type="string", nullable=true) * @Groups({"read", "write"}) + * @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED}) */ - private $intensity; + private $intensity = self::INTENSITY_OCCASIONAL; /** * @var Collection @@ -210,6 +218,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface * inverseJoinColumns={@ORM\JoinColumn(name="scope_id", referencedColumnName="id")} * ) * @Groups({"read"}) + * @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}) */ private $scopes; @@ -256,6 +265,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface * orphanRemoval=true * ) * @Groups({"read"}) + * @ResourceDuplicateCheck(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED, "Default", "default"}) */ private $resources; @@ -267,6 +277,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface * name="chill_person_accompanying_period_social_issues" * ) * @Groups({"read"}) + * @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}) */ private Collection $socialIssues; @@ -606,6 +617,14 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface return $participation; } + /** + * Remove Participation + */ + + public function removeParticipation(AccompanyingPeriodParticipation $participation) + { + $participation->setAccompanyingPeriod(null); + } /** * Remove Person @@ -1115,4 +1134,17 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface return $centers ?? null; } + + public function getGroupSequence() + { + if($this->getStep() == self::STEP_DRAFT) + { + return [[self::STEP_DRAFT]]; + } + + if($this->getStep() == self::STEP_CONFIRMED) + { + return [[self::STEP_DRAFT, self::STEP_CONFIRMED]]; + } + } } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php index a50e28621..48e532e10 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php @@ -33,7 +33,13 @@ use Symfony\Component\Serializer\Annotation\Groups; /** * @ORM\Entity - * @ORM\Table(name="chill_person_accompanying_period_resource") + * @ORM\Table( + * name="chill_person_accompanying_period_resource", + * uniqueConstraints={ + * @ORM\UniqueConstraint(name="person_unique", columns={"person_id", "accompanyingperiod_id"}), + * @ORM\UniqueConstraint(name="thirdparty_unique", columns={"thirdparty_id", "accompanyingperiod_id"}) + * } + * ) * @DiscriminatorMap(typeProperty="type", mapping={ * "accompanying_period_resource"=Resource::class * }) diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php index c873e527a..f246c25bd 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php @@ -134,4 +134,11 @@ class AccompanyingPeriodParticipation { return $this->endDate === null; } + + private function checkSameStartEnd() + { + if($this->endDate == $this->startDate) { + $this->accompanyingPeriod->removeParticipation($this); + } + } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue index 9c65137bd..ff77f77ce 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue @@ -16,16 +16,16 @@ -
+ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js index 52f3f6c36..c372ac7a7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js @@ -86,7 +86,8 @@ const postParticipation = (id, payload, method) => { }) .then(response => { if (response.ok) { return response.json(); } - throw { msg: 'Error while sending AccompanyingPeriod Course participation.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; + // TODO: adjust message according to status code? Or how to access the message from the violation array? + throw { msg: 'Error while sending AccompanyingPeriod Course participation', sta: response.status, txt: response.statusText, err: new Error(), body: response.body }; }); }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue index 30001028c..30ad6afe0 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue @@ -10,13 +10,13 @@ @@ -47,18 +47,18 @@ export default { }, methods: { getOptions() { - //console.log('loading origins list'); getListOrigins().then(response => new Promise((resolve, reject) => { this.options = response.results; resolve(); })); }, updateOrigin(value) { - //console.log('value', value); + console.log('value', value); this.$store.dispatch('updateOrigin', value); }, transText ({ text }) { - return text.fr //TODO multilang + const parsedText = JSON.parse(text); + return parsedText.fr; }, } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js index e3fdec4a3..ace482d79 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js @@ -2,6 +2,8 @@ import { createApp } from 'vue' import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' import { appMessages } from './js/i18n' import { initPromise } from './store' +import VueToast from 'vue-toast-notification'; +import 'vue-toast-notification/dist/theme-sugar.css'; import App from './App.vue'; import Banner from './components/Banner.vue'; @@ -21,6 +23,7 @@ if (root === 'app') { }) .use(store) .use(i18n) + .use(VueToast) .component('app', App) .mount('#accompanying-course'); }); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js index 9b69845cf..41e341dd8 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js @@ -77,7 +77,7 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise]) }, mutations: { catchError(state, error) { - console.log('### mutation: a new error have been catched and pushed in store !', error); + // console.log('### mutation: a new error have been catched and pushed in store !', error); state.errorMsg.push(error); }, removeParticipation(state, participation) { diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php index 75c10ca75..b1b660596 100644 --- a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php @@ -11,7 +11,7 @@ class LocationValidity extends Constraint { public $messagePersonLocatedMustBeAssociated = "The person where the course is located must be associated to the course. Change course's location before removing the person."; - public $messagePeriodMustRemainsLocated = "The period must remains located"; + public $messagePeriodMustRemainsLocated = "The period must remain located"; public function getTargets() { diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ParticipationOverlap.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ParticipationOverlap.php new file mode 100644 index 000000000..38d5516b5 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ParticipationOverlap.php @@ -0,0 +1,15 @@ +getStartDate()->getTimezone()); + + foreach ($participations as $participation) { + + if (!$participation instanceof AccompanyingPeriodParticipation) { + throw new UnexpectedTypeException($participation, AccompanyingPeriodParticipation::class); + } + + $personId = $participation->getPerson()->getId(); + + $particpationList[$personId][] = $participation; + + } + + foreach ($particpationList as $group) { + if (count($group) > 1) { + foreach ($group as $p) { + $overlaps->add($p->getStartDate(), $p->getEndDate(), $p->getId()); + } + } + } + + $overlaps->compute(); + + if ($overlaps->hasIntersections()) { + foreach ($overlaps->getIntersections() as list($start, $end, $ids)) { + $msg = $end === null ? $constraint->message : + $constraint->message; + + $this->context->buildViolation($msg) + ->setParameters([ + '{{ start }}' => $start->format('d-m-Y'), + '{{ end }}' => $end === null ? null : $end->format('d-m-Y'), + '{{ ids }}' => $ids, + ]) + ->addViolation(); + } + } + + } +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ResourceDuplicateCheck.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ResourceDuplicateCheck.php new file mode 100644 index 000000000..bfc9d62af --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ResourceDuplicateCheck.php @@ -0,0 +1,16 @@ +personRender = $personRender; + $this->thirdpartyRender = $thirdPartyRender; + } + + public function validate($resources, Constraint $constraint) + { + if (!$constraint instanceof ResourceDuplicateCheck) { + throw new UnexpectedTypeException($constraint, ParticipationOverlap::class); + } + + if (!$resources instanceof Collection) { + throw new UnexpectedTypeException($resources, Collection::class); + } + + $resourceList = []; + + foreach ($resources as $resource) { + $id = ($resource->getResource() instanceof Person ? 'p' : + 't').$resource->getResource()->getId(); + + if (\in_array($id, $resourceList, true)) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ name }}', $resource->getResource() instanceof Person ? $this->personRender->renderString($resource->getResource(), []) : + $this->thirdpartyRender->renderString($resource->getResource(), [])) + ->addViolation(); + } + + $resourceList[] = $id; + + } + + } + +} \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/config/services/validator.yaml b/src/Bundle/ChillPersonBundle/config/services/validator.yaml new file mode 100644 index 000000000..435f2f5b5 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/config/services/validator.yaml @@ -0,0 +1,6 @@ +services: + Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod: + autowire: true + autoconfigure: true + tags: ['validator.service_arguments'] + \ No newline at end of file diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20211020131133.php b/src/Bundle/ChillPersonBundle/migrations/Version20211020131133.php new file mode 100644 index 000000000..031356cd3 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20211020131133.php @@ -0,0 +1,31 @@ +addSql('CREATE UNIQUE INDEX person_unique ON chill_person_accompanying_period_resource (person_id, accompanyingperiod_id) WHERE person_id IS NOT NULL'); + $this->addSql('CREATE UNIQUE INDEX thirdparty_unique ON chill_person_accompanying_period_resource (thirdparty_id, accompanyingperiod_id) WHERE thirdparty_id IS NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP INDEX person_unique'); + $this->addSql('DROP INDEX thirdparty_unique'); + } +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20211021125359.php b/src/Bundle/ChillPersonBundle/migrations/Version20211021125359.php new file mode 100644 index 000000000..a4ed54254 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20211021125359.php @@ -0,0 +1,36 @@ +addSql('ALTER TABLE chill_person_accompanying_period_participation ADD CONSTRAINT '. + "participations_no_overlap EXCLUDE USING GIST( + -- extension btree_gist required to include comparaison with integer + person_id WITH =, accompanyingperiod_id WITH =, + daterange(startdate, enddate) WITH && + ) + INITIALLY DEFERRED"); + } + + public function down(Schema $schema): void + { + $this->addSql('CREATE UNIQUE INDEX participation_unique ON chill_person_accompanying_period_participation (accompanyingperiod_id, person_id)'); + } +} diff --git a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml index 0e77dae0c..6d2ccb3cb 100644 --- a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml @@ -41,3 +41,6 @@ household: household_membership: The end date must be after start date: La date de la fin de l'appartenance doit être postérieure à la date de début. Person with membership covering: Une personne ne peut pas appartenir à deux ménages simultanément. Or, avec cette modification, %person_name% appartiendrait à %nbHousehold% ménages à partir du %from%. + +# Accompanying period +'{{ name }} is already associated to this accompanying course.': '{{ name }} est déjà associé avec ce parcours.' \ No newline at end of file