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
<!-- 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)
* [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

View File

@ -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
{

View File

@ -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)

View File

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

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
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