diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ea16e7fc..6a3d24a1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,8 @@ and this project adheres to ## Unreleased +* [person] prevent duplicate relationship in filiation/household graph (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/560) * [Documents] Validate storedObject and allow for null data (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/565) -* [Activity form] invert 'incoming' and 'receiving' in Activity form -* [Activity form] keep the same order for 'attendee' field in new and edit form -* [list with period] use "sameas" test operator to introduce requestor in list ## Test releases diff --git a/src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php b/src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php index d51881bb2..62fbb63a9 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php +++ b/src/Bundle/ChillPersonBundle/Entity/Relationships/Relationship.php @@ -15,6 +15,7 @@ use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Validator\Constraints\Relationship\RelationshipNoDuplicate; use DateTimeImmutable; use DateTimeInterface; use Doctrine\ORM\Mapping as ORM; @@ -31,6 +32,7 @@ use Symfony\Component\Validator\Constraints as Assert; * @DiscriminatorMap(typeProperty="type", mapping={ * "relationship": Relationship::class * }) + * @RelationshipNoDuplicate */ class Relationship implements TrackCreationInterface, TrackUpdateInterface { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue index f5db187b9..f86c68576 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue @@ -472,15 +472,25 @@ export default { case 'create': return postRelationship(this.modal.data) - .then(relationship => new Promise(resolve => { - //console.log('post relationship response', relationship) - this.$store.dispatch('addLinkFromRelationship', relationship) - this.modal.showModal = false - this.resetForm() - this.forceUpdateComponent() - resolve() - })) - .catch() + .then(relationship => new Promise(resolve => { + //console.log('post relationship response', relationship) + this.$store.dispatch('addLinkFromRelationship', relationship) + this.modal.showModal = false + this.resetForm() + this.forceUpdateComponent() + resolve() + })) + .catch( error => { + if (error.name === 'ValidationException') { + for (let v of error.violations) { + this.$toast.open({message: v }); + console.log(v) + } + } else { + this.$toast.open({message: 'An error occurred'}); + } + } + ) case 'edit': return patchRelationship(this.modal.data) diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/api.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/api.js index 448ff6633..01c335436 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/api.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/api.js @@ -1,50 +1,5 @@ -import { splitId } from './vis-network' - -/** - * @function makeFetch - * @param method - * @param url - * @param body - * @returns {Promise} - */ -const makeFetch = (method, url, body) => { - return fetch(url, { - method: method, - headers: { - 'Content-Type': 'application/json;charset=utf-8' - }, - body: (body !== null) ? JSON.stringify(body) : null - }) - .then(response => { - - if (response.ok) { - return response.json(); - } - - if (response.status === 422) { - return response.json().then(violations => { - throw ValidationException(violations) - }); - } - - throw { - msg: 'Error while updating AccompanyingPeriod Course.', - sta: response.status, - txt: response.statusText, - err: new Error(), - body: response.body - }; - }); -} - -/** - * @param violations - * @constructor - */ -const ValidationException = (violations) => { - this.violations = violations - this.name = 'ValidationException' -} +import { splitId } from './vis-network'; +import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods.js'; /** * @function getFetch @@ -136,7 +91,7 @@ const getRelationsList = () => { * @returns {Promise} */ const postRelationship = (relationship) => { - //console.log(relationship) + //console.log(relationship); return postFetch( `/api/1.0/relations/relationship.json`, { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js index 5e8989e49..a64afd4a1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js @@ -3,8 +3,10 @@ import { store } from "./store.js" import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' import { visMessages } from './i18n' import App from './App.vue' +import VueToast from 'vue-toast-notification'; +import 'vue-toast-notification/dist/theme-sugar.css'; -import './vis-network' +import './vis-network'; const i18n = _createI18n(visMessages) const container = document.getElementById('relationship-graph') @@ -25,5 +27,11 @@ const app = createApp({ }) .use(store) .use(i18n) +.use(VueToast, { + position: "bottom-right", + type: "error", + duration: 5000, + dismissible: true + }) .component('app', App) .mount('#relationship-graph') diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/Relationship/RelationshipNoDuplicate.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/Relationship/RelationshipNoDuplicate.php new file mode 100644 index 000000000..db4d7c0d6 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/Relationship/RelationshipNoDuplicate.php @@ -0,0 +1,27 @@ +relationshipRepository = $relationshipRepository; + } + + public function validate($relationship, Constraint $constraint) + { + if (!$constraint instanceof RelationshipNoDuplicate) { + throw new UnexpectedTypeException($constraint, RelationshipNoDuplicate::class); + } + + $fromPerson = $relationship->getFromPerson(); + $toPerson = $relationship->getToPerson(); + + $relationships = $this->relationshipRepository->findBy([ + 'fromPerson' => [$fromPerson, $toPerson], + 'toPerson' => [$fromPerson, $toPerson], + ]); + + foreach ($relationships as $r) { + if ( + $r->getFromPerson() === $fromPerson + || $r->getFromPerson() === $toPerson + || $r->getToPerson() === $fromPerson + || $r->getToPerson() === $toPerson + ) { + $this->context->buildViolation($constraint->message) + ->addViolation(); + } + } + } +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20220425000000.php b/src/Bundle/ChillPersonBundle/migrations/Version20220425000000.php new file mode 100644 index 000000000..1dd50d224 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20220425000000.php @@ -0,0 +1,36 @@ +addSql('DROP INDEX IDX_RELATIONSHIPS000'); + } + + public function getDescription(): string + { + return 'Add constraint with a index on chill_person_relationships.'; + } + + public function up(Schema $schema): void + { + $this->addSql('CREATE UNIQUE INDEX IDX_RELATIONSHIPS000 ON chill_person_relationships (least(fromperson_id, toperson_id), greatest(fromperson_id, toperson_id))'); + } +} diff --git a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml index 13fcb54fb..b2bc549d7 100644 --- a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml @@ -62,3 +62,7 @@ You cannot associate a resource with the same person: Vous ne pouvez pas ajouter #location The period must remain located: 'Un parcours doit être localisé' The person where the course is located must be associated to the course. Change course's location before removing the person.: "Le parcours est localisé auprès cet usager. Veuillez changer la localisation du parcours avant de suprimer l'usager" + +#relationship +relationship: + duplicate: Une relation de filiation existe déjà entre ces 2 personnes \ No newline at end of file