Merge branch 'issue560_relationship_validation' into 'master'

person: add validation to relationship to avoid duplicate

See merge request Chill-Projet/chill-bundles!415
This commit is contained in:
Julien Fastré 2022-04-27 13:28:21 +00:00
commit a8527b6cbb
9 changed files with 155 additions and 61 deletions

View File

@ -11,10 +11,8 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [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) * [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 ## Test releases

View File

@ -15,6 +15,7 @@ use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Validator\Constraints\Relationship\RelationshipNoDuplicate;
use DateTimeImmutable; use DateTimeImmutable;
use DateTimeInterface; use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@ -31,6 +32,7 @@ use Symfony\Component\Validator\Constraints as Assert;
* @DiscriminatorMap(typeProperty="type", mapping={ * @DiscriminatorMap(typeProperty="type", mapping={
* "relationship": Relationship::class * "relationship": Relationship::class
* }) * })
* @RelationshipNoDuplicate
*/ */
class Relationship implements TrackCreationInterface, TrackUpdateInterface class Relationship implements TrackCreationInterface, TrackUpdateInterface
{ {

View File

@ -472,15 +472,25 @@ export default {
case 'create': case 'create':
return postRelationship(this.modal.data) return postRelationship(this.modal.data)
.then(relationship => new Promise(resolve => { .then(relationship => new Promise(resolve => {
//console.log('post relationship response', relationship) //console.log('post relationship response', relationship)
this.$store.dispatch('addLinkFromRelationship', relationship) this.$store.dispatch('addLinkFromRelationship', relationship)
this.modal.showModal = false this.modal.showModal = false
this.resetForm() this.resetForm()
this.forceUpdateComponent() this.forceUpdateComponent()
resolve() resolve()
})) }))
.catch() .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': case 'edit':
return patchRelationship(this.modal.data) return patchRelationship(this.modal.data)

View File

@ -1,50 +1,5 @@
import { splitId } from './vis-network' import { splitId } from './vis-network';
import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods.js';
/**
* @function makeFetch
* @param method
* @param url
* @param body
* @returns {Promise<Response>}
*/
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'
}
/** /**
* @function getFetch * @function getFetch
@ -136,7 +91,7 @@ const getRelationsList = () => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
const postRelationship = (relationship) => { const postRelationship = (relationship) => {
//console.log(relationship) //console.log(relationship);
return postFetch( return postFetch(
`/api/1.0/relations/relationship.json`, `/api/1.0/relations/relationship.json`,
{ {

View File

@ -3,8 +3,10 @@ import { store } from "./store.js"
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
import { visMessages } from './i18n' import { visMessages } from './i18n'
import App from './App.vue' 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 i18n = _createI18n(visMessages)
const container = document.getElementById('relationship-graph') const container = document.getElementById('relationship-graph')
@ -25,5 +27,11 @@ const app = createApp({
}) })
.use(store) .use(store)
.use(i18n) .use(i18n)
.use(VueToast, {
position: "bottom-right",
type: "error",
duration: 5000,
dismissible: true
})
.component('app', App) .component('app', App)
.mount('#relationship-graph') .mount('#relationship-graph')

View File

@ -0,0 +1,27 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Validator\Constraints\Relationship;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class RelationshipNoDuplicate extends Constraint
{
public $message = 'relationship.duplicate';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\PersonBundle\Validator\Constraints\Relationship;
use Chill\PersonBundle\Repository\Relationships\RelationshipRepository;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class RelationshipNoDuplicateValidator extends ConstraintValidator
{
private RelationshipRepository $relationshipRepository;
public function __construct(RelationshipRepository $relationshipRepository)
{
$this->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();
}
}
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add constraint with a index on chill_person_relationships.
*/
final class Version20220425000000 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->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))');
}
}

View File

@ -62,3 +62,7 @@ You cannot associate a resource with the same person: Vous ne pouvez pas ajouter
#location #location
The period must remain located: 'Un parcours doit être localisé' 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" 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