mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
add validation to accompanying periods
This commit is contained in:
parent
39ab7057ce
commit
c8135e0741
@ -37,6 +37,18 @@ class DateRangeCoveringTest extends TestCase
|
|||||||
$this->assertNotContains(3, $cover->getIntersections()[0][2]);
|
$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()
|
public function testCoveringWithMinCover1WithTwoIntersections()
|
||||||
{
|
{
|
||||||
$cover = new DateRangeCovering(1, new \DateTimeZone('Europe/Brussels'));
|
$cover = new DateRangeCovering(1, new \DateTimeZone('Europe/Brussels'));
|
||||||
|
@ -140,67 +140,6 @@ class DateRangeCovering
|
|||||||
return $this;
|
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
|
public function hasIntersections(): bool
|
||||||
{
|
{
|
||||||
if (!$this->computed) {
|
if (!$this->computed) {
|
||||||
|
@ -54,10 +54,14 @@ class AccompanyingCourseApiController extends ApiController
|
|||||||
$accompanyingPeriod = $this->getEntity('participation', $id, $request);
|
$accompanyingPeriod = $this->getEntity('participation', $id, $request);
|
||||||
|
|
||||||
$this->checkACL('confirm', $request, $_format, $accompanyingPeriod);
|
$this->checkACL('confirm', $request, $_format, $accompanyingPeriod);
|
||||||
$workflow = $this->registry->get($accompanyingPeriod);
|
$workflow = $this->registry->get($accompanyingPeriod);
|
||||||
|
|
||||||
if (FALSE === $workflow->can($accompanyingPeriod, 'confirm')) {
|
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');
|
$workflow->apply($accompanyingPeriod, 'confirm');
|
||||||
@ -109,6 +113,13 @@ $workflow = $this->registry->get($accompanyingPeriod);
|
|||||||
|
|
||||||
public function resourceApi($id, Request $request, string $_format): Response
|
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);
|
return $this->addRemoveSomething('resource', $id, $request, $_format, 'resource', Resource::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class LoadAccompanyingPeriodOrigin extends AbstractFixture implements OrderedFix
|
|||||||
|
|
||||||
public function getOrder()
|
public function getOrder()
|
||||||
{
|
{
|
||||||
return 10005;
|
return 9000;
|
||||||
}
|
}
|
||||||
|
|
||||||
private $phoneCall = ['en' => 'phone call', 'fr' => 'appel téléphonique'];
|
private $phoneCall = ['en' => 'phone call', 'fr' => 'appel téléphonique'];
|
||||||
|
@ -247,7 +247,9 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
|
|||||||
if (\random_int(0, 10) > 3) {
|
if (\random_int(0, 10) > 3) {
|
||||||
// always add social scope:
|
// always add social scope:
|
||||||
$accompanyingPeriod->addScope($this->getReference('scope_social'));
|
$accompanyingPeriod->addScope($this->getReference('scope_social'));
|
||||||
|
$origin = $this->getReference(LoadAccompanyingPeriodOrigin::ACCOMPANYING_PERIOD_ORIGIN);
|
||||||
|
$accompanyingPeriod->setOrigin($origin);
|
||||||
|
$accompanyingPeriod->setIntensity('regular');
|
||||||
$accompanyingPeriod->setAddressLocation($this->createAddress());
|
$accompanyingPeriod->setAddressLocation($this->createAddress());
|
||||||
$manager->persist($accompanyingPeriod->getAddressLocation());
|
$manager->persist($accompanyingPeriod->getAddressLocation());
|
||||||
$workflow = $this->workflowRegistry->get($accompanyingPeriod);
|
$workflow = $this->workflowRegistry->get($accompanyingPeriod);
|
||||||
|
@ -45,6 +45,9 @@ use Chill\MainBundle\Entity\User;
|
|||||||
use Symfony\Component\Serializer\Annotation\Groups;
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
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
|
* AccompanyingPeriod Class
|
||||||
@ -54,9 +57,10 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
* @DiscriminatorMap(typeProperty="type", mapping={
|
* @DiscriminatorMap(typeProperty="type", mapping={
|
||||||
* "accompanying_period"=AccompanyingPeriod::class
|
* "accompanying_period"=AccompanyingPeriod::class
|
||||||
* })
|
* })
|
||||||
|
* @Assert\GroupSequenceProvider
|
||||||
*/
|
*/
|
||||||
class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface,
|
class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface,
|
||||||
HasScopesInterface, HasCentersInterface
|
HasScopesInterface, HasCentersInterface, GroupSequenceProviderInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Mark an accompanying period as "occasional"
|
* Mark an accompanying period as "occasional"
|
||||||
@ -132,6 +136,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
* cascade={"persist", "remove"},
|
* cascade={"persist", "remove"},
|
||||||
* orphanRemoval=true
|
* orphanRemoval=true
|
||||||
* )
|
* )
|
||||||
|
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_DRAFT})
|
||||||
*/
|
*/
|
||||||
private $comments;
|
private $comments;
|
||||||
|
|
||||||
@ -147,9 +152,10 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
* @var Collection
|
* @var Collection
|
||||||
*
|
*
|
||||||
* @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class,
|
* @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class,
|
||||||
* mappedBy="accompanyingPeriod",
|
* mappedBy="accompanyingPeriod", orphanRemoval=true,
|
||||||
* cascade={"persist", "refresh", "remove", "merge", "detach"})
|
* cascade={"persist", "refresh", "remove", "merge", "detach"})
|
||||||
* @Groups({"read"})
|
* @Groups({"read"})
|
||||||
|
* @ParticipationOverlap(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
|
||||||
*/
|
*/
|
||||||
private $participations;
|
private $participations;
|
||||||
|
|
||||||
@ -188,6 +194,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
* @ORM\ManyToOne(targetEntity=Origin::class)
|
* @ORM\ManyToOne(targetEntity=Origin::class)
|
||||||
* @ORM\JoinColumn(nullable=true)
|
* @ORM\JoinColumn(nullable=true)
|
||||||
* @Groups({"read", "write"})
|
* @Groups({"read", "write"})
|
||||||
|
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED})
|
||||||
*/
|
*/
|
||||||
private $origin;
|
private $origin;
|
||||||
|
|
||||||
@ -195,8 +202,9 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
* @var string
|
* @var string
|
||||||
* @ORM\Column(type="string", nullable=true)
|
* @ORM\Column(type="string", nullable=true)
|
||||||
* @Groups({"read", "write"})
|
* @Groups({"read", "write"})
|
||||||
|
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED})
|
||||||
*/
|
*/
|
||||||
private $intensity;
|
private $intensity = self::INTENSITY_OCCASIONAL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection
|
* @var Collection
|
||||||
@ -210,6 +218,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
* inverseJoinColumns={@ORM\JoinColumn(name="scope_id", referencedColumnName="id")}
|
* inverseJoinColumns={@ORM\JoinColumn(name="scope_id", referencedColumnName="id")}
|
||||||
* )
|
* )
|
||||||
* @Groups({"read"})
|
* @Groups({"read"})
|
||||||
|
* @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED})
|
||||||
*/
|
*/
|
||||||
private $scopes;
|
private $scopes;
|
||||||
|
|
||||||
@ -256,6 +265,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
* orphanRemoval=true
|
* orphanRemoval=true
|
||||||
* )
|
* )
|
||||||
* @Groups({"read"})
|
* @Groups({"read"})
|
||||||
|
* @ResourceDuplicateCheck(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED, "Default", "default"})
|
||||||
*/
|
*/
|
||||||
private $resources;
|
private $resources;
|
||||||
|
|
||||||
@ -267,6 +277,7 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
* name="chill_person_accompanying_period_social_issues"
|
* name="chill_person_accompanying_period_social_issues"
|
||||||
* )
|
* )
|
||||||
* @Groups({"read"})
|
* @Groups({"read"})
|
||||||
|
* @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED})
|
||||||
*/
|
*/
|
||||||
private Collection $socialIssues;
|
private Collection $socialIssues;
|
||||||
|
|
||||||
@ -606,6 +617,14 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
return $participation;
|
return $participation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove Participation
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function removeParticipation(AccompanyingPeriodParticipation $participation)
|
||||||
|
{
|
||||||
|
$participation->setAccompanyingPeriod(null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove Person
|
* Remove Person
|
||||||
@ -1115,4 +1134,17 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
|
|
||||||
return $centers ?? null;
|
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]];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,13 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Entity
|
* @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={
|
* @DiscriminatorMap(typeProperty="type", mapping={
|
||||||
* "accompanying_period_resource"=Resource::class
|
* "accompanying_period_resource"=Resource::class
|
||||||
* })
|
* })
|
||||||
|
@ -134,4 +134,11 @@ class AccompanyingPeriodParticipation
|
|||||||
{
|
{
|
||||||
return $this->endDate === null;
|
return $this->endDate === null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function checkSameStartEnd()
|
||||||
|
{
|
||||||
|
if($this->endDate == $this->startDate) {
|
||||||
|
$this->accompanyingPeriod->removeParticipation($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,16 +16,16 @@
|
|||||||
<comment v-if="accompanyingCourse.step === 'DRAFT'"></comment>
|
<comment v-if="accompanyingCourse.step === 'DRAFT'"></comment>
|
||||||
<confirm v-if="accompanyingCourse.step === 'DRAFT'"></confirm>
|
<confirm v-if="accompanyingCourse.step === 'DRAFT'"></confirm>
|
||||||
|
|
||||||
<div v-for="error in errorMsg" class="vue-component errors alert alert-danger">
|
<!-- <div v-for="error in errorMsg" v-bind:key="error.id" class="vue-component errors alert alert-danger">
|
||||||
<p>
|
<p>
|
||||||
<span>{{ error.sta }} {{ error.txt }}</span><br>
|
<span>{{ error.sta }} {{ error.txt }}</span><br>
|
||||||
<span>{{ $t(error.msg) }}</span>
|
<span>{{ $t(error.msg) }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div> -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapGetters, mapState } from 'vuex'
|
||||||
import Banner from './components/Banner.vue';
|
import Banner from './components/Banner.vue';
|
||||||
import StickyNav from './components/StickyNav.vue';
|
import StickyNav from './components/StickyNav.vue';
|
||||||
import OriginDemand from './components/OriginDemand.vue';
|
import OriginDemand from './components/OriginDemand.vue';
|
||||||
@ -55,11 +55,12 @@ export default {
|
|||||||
Comment,
|
Comment,
|
||||||
Confirm,
|
Confirm,
|
||||||
},
|
},
|
||||||
computed: mapState([
|
computed: {
|
||||||
|
...mapState([
|
||||||
'accompanyingCourse',
|
'accompanyingCourse',
|
||||||
'addressContext',
|
'addressContext'
|
||||||
'errorMsg'
|
]),
|
||||||
])
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -86,7 +86,8 @@ const postParticipation = (id, payload, method) => {
|
|||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.ok) { return response.json(); }
|
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 };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,13 +10,13 @@
|
|||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
name="selectOrigin"
|
name="selectOrigin"
|
||||||
label="text"
|
label="text"
|
||||||
v-bind:custom-label="transText"
|
:custom-label="transText"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
v-bind:multiple="false"
|
:multiple="false"
|
||||||
v-bind:searchable="true"
|
:searchable="true"
|
||||||
v-bind:placeholder="$t('origin.placeholder')"
|
:placeholder="$t('origin.placeholder')"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
v-bind:options="options"
|
:options="options"
|
||||||
@select="updateOrigin">
|
@select="updateOrigin">
|
||||||
</VueMultiselect>
|
</VueMultiselect>
|
||||||
|
|
||||||
@ -47,18 +47,18 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getOptions() {
|
getOptions() {
|
||||||
//console.log('loading origins list');
|
|
||||||
getListOrigins().then(response => new Promise((resolve, reject) => {
|
getListOrigins().then(response => new Promise((resolve, reject) => {
|
||||||
this.options = response.results;
|
this.options = response.results;
|
||||||
resolve();
|
resolve();
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
updateOrigin(value) {
|
updateOrigin(value) {
|
||||||
//console.log('value', value);
|
console.log('value', value);
|
||||||
this.$store.dispatch('updateOrigin', value);
|
this.$store.dispatch('updateOrigin', value);
|
||||||
},
|
},
|
||||||
transText ({ text }) {
|
transText ({ text }) {
|
||||||
return text.fr //TODO multilang
|
const parsedText = JSON.parse(text);
|
||||||
|
return parsedText.fr;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ import { createApp } from 'vue'
|
|||||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
|
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
|
||||||
import { appMessages } from './js/i18n'
|
import { appMessages } from './js/i18n'
|
||||||
import { initPromise } from './store'
|
import { initPromise } from './store'
|
||||||
|
import VueToast from 'vue-toast-notification';
|
||||||
|
import 'vue-toast-notification/dist/theme-sugar.css';
|
||||||
|
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import Banner from './components/Banner.vue';
|
import Banner from './components/Banner.vue';
|
||||||
@ -21,6 +23,7 @@ if (root === 'app') {
|
|||||||
})
|
})
|
||||||
.use(store)
|
.use(store)
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
|
.use(VueToast)
|
||||||
.component('app', App)
|
.component('app', App)
|
||||||
.mount('#accompanying-course');
|
.mount('#accompanying-course');
|
||||||
});
|
});
|
||||||
|
@ -77,7 +77,7 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
|
|||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
catchError(state, error) {
|
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);
|
state.errorMsg.push(error);
|
||||||
},
|
},
|
||||||
removeParticipation(state, participation) {
|
removeParticipation(state, participation) {
|
||||||
|
@ -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 $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()
|
public function getTargets()
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod;
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Annotation
|
||||||
|
*/
|
||||||
|
class ParticipationOverlap extends Constraint
|
||||||
|
{
|
||||||
|
public $message = 'This participation already exists.';
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Util\DateRangeCovering;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
||||||
|
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ParticipationOverlap;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
use Symfony\Component\Validator\ConstraintValidator;
|
||||||
|
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||||
|
|
||||||
|
class ParticipationOverlapValidator extends ConstraintValidator
|
||||||
|
{
|
||||||
|
private const MAX_PARTICIPATION = 1;
|
||||||
|
|
||||||
|
public function validate($participations, Constraint $constraint)
|
||||||
|
{
|
||||||
|
if (!$constraint instanceof ParticipationOverlap) {
|
||||||
|
throw new UnexpectedTypeException($constraint, ParticipationOverlap::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$participations instanceof Collection) {
|
||||||
|
throw new UnexpectedTypeException($participations, 'This should be a collection');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($participations) <= self::MAX_PARTICIPATION) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$overlaps = new DateRangeCovering(self::MAX_PARTICIPATION, $participations[0]->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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod;
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Annotation
|
||||||
|
*/
|
||||||
|
class ResourceDuplicateCheck extends Constraint
|
||||||
|
{
|
||||||
|
public $message = '{{ name }} is already associated to this accompanying course.';
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Templating\Entity\PersonRender;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
use Symfony\Component\Validator\ConstraintValidator;
|
||||||
|
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ResourceDuplicateCheck;
|
||||||
|
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
|
||||||
|
|
||||||
|
class ResourceDuplicateCheckValidator extends ConstraintValidator
|
||||||
|
{
|
||||||
|
|
||||||
|
private PersonRender $personRender;
|
||||||
|
private ThirdPartyRender $thirdpartyRender;
|
||||||
|
|
||||||
|
public function __construct(PersonRender $personRender, ThirdPartyRender $thirdPartyRender)
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
services:
|
||||||
|
Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
tags: ['validator.service_arguments']
|
||||||
|
|
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\Migrations\Person;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation added to accompanying period resources and accompanying period.
|
||||||
|
*/
|
||||||
|
final class Version20211020131133 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Validation added to accompanying period resources and accompanying period.';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->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');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\Migrations\Person;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom constraint added to database to prevent identical participations.
|
||||||
|
*/
|
||||||
|
final class Version20211021125359 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Custom constraint added to database to prevent identical participations.';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// creates a constraint 'participations may not overlap'
|
||||||
|
$this->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)');
|
||||||
|
}
|
||||||
|
}
|
@ -41,3 +41,6 @@ household:
|
|||||||
household_membership:
|
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.
|
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%.
|
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.'
|
Loading…
x
Reference in New Issue
Block a user