Merge remote-tracking branch 'origin/master' into issue464_document_on_person

This commit is contained in:
2022-03-03 14:42:06 +01:00
103 changed files with 1496 additions and 534 deletions

View File

@@ -322,19 +322,39 @@ final class AccompanyingCourseApiController extends ApiController
/**
* @Route("/api/1.0/person/accompanying-course/{id}/confidential.json", name="chill_api_person_accompanying_period_confidential")
* @ParamConverter("accompanyingCourse", options={"id": "id"})
*
* @param mixed $id
*/
public function toggleConfidentialApi(AccompanyingPeriod $accompanyingCourse, Request $request)
public function toggleConfidentialApi(AccompanyingPeriod $accompanyingCourse, $id, Request $request)
{
if ($request->getMethod() === 'POST') {
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::TOGGLE_CONFIDENTIAL, $accompanyingCourse);
$accompanyingCourse->setConfidential(!$accompanyingCourse->isConfidential());
$this->getDoctrine()->getManager()->flush();
}
return $this->json($accompanyingCourse->isConfidential(), Response::HTTP_OK, [], ['groups' => ['read']]);
}
/**
* @Route("/api/1.0/person/accompanying-course/{id}/intensity.json", name="chill_api_person_accompanying_period_intensity")
* @ParamConverter("accompanyingCourse", options={"id": "id"})
*/
public function toggleIntensityApi(AccompanyingPeriod $accompanyingCourse, Request $request)
{
if ($request->getMethod() === 'POST') {
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::TOGGLE_INTENSITY, $accompanyingCourse);
$status = $accompanyingCourse->getIntensity() === 'regular' ? 'occasional' : 'regular';
$accompanyingCourse->setIntensity($status);
$this->getDoctrine()->getManager()->flush();
}
return $this->json($accompanyingCourse->getIntensity(), Response::HTTP_OK, [], ['groups' => ['read']]);
}
public function workApi($id, Request $request, string $_format): Response
{
return $this->addRemoveSomething(

View File

@@ -69,7 +69,7 @@ class AccompanyingPeriodWorkEvaluationApiController
$evaluations =
array_filter(
$this->docGeneratorTemplateRepository
->findByEntity(AccompanyingPeriodWorkEvaluation::class),
->findByEntity(AccompanyingPeriodWorkEvaluation::class, 0, 500),
static function (DocGeneratorTemplate $t) use ($evaluation) {
$ids = $t->getOptions()['evaluations'] ?? [];

View File

@@ -11,10 +11,10 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\Entity\ResidentialAddress;
use Chill\MainBundle\Form\Type\ResidentialAddressType;
use Chill\MainBundle\Repository\ResidentialAddressRepository;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Person\ResidentialAddress;
use Chill\PersonBundle\Form\Type\ResidentialAddressType;
use Chill\PersonBundle\Repository\ResidentialAddressRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType;

View File

@@ -395,16 +395,16 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE,
],
],
'confidential' => [
'methods' => [
Request::METHOD_POST => true,
Request::METHOD_GET => true,
],
'controller_action' => 'toggleConfidentialApi',
'roles' => [
Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::TOGGLE_CONFIDENTIAL,
],
],
// 'confidential' => [
// 'methods' => [
// Request::METHOD_POST => true,
// Request::METHOD_GET => true,
// ],
// 'controller_action' => 'toggleConfidentialApi',
// 'roles' => [
// Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::TOGGLE_CONFIDENTIAL,
// ],
// ],
'findAccompanyingPeriodsByPerson' => [
'path' => '/by-person/{person_id}.{_format}',
'controller_action' => 'getAccompanyingPeriodsByPerson',

View File

@@ -30,6 +30,8 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
use Chill\PersonBundle\Entity\AccompanyingPeriod\UserHistory;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\AccompanyingPeriodValidity;
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ConfidentialCourseMustHaveReferrer;
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\LocationValidity;
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ParticipationOverlap;
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ResourceDuplicateCheck;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
@@ -62,12 +64,9 @@ use const SORT_REGULAR;
* "accompanying_period": AccompanyingPeriod::class
* })
* @Assert\GroupSequenceProvider
* @Assert\Expression(
* "this.isConfidential and this.getUser === NULL",
* message="If the accompanying course is confirmed and confidential, a referrer must remain assigned."
* )
*
* @AccompanyingPeriodValidity(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
* @LocationValidity(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
* @ConfidentialCourseMustHaveReferrer(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
*/
class AccompanyingPeriod implements
GroupSequenceProviderInterface,
@@ -201,7 +200,7 @@ class AccompanyingPeriod implements
/**
* @var string
* @ORM\Column(type="string", nullable=true)
* @Groups({"read", "write"})
* @Groups({"read"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED})
*/
private $intensity = self::INTENSITY_OCCASIONAL;
@@ -1000,7 +999,7 @@ class AccompanyingPeriod implements
}
/**
* Validation function.
* Validation functions.
*/
public function isDateConsistent(ExecutionContextInterface $context)
{

View File

@@ -37,6 +37,7 @@ use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
use Exception;
use libphonenumber\PhoneNumber;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
@@ -371,15 +372,10 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/**
* The person's mobile phone number.
*
* @ORM\Column(type="text")
* @Assert\Regex(
* pattern="/^([\+{1}])([0-9\s*]{4,20})$/",
* )
* @PhonenumberConstraint(
* type="mobile",
* )
* @PhonenumberConstraint(type="mobile")
* @ORM\Column(type="phone_number", nullable=true)
*/
private string $mobilenumber = '';
private ?PhoneNumber $mobilenumber = null;
/**
* The person's nationality.
@@ -429,15 +425,12 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/**
* The person's phonenumber.
*
* @ORM\Column(type="text")
* @Assert\Regex(
* pattern="/^([\+{1}])([0-9\s*]{4,20})$/",
* )
* @ORM\Column(type="phone_number", nullable=true)
* @PhonenumberConstraint(
* type="landline",
* )
*/
private string $phonenumber = '';
private ?PhoneNumber $phonenumber = null;
/**
* The person's place of birth.
@@ -1227,10 +1220,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this->memo;
}
/**
* Get mobilenumber.
*/
public function getMobilenumber(): string
public function getMobilenumber(): ?PhoneNumber
{
return $this->mobilenumber;
}
@@ -1295,10 +1285,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this->otherPhoneNumbers;
}
/**
* Get phonenumber.
*/
public function getPhonenumber(): string
public function getPhonenumber(): ?PhoneNumber
{
return $this->phonenumber;
}
@@ -1737,16 +1724,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this;
}
/**
* Set mobilenumber.
*
* @param string $mobilenumber
*
* @return Person
*/
public function setMobilenumber(?string $mobilenumber = '')
public function setMobilenumber(?PhoneNumber $mobilenumber)
{
$this->mobilenumber = (string) $mobilenumber;
$this->mobilenumber = $mobilenumber;
return $this;
}
@@ -1782,16 +1762,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this;
}
/**
* Set phonenumber.
*
* @param string $phonenumber
*
* @return Person
*/
public function setPhonenumber(?string $phonenumber = '')
public function setPhonenumber(?PhoneNumber $phonenumber)
{
$this->phonenumber = (string) $phonenumber;
$this->phonenumber = $phonenumber;
return $this;
}

View File

@@ -0,0 +1,175 @@
<?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\Entity\Person;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\ResidentialAddressRepository;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Context;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @ORM\Entity(repositoryClass=ResidentialAddressRepository::class)
* @ORM\Table(name="chill_person_residential_address")
*/
class ResidentialAddress
{
/**
* @ORM\ManyToOne(targetEntity=Address::class)
* @ORM\JoinColumn(nullable=true)
* @Groups({"read"})
*/
private ?Address $address = null;
/**
* @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="residentialAddressComment_")
*/
private CommentEmbeddable $comment;
/**
* @ORM\Column(type="datetime_immutable", nullable=true)
* @Groups({"read"})
*/
private ?DateTimeImmutable $endDate = null;
/**
* @ORM\ManyToOne(targetEntity=Person::class)
* @ORM\JoinColumn(nullable=true)
* @Groups({"read"})
* @Context(normalizationContext={"groups": {"minimal"}})
*/
private ?Person $hostPerson = null;
/**
* @ORM\ManyToOne(targetEntity=ThirdParty::class)
* @ORM\JoinColumn(nullable=true)
* @Groups({"read"})
*/
private ?ThirdParty $hostThirdParty = null;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=Person::class)
* @ORM\JoinColumn(nullable=false)
*/
private Person $person;
/**
* @ORM\Column(type="datetime_immutable")
* @Groups({"read"})
*/
private ?DateTimeImmutable $startDate = null;
public function __construct()
{
$this->comment = new CommentEmbeddable();
}
public function getAddress(): ?Address
{
return $this->address;
}
public function getComment(): CommentEmbeddable
{
return $this->comment;
}
public function getEndDate(): ?DateTimeImmutable
{
return $this->endDate;
}
public function getHostPerson(): ?Person
{
return $this->hostPerson;
}
public function getHostThirdParty(): ?ThirdParty
{
return $this->hostThirdParty;
}
public function getId(): ?int
{
return $this->id;
}
public function getPerson(): ?Person
{
return $this->person;
}
public function getStartDate(): ?DateTimeImmutable
{
return $this->startDate;
}
public function setAddress(?Address $address): self
{
$this->address = $address;
return $this;
}
public function setComment(CommentEmbeddable $comment): self
{
$this->comment = $comment;
return $this;
}
public function setEndDate(?DateTimeImmutable $endDate): self
{
$this->endDate = $endDate;
return $this;
}
public function setHostPerson(?Person $hostPerson): self
{
$this->hostPerson = $hostPerson;
return $this;
}
public function setHostThirdParty(?ThirdParty $hostThirdParty): self
{
$this->hostThirdParty = $hostThirdParty;
return $this;
}
public function setPerson(?Person $person): self
{
$this->person = $person;
return $this;
}
public function setStartDate(DateTimeImmutable $startDate): self
{
$this->startDate = $startDate;
return $this;
}
}

View File

@@ -13,17 +13,18 @@ namespace Chill\PersonBundle\Form;
use Chill\MainBundle\Form\Event\CustomizeFormEvent;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
use Chill\MainBundle\Form\Type\PickCenterType;
use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\Type\GenderType;
use Chill\PersonBundle\Form\Type\PersonAltNameType;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use libphonenumber\PhoneNumberType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -60,11 +61,13 @@ final class CreationPersonType extends AbstractType
->add('birthdate', ChillDateType::class, [
'required' => false,
])
->add('phonenumber', TelType::class, [
->add('phonenumber', ChillPhoneNumberType::class, [
'required' => false,
'type' => PhoneNumberType::FIXED_LINE,
])
->add('mobilenumber', TelType::class, [
->add('mobilenumber', ChillPhoneNumberType::class, [
'required' => false,
'type' => PhoneNumberType::MOBILE,
])
->add('email', EmailType::class, [
'required' => false,

View File

@@ -14,26 +14,27 @@ namespace Chill\PersonBundle\Form;
use Chill\CustomFieldsBundle\Form\Type\CustomFieldType;
use Chill\MainBundle\Form\Type\ChillCollectionType;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PickCivilityType;
use Chill\MainBundle\Form\Type\Select2CountryType;
use Chill\MainBundle\Form\Type\Select2LanguageType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\PersonPhone;
use Chill\PersonBundle\Form\Type\GenderType;
use Chill\PersonBundle\Form\Type\PersonAltNameType;
use Chill\PersonBundle\Form\Type\PersonPhoneType;
use Chill\PersonBundle\Form\Type\Select2MaritalStatusType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -57,17 +58,21 @@ class PersonType extends AbstractType
protected TranslatableStringHelper $translatableStringHelper;
private ParameterBagInterface $parameterBag;
/**
* @param string[] $personFieldsConfiguration configuration of visibility of some fields
*/
public function __construct(
array $personFieldsConfiguration,
ConfigPersonAltNamesHelper $configAltNamesHelper,
TranslatableStringHelper $translatableStringHelper
TranslatableStringHelperInterface $translatableStringHelper,
ParameterBagInterface $parameterBag
) {
$this->config = $personFieldsConfiguration;
$this->configAltNamesHelper = $configAltNamesHelper;
$this->translatableStringHelper = $translatableStringHelper;
$this->parameterBag = $parameterBag;
}
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -126,22 +131,34 @@ class PersonType extends AbstractType
}
if ('visible' === $this->config['phonenumber']) {
$builder->add('phonenumber', TelType::class, [
'required' => false,
// 'placeholder' => '+33623124554' //TODO placeholder for phone numbers
]);
$builder
->add(
'phonenumber',
ChillPhoneNumberType::class,
[
'required' => false,
'type' => \libphonenumber\PhoneNumberType::FIXED_LINE,
]
);
}
if ('visible' === $this->config['mobilenumber']) {
$builder
->add('mobilenumber', TelType::class, ['required' => false])
->add(
'mobilenumber',
ChillPhoneNumberType::class,
[
'type' => \libphonenumber\PhoneNumberType::MOBILE,
'required' => false,
]
)
->add('acceptSMS', CheckboxType::class, [
'required' => false,
]);
}
$builder->add('otherPhoneNumbers', ChillCollectionType::class, [
'entry_type' => PersonPhoneType::class,
'entry_type' => ChillPhoneNumberType::class,
'button_add_label' => 'Add new phone',
'button_remove_label' => 'Remove phone',
'required' => false,

View File

@@ -0,0 +1,74 @@
<?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\Form\Type;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PickAddressType;
use Chill\PersonBundle\Entity\Person\ResidentialAddress;
use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class ResidentialAddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('startDate', DateType::class, [
'required' => true,
'input' => 'datetime_immutable',
'widget' => 'single_text',
])
->add('endDate', DateType::class, [
'required' => false,
'input' => 'datetime_immutable',
'widget' => 'single_text',
])
->add('comment', CommentType::class, [
'required' => false,
]);
if ('person' === $options['kind']) {
$builder
->add('hostPerson', PickPersonDynamicType::class, [
'label' => 'Person',
]);
}
if ('thirdparty' === $options['kind']) {
$builder
->add('hostThirdParty', PickThirdpartyDynamicType::class, [
'label' => 'Third party',
]);
}
if ('address' === $options['kind']) {
$builder
->add('address', PickAddressType::class, [
'required' => false,
'label' => 'Address',
'use_valid_from' => false,
'use_valid_to' => false,
]);
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ResidentialAddress::class,
'kind' => null,
]);
}
}

View File

@@ -0,0 +1,96 @@
<?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\Repository;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Person\ResidentialAddress;
use DateTimeImmutable;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method ResidentialAddress|null find($id, $lockMode = null, $lockVersion = null)
* @method ResidentialAddress|null findOneBy(array $criteria, array $orderBy = null)
* @method ResidentialAddress[] findAll()
* @method ResidentialAddress[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ResidentialAddressRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ResidentialAddress::class);
}
public function buildQueryFindCurrentResidentialAddresses(Person $person, ?DateTimeImmutable $at = null): QueryBuilder
{
$date = null === $at ? new DateTimeImmutable('today') : $at;
$qb = $this->createQueryBuilder('ra');
$dateFilter = $qb->expr()->andX(
$qb->expr()->lte('ra.startDate', ':dateIn'),
$qb->expr()->orX(
$qb->expr()->isNull('ra.endDate'),
$qb->expr()->gte('ra.endDate', ':dateIn')
)
);
$qb
->where($dateFilter)
->setParameter('dateIn', $date, Types::DATE_IMMUTABLE)
->andWhere('ra.person = :person')
->setParameter('person', $person);
return $qb;
}
/**
* @return array|ResidentialAddress[]|null
*/
public function findCurrentResidentialAddressByPerson(Person $person, ?DateTimeImmutable $at = null): array
{
return $this->buildQueryFindCurrentResidentialAddresses($person, $at)
->select('ra')
->getQuery()
->getResult();
}
// /**
// * @return ResidentialAddress[] Returns an array of ResidentialAddress objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('r')
->andWhere('r.exampleField = :val')
->setParameter('val', $value)
->orderBy('r.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?ResidentialAddress
{
return $this->createQueryBuilder('r')
->andWhere('r.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}

View File

@@ -58,7 +58,7 @@ export default {
this.$store.dispatch('toggleIntensity', value)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
this.$toast.open({message: this.$t('Only the referrer can toggle the intensity of an accompanying course')})
} else {
this.$toast.open({message: 'An error occurred'})
}
@@ -75,20 +75,15 @@ export default {
});
},
toggleConfidential() {
this.$store.dispatch('fetchPermissions').then(() => {
if (!this.$store.getters.canTogglePermission) {
this.$toast.open({message: "Seul le référent peut modifier la confidentialité"});
return Promise.resolve();
} else {
return this.$store.dispatch('toggleConfidential', (!this.isConfidential));
}
}).catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
this.$store.dispatch('toggleConfidential')
.catch(({name, violations}) => {
console.log(name);
if (name === 'ValidationException' || name === 'AccessException') {
this.$toast.open({message: this.$t('Only the referrer can toggle the confidentiality of an accompanying course')})
} else {
this.$toast.open({message: 'An error occurred'})
}
});
},
},
}

View File

@@ -9,7 +9,7 @@
<input type="checkbox" v-model="requestorIsAnonymous" class="me-2" />
{{ $t('requestor.is_anonymous') }}
</label>
<confidential v-if="accompanyingCourse.requestor.type === 'thirdparty'">
<confidential :positionBtn="false" v-if="accompanyingCourse.requestor.type === 'thirdparty'">
<template v-slot:confidential-content>
<third-party-render-box
:thirdparty="accompanyingCourse.requestor"
@@ -33,7 +33,7 @@
</template>
</confidential>
<confidential v-else-if="accompanyingCourse.requestor.type === 'person'">
<confidential :positionBtnFar="false" v-else-if="accompanyingCourse.requestor.type === 'person'">
<template v-slot:confidential-content>
<person-render-box render="bloc"
:person="accompanyingCourse.requestor"

View File

@@ -168,7 +168,7 @@ export default {
}
else if (payload.type === 'thirdparty') {
console.log('data', payload.data)
body.name = payload.data.text;
body.name = payload.data.name;
body.email = payload.data.email;
body.telephone = payload.data.phonenumber;
body.address = payload.data.address ? { id: payload.data.address.address_id } : null;

View File

@@ -46,6 +46,12 @@ if (root === 'banner') {
})
.use(store)
.use(i18n)
.use(VueToast, {
position: "bottom-right",
type: "error",
duration: 5000,
dismissible: true
})
.component('banner', Banner)
.mount('#banner-accompanying-course');
});

View File

@@ -167,6 +167,8 @@ const appMessages = {
'Error while retriving users.': "Erreur du serveur lors du chargement de la liste des travailleurs.",
'Error while getting whoami.': "Erreur du serveur lors de la requête 'qui suis-je ?'",
'Error while retriving origin\'s list.': "Erreur du serveur lors du chargement de la liste des origines de la demande.",
'Only the referrer can toggle the intensity of an accompanying course': "Seul le référent peut modifier l'intensité d'un parcours.",
'Only the referrer can toggle the confidentiality of an accompanying course': "Seul le référent peut modifier la confidentialité d'un parcours."
}
};

View File

@@ -420,24 +420,24 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
const url = `/api/1.0/person/accompanying-course/resource/${id}.json`;
return makeFetch('PATCH', url, body)
.then((response) => {
commit('patchResource', response);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
.then((response) => {
commit('patchResource', response);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
},
/**
* Update accompanying course intensity/emergency/confidentiality
*/
toggleIntensity({ commit }, payload) {
const url = `/api/1.0/person/accompanying-course/${id}.json`
const url = `/api/1.0/person/accompanying-course/${id}/intensity.json`
const body = { type: "accompanying_period", 'intensity': payload }
return makeFetch('PATCH', url, body)
return makeFetch('POST', url, body)
.then((response) => {
commit('toggleIntensity', response.intensity);
commit('toggleIntensity', response);
})
.catch((error) => {
@@ -459,14 +459,18 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
})
},
toggleConfidential({ commit }, payload) {
const url = `/api/1.0/person/accompanying-course/${id}.json`
const url = `/api/1.0/person/accompanying-course/${id}/confidential.json`
const body = { type: "accompanying_period", confidential: payload }
return makeFetch('PATCH', url, body)
console.log('url', url, 'body', body);
return makeFetch('POST', url, body)
.then((response) => {
commit('toggleConfidential', response.confidential);
console.log('response', response);
commit('toggleConfidential', response);
})
.catch((error) => {
console.log('error', error)
commit('catchError', error);
throw error;
})
@@ -737,10 +741,10 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
"object": {
"type": "accompanying_period",
"id": id
},
"class": "Chill\\PersonBundle\\Entity\\AccompanyingPeriod",
"roles": [
"CHILL_PERSON_ACCOMPANYING_PERIOD_TOGGLE_CONFIDENTIAL"
},
"class": "Chill\\PersonBundle\\Entity\\AccompanyingPeriod",
"roles": [
"CHILL_PERSON_ACCOMPANYING_PERIOD_TOGGLE_CONFIDENTIAL"
]
}

View File

@@ -113,15 +113,6 @@
<p class="chill-no-data-statement">{{ $t('renderbox.no_data') }}</p>
</li>
<li v-if="person.email">
<i class="fa fa-li fa-envelope-o"></i>
<a :href="'mailto: ' + person.email">{{ person.email }}</a>
</li>
<li v-else-if="options.addNoData">
<i class="fa fa-li fa-envelope-o"></i>
<p class="chill-no-data-statement">{{ $t('renderbox.no_data') }}</p>
</li>
<li v-if="person.centers !== undefined && person.centers.length > 0 && options.addCenter">
<i class="fa fa-li fa-long-arrow-right"></i>
<template v-for="c in person.centers">{{ c.name }}</template>
@@ -135,7 +126,53 @@
</ul>
</div>
</div>
</div>
<div class="item-col mx-3" v-if="this.showResidentialAddresses && (person.current_residential_addresses || []).length > 0">
<div class="float-button bottom">
<div class="box" >
<ul class="list-content fa-ul">
<li v-for="(addr, i) in person.current_residential_addresses" :key="i">
<i class="fa fa-li fa-map-marker"></i>
<div v-if="addr.address">
<address-render-box
:address="addr.address"
:isMultiline="isMultiline">
</address-render-box>
<p>({{ $t('renderbox.residential_address') }})</p>
</div>
<div v-else-if="addr.hostPerson" class="mt-3">
<p>{{ $t('renderbox.located_at') }}:</p>
<span class="chill-entity entity-person badge-person">
<person-text
v-if="addr.hostPerson"
:person="addr.hostPerson"
></person-text>
</span>
<address-render-box v-if="addr.hostPerson.address"
:address="addr.hostPerson.address"
:isMultiline="isMultiline">
</address-render-box>
</div>
<div v-else-if="addr.hostThirdParty" class="mt-3">
<p>{{ $t('renderbox.located_at') }}:</p>
<span class="chill-entity entity-person badge-thirdparty">
<third-party-text
v-if="addr.hostThirdParty"
:thirdparty="addr.hostThirdParty"
></third-party-text>
</span>
<address-render-box v-if="addr.hostThirdParty.address"
:address="addr.hostThirdParty.address"
:isMultiline="isMultiline">
</address-render-box>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</section>
@@ -168,6 +205,7 @@ import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRe
import Confidential from 'ChillMainAssets/vuejs/_components/Confidential.vue';
import BadgeEntity from 'ChillMainAssets/vuejs/_components/BadgeEntity.vue';
import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue';
import ThirdPartyText from 'ChillThirdPartyAssets/vuejs/_components/Entity/ThirdPartyText.vue';
export default {
name: "PersonRenderBox",
@@ -175,9 +213,28 @@ export default {
AddressRenderBox,
Confidential,
BadgeEntity,
PersonText
PersonText,
ThirdPartyText
},
props: {
person: {
required: true,
},
options: {
type: Object,
required: false,
},
render: {
type: String,
},
returnPath: {
type: String,
},
showResidentialAddresses: {
type: Boolean,
default: false,
}
},
props: ['person', 'options', 'render', 'returnPath'],
computed: {
isMultiline: function() {
if(this.options.isMultiline){

View File

@@ -15,6 +15,7 @@
addNoData: true,
isMultiline: true
}"
:show-residential-addresses="true"
></person-render-box>
</div>
</div>

View File

@@ -151,14 +151,14 @@
{% if accompanyingCourse.requestorPerson is not null %}
<h4 class="item-key">{{ 'Requestor'|trans }}</h4>
{% if accompanyingCourse.requestorAnonymous %}
<div class="confidential"><p>{{ _self.insert_onthefly('person', accompanyingCourse.requestorPerson) }}</p></div>
<div class="confidential toggle-close-twig"><p>{{ _self.insert_onthefly('person', accompanyingCourse.requestorPerson) }}</p></div>
{% else %}
{{ _self.insert_onthefly('person', accompanyingCourse.requestorPerson) }}
{% endif %}
{% elseif accompanyingCourse.requestorThirdParty is not null %}
<h4 class="item-key">{{ 'Requestor'|trans }}</h4>
{% if accompanyingCourse.requestorAnonymous %}
<div class="confidential"><p>{{ _self.insert_onthefly('thirdparty', accompanyingCourse.requestorThirdParty) }}</p></div>
<div class="confidential toggle-close-twig"><p>{{ _self.insert_onthefly('thirdparty', accompanyingCourse.requestorThirdParty) }}</p></div>
{% else %}
{{ _self.insert_onthefly('thirdparty', accompanyingCourse.requestorThirdParty) }}
{% endif %}

View File

@@ -107,37 +107,35 @@
</div>
{% if displayAction is defined and displayAction == true %}
<div class="item-col">
<ul class="record_actions">
{% set suppEvaluations = [] %}
{% for e in w.accompanyingPeriodWorkEvaluations %}
{% set suppEvaluations = suppEvaluations|merge([
{'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation', 'relatedEntityId': e.id }
]) %}
<ul class="item-col record_actions">
{% set suppEvaluations = [] %}
{% for e in w.accompanyingPeriodWorkEvaluations %}
{% set suppEvaluations = suppEvaluations|merge([
{'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation', 'relatedEntityId': e.id }
]) %}
{% for d in e.documents %}
{% set suppEvaluations = suppEvaluations|merge([
{'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', 'relatedEntityId': d.id }
]) %}
{% endfor %}
{% endfor %}
<li>
{{ chill_entity_workflow_list(
'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork',
w.id, [], suppEvaluations) }}
</li>
<li>
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
></a>
</li>
<li>
<a class="btn btn-delete" title="{{ 'Delete'|trans }}"
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}"
></a>
</li>
</ul>
</div>
{% for d in e.documents %}
{% set suppEvaluations = suppEvaluations|merge([
{'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', 'relatedEntityId': d.id }
]) %}
{% endfor %}
{% endfor %}
<li>
{{ chill_entity_workflow_list(
'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork',
w.id, [], suppEvaluations) }}
</li>
<li>
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
></a>
</li>
<li>
<a class="btn btn-delete" title="{{ 'Delete'|trans }}"
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}"
></a>
</li>
</ul>
{% endif %}
</div>

View File

@@ -146,22 +146,27 @@
'with_valid_from': false
}) }}
{% endif %}
<li>
{% if person.mobilenumber %}
<i class="fa fa-li fa-mobile"></i><a href="{{ 'tel:' ~ person.mobilenumber }}">
{% if person.phonenumber is not null %}
<li>
<i class="fa fa-li fa-phone"></i>
<a href="{{ 'tel:' ~ person.phonenumber|phone_number_format('E164') }}">
{{ person.phonenumber|chill_format_phonenumber }}
</a>
</li>
{% endif %}
{% if person.mobilenumber is not null %}
<li>
<i class="fa fa-li fa-mobile"></i><a href="{{ 'tel:' ~ person.mobilenumber|phone_number_format('E164') }}">
{{ person.mobilenumber|chill_format_phonenumber }}
</a>
{% else %}
</li>
{% endif %}
{% if person.phonenumber is null and person.mobilenumber is null %}
<li>
<i class="fa fa-li fa-phone"></i>
{% if person.phonenumber %}
<a href="{{ 'tel:' ~ person.phonenumber }}">
{{ person.phonenumber|chill_format_phonenumber }}
</a>
{% else %}
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
{% endif %}
{% endif %}
</li>
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
</li>
{% endif %}
{% if options['addCenter'] and person|chill_resolve_center|length > 0 %}
<li>
<i class="fa fa-li fa-long-arrow-right"></i>

View File

@@ -25,14 +25,14 @@
{% if person.phonenumber %}
<span class="phonenumber d-block d-sm-inline-block">
<i class="fa fa-fw fa-phone"></i>
<a href="{{ 'tel:' ~ person.phonenumber }}" class="phone mr-3" title="{{ 'Phonenumber'|trans }}">
<a href="{{ 'tel:' ~ person.phonenumber|phone_number_format('E164') }}" class="phone mr-3" title="{{ 'Phonenumber'|trans }}">
{{ person.phonenumber|chill_format_phonenumber }}</a>
</span>
{% endif %}
{% if person.mobilenumber %}
<span class="mobilenumber d-block d-sm-inline-block">
<i class="fa fa-fw fa-mobile"></i>
<a href="{{ 'tel:' ~ person.mobilenumber }}" class="phone mr-3" title="{{ 'Mobilenumber'|trans }}">
<a href="{{ 'tel:' ~ person.mobilenumber|phone_number_format('E164') }}" class="phone mr-3" title="{{ 'Mobilenumber'|trans }}">
{{ person.mobilenumber|chill_format_phonenumber }}</a>
</span>
{% endif %}

View File

@@ -62,12 +62,12 @@
<ul>
{% if person.phonenumber is not empty %}
<li>
<a href="tel:{{ person.phonenumber }}"><img src="{{ asset('build/images/mobile-alt-solid.svg') }}">&nbsp;<pre>{{ person.phonenumber|chill_format_phonenumber }}</pre></a>
<a href="tel:{{ person.phonenumber|phone_number_format('E164') }}"><img src="{{ asset('build/images/mobile-alt-solid.svg') }}">&nbsp;<pre>{{ person.phonenumber|chill_format_phonenumber }}</pre></a>
</li>
{% endif %}
{% if person.mobilenumber is not empty%}
<li>
<a href="tel:{{ person.mobilenumber }}"><img src="{{ asset('build/images/phone-alt-solid.svg') }}">&nbsp;<pre>{{ person.mobilenumber|chill_format_phonenumber }}</pre></a>
<a href="tel:{{ person.mobilenumber|phone_number_format('E164') }}"><img src="{{ asset('build/images/phone-alt-solid.svg') }}">&nbsp;<pre>{{ person.mobilenumber|chill_format_phonenumber }}</pre></a>
</li>
{% endif %}
</ul>

View File

@@ -232,14 +232,14 @@ This view should receive those arguments:
{%- if chill_person.fields.phonenumber == 'visible' -%}
<dl>
<dt>{{ 'Phonenumber'|trans }}&nbsp;:</dt>
<dd>{% if person.phonenumber is not empty %}<a href="tel:{{ person.phonenumber }}"><pre>{{ person.phonenumber|chill_format_phonenumber }}</pre></a>{% else %}<span class="chill-no-data-statement">{{ 'No data given'|trans }}{% endif %}</dd>
<dd>{% if person.phonenumber is not empty %}<a href="tel:{{ person.phonenumber|phone_number_format('E164') }}">{{ person.phonenumber|chill_format_phonenumber }}</a>{% else %}<span class="chill-no-data-statement">{{ 'No data given'|trans }}{% endif %}</dd>
</dl>
{% endif %}
{%- if chill_person.fields.mobilenumber == 'visible' -%}
<dl>
<dt>{{ 'Mobilenumber'|trans }}&nbsp;:</dt>
<dd>{% if person.mobilenumber is not empty %}<a href="tel:{{ person.mobilenumber }}">{{ person.mobilenumber|chill_format_phonenumber }}</a>{% else %}<span class="chill-no-data-statement">{{ 'No data given'|trans }}{% endif %}</dd>
<dd>{% if person.mobilenumber is not empty %}<a href="tel:{{ person.mobilenumber|phone_number_format('E164') }}">{{ person.mobilenumber|chill_format_phonenumber }}</a>{% else %}<span class="chill-no-data-statement">{{ 'No data given'|trans }}{% endif %}</dd>
<p>{% if person.acceptSMS %}{{ 'Accept short text message'|trans }}{% endif %}</p>
</dl>
{% endif %}
@@ -250,7 +250,7 @@ This view should receive those arguments:
<dt>{{ 'Others phone numbers'|trans }}&nbsp;:</dt>
{% for el in person.otherPhoneNumbers %}
{% if el.phonenumber is not empty %}
<dd>{% if el.description is not empty %}{{ el.description }}&nbsp;:&nbsp;{% endif %}<a href="tel:{{ el.phonenumber }}">{{ el.phonenumber|chill_format_phonenumber }}</a></dd>
<dd>{% if el.description is not empty %}{{ el.description }}&nbsp;:&nbsp;{% endif %}<a href="tel:{{ el.phonenumber|phone_number_format('E164') }}">{{ el.phonenumber|chill_format_phonenumber }}</a></dd>
{% endif %}
{% endfor %}
</ul>
@@ -317,4 +317,4 @@ This view should receive those arguments:
{% endif %}
</div>
{% endblock %}
{% endblock %}

View File

@@ -62,7 +62,7 @@
<li>
<i class="fa fa-li fa-home"></i>
<span class="item-key">{{ "Address of"|trans}}</span>
<span class="chill-entity entity-person badge-person">{{ a.hostThirdParty|chill_entity_render_box }}</span>
<span class="chill-entity entity-person badge-thirdparty">{{ a.hostThirdParty|chill_entity_render_box }}</span>
</li>
<li>
{% if a.hostThirdParty.address is not null %}

View File

@@ -33,6 +33,7 @@ class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleH
self::DELETE,
self::FULL,
self::TOGGLE_CONFIDENTIAL_ALL,
self::TOGGLE_INTENSITY,
];
public const CREATE = 'CHILL_PERSON_ACCOMPANYING_PERIOD_CREATE';
@@ -62,6 +63,11 @@ class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleH
*/
public const TOGGLE_CONFIDENTIAL_ALL = 'CHILL_PERSON_ACCOMPANYING_PERIOD_TOGGLE_CONFIDENTIAL_ALL';
/**
* Right to toggle urgency of parcours.
*/
public const TOGGLE_INTENSITY = 'CHILL_PERSON_ACCOMPANYING_PERIOD_TOGGLE_INTENSITY';
private Security $security;
private VoterHelperInterface $voterHelper;
@@ -125,11 +131,20 @@ class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleH
}
if (self::TOGGLE_CONFIDENTIAL === $attribute) {
if ($subject->getUser() === $token->getUser()) {
if (null !== $subject->getUser() && ($subject->getUser() === $token->getUser())) {
return true;
}
return $this->voterHelper->voteOnAttribute(self::TOGGLE_CONFIDENTIAL_ALL, $subject, $token);
return false;
// return $this->voterHelper->voteOnAttribute(self::TOGGLE_CONFIDENTIAL_ALL, $subject, $token);
}
if (self::TOGGLE_INTENSITY === $attribute) {
if (null !== $subject->getUser() && ($subject->getUser() === $token->getUser())) {
return true;
}
return false;
}
// if confidential, only the referent can see it

View File

@@ -95,9 +95,9 @@ class PersonDocGenNormalizer implements
'maritalStatus' => null !== ($ms = $person->getMaritalStatus()) ? $this->translatableStringHelper->localize($ms->getName()) : '',
'maritalStatusDate' => $this->normalizer->normalize($person->getMaritalStatusDate(), $format, $dateContext),
'email' => $person->getEmail(),
'firstPhoneNumber' => $person->getPhonenumber() ?? $person->getMobilenumber(),
'fixPhoneNumber' => $person->getPhonenumber(),
'mobilePhoneNumber' => $person->getMobilenumber(),
'firstPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber() ?? $person->getMobilenumber(), $format, $context),
'fixPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber(), $format, $context),
'mobilePhoneNumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $context),
'nationality' => null !== ($c = $person->getNationality()) ? $this->translatableStringHelper->localize($c->getName()) : '',
'placeOfBirth' => $person->getPlaceOfBirth(),
'memo' => $person->getMemo(),

View File

@@ -12,21 +12,27 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\PersonAltName;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Repository\ResidentialAddressRepository;
use DateTime;
use DateTimeImmutable;
use Doctrine\Common\Collections\Collection;
use libphonenumber\PhoneNumber;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;
use function array_key_exists;
use function count;
use function in_array;
/**
* Serialize a Person entity.
@@ -41,18 +47,26 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
private CenterResolverManagerInterface $centerResolverManager;
private PhoneNumberHelperInterface $phoneNumberHelper;
private ChillEntityRenderExtension $render;
private PersonRepository $repository;
private ResidentialAddressRepository $residentialAddressRepository;
public function __construct(
ChillEntityRenderExtension $render,
PersonRepository $repository,
CenterResolverManagerInterface $centerResolverManager
CenterResolverManagerInterface $centerResolverManager,
ResidentialAddressRepository $residentialAddressRepository,
PhoneNumberHelperInterface $phoneNumberHelper
) {
$this->render = $render;
$this->repository = $repository;
$this->centerResolverManager = $centerResolverManager;
$this->residentialAddressRepository = $residentialAddressRepository;
$this->phoneNumberHelper = $phoneNumberHelper;
}
public function denormalize($data, $type, $format = null, array $context = [])
@@ -106,12 +120,12 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
break;
case 'phonenumber':
$person->setPhonenumber($data[$item]);
$person->setPhonenumber($this->denormalizer->denormalize($data[$item], PhoneNumber::class, $format, $context));
break;
case 'mobilenumber':
$person->setMobilenumber($data[$item]);
$person->setMobilenumber($this->denormalizer->denormalize($data[$item], PhoneNumber::class, $format, $context));
break;
@@ -174,27 +188,42 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
*/
public function normalize($person, $format = null, array $context = [])
{
$groups = $context[AbstractNormalizer::GROUPS] ?? [];
if (is_string($groups)) {
$groups = [$groups];
}
$household = $person->getCurrentHousehold();
$currentResidentialAddresses = $this->residentialAddressRepository->findCurrentResidentialAddressByPerson($person);
return [
$data = [
'type' => 'person',
'id' => $person->getId(),
'text' => $this->render->renderString($person, ['addAge' => false]),
'textAge' => $this->render->renderString($person, ['addAge' => true]),
'firstName' => $person->getFirstName(),
'lastName' => $person->getLastName(),
'current_household_address' => $this->normalizer->normalize($person->getCurrentHouseholdAddress(), $format, $context),
'birthdate' => $this->normalizer->normalize($person->getBirthdate(), $format, $context),
'deathdate' => $this->normalizer->normalize($person->getDeathdate(), $format, $context),
'age' => $this->normalizer->normalize($person->getAge(), $format, $context),
'centers' => $this->normalizer->normalize($this->centerResolverManager->resolveCenters($person), $format, $context),
'phonenumber' => $person->getPhonenumber(),
'mobilenumber' => $person->getMobilenumber(),
'phonenumber' => $this->normalizer->normalize($person->getPhonenumber()),
'mobilenumber' => $this->normalizer->normalize($person->getMobilenumber()),
'email' => $person->getEmail(),
'altNames' => $this->normalizeAltNames($person->getAltNames()),
'gender' => $person->getGender(),
'current_household_address' => $this->normalizer->normalize($person->getCurrentHouseholdAddress(), $format, $context),
'current_household_id' => $household ? $this->normalizer->normalize($household->getId(), $format, $context) : null,
];
if (in_array('minimal', $groups, true) && 1 === count($groups)) {
return $data;
}
return array_merge($data, [
'centers' => $this->normalizer->normalize($this->centerResolverManager->resolveCenters($person), $format, $context),
'altNames' => $this->normalizeAltNames($person->getAltNames()),
'current_household_id' => $household ? $this->normalizer->normalize($household->getId(), $format, $context) : null,
'current_residential_addresses' => $currentResidentialAddresses ?
$this->normalizer->normalize($currentResidentialAddresses, $format, $context) :
null,
]);
}
public function supportsDenormalization($data, $type, $format = null)

View File

@@ -13,9 +13,7 @@ namespace Chill\PersonBundle\Tests\AccompanyingPeriod;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request;
/**
* @internal
@@ -42,7 +40,7 @@ final class AccompanyingPeriodConfidentialTest extends WebTestCase
]);
}
public function dataGenerateRandomAccompanyingCourse()
public function testConfidentialInvalid()
{
// Disabling this dataprovider to avoid having errors while running the test.
return yield from [];
@@ -88,10 +86,7 @@ final class AccompanyingPeriodConfidentialTest extends WebTestCase
}
}
/**
* @dataProvider dataGenerateRandomAccompanyingCourse
*/
public function testRemoveUserWhenConfidential(int $periodId)
public function testConfidentialValid()
{
$this->markTestIncomplete(
'Marked as incomplete because of a problem in the dataprovider, at line 81.'
@@ -101,8 +96,7 @@ final class AccompanyingPeriodConfidentialTest extends WebTestCase
->find($periodId);
$em = self::$kernel->getContainer()->get('doctrine.orm.entity_manager');
$isConfidential = $period->isConfidential();
$step = $period->getStep();
$violations = self::$validator->validate($period, null, ['confirmed']);
$initialUser = $period->getUser();

View File

@@ -297,13 +297,14 @@ final class PersonControllerUpdateTest extends WebTestCase
// reminder: this value is capitalized
['placeOfBirth', 'A PLACE', static function (Person $person) { return $person->getPlaceOfBirth(); }],
['birthdate', '1980-12-15', static function (Person $person) { return $person->getBirthdate()->format('Y-m-d'); }],
['phonenumber', '+32123456789', static function (Person $person) { return $person->getPhonenumber(); }],
// TODO test on phonenumber update
// ['phonenumber', '+32123456789', static function (Person $person) { return $person->getPhonenumber(); }],
['memo', 'jfkdlmq jkfldmsq jkmfdsq', static function (Person $person) { return $person->getMemo(); }],
['countryOfBirth', 'BE', static function (Person $person) { return $person->getCountryOfBirth()->getCountryCode(); }],
['nationality', 'FR', static function (Person $person) { return $person->getNationality()->getCountryCode(); }],
['placeOfBirth', '', static function (Person $person) { return $person->getPlaceOfBirth(); }],
['birthdate', '', static function (Person $person) { return $person->getBirthdate(); }],
['phonenumber', '', static function (Person $person) { return $person->getPhonenumber(); }],
//['phonenumber', '', static function (Person $person) { return $person->getPhonenumber(); }],
['memo', '', static function (Person $person) { return $person->getMemo(); }],
['countryOfBirth', null, static function (Person $person) { return $person->getCountryOfBirth(); }],
['nationality', null, static function (Person $person) { return $person->getNationality(); }],

View File

@@ -158,7 +158,7 @@ final class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase
$participationL = $period->closeParticipationFor($person);
$this->assertSame($participationL, $participation);
$this->assertTrue($participation->getEndDate() instanceof DateTimeInterface);
$this->assertTrue($participationL->getEndDate() instanceof DateTimeInterface);
$participation = $period->getOpenParticipationContainsPerson($person);
$this->assertNull($participation);

View File

@@ -15,6 +15,7 @@ use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
@@ -28,10 +29,13 @@ class AccompanyingPeriodValidityValidator extends ConstraintValidator
private SocialIssueRender $socialIssueRender;
public function __construct(ActivityRepository $activityRepository, SocialIssueRender $socialIssueRender)
private TokenStorageInterface $token;
public function __construct(ActivityRepository $activityRepository, SocialIssueRender $socialIssueRender, TokenStorageInterface $token)
{
$this->activityRepository = $activityRepository;
$this->socialIssueRender = $socialIssueRender;
$this->token = $token;
}
public function validate($period, Constraint $constraint)
@@ -44,6 +48,7 @@ class AccompanyingPeriodValidityValidator extends ConstraintValidator
throw new UnexpectedValueException($period, AccompanyingPeriod::class);
}
/** Check if a social issue can be deleted (is not linked to an action or activity within the parcours) */
$socialIssues = [];
$activities = $this->activityRepository->findBy(['accompanyingPeriod' => $period]);

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\AccompanyingPeriod;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class ConfidentialCourseMustHaveReferrer extends Constraint
{
public string $message = 'A confidential parcours must have a referrer';
public function getTargets()
{
return [self::CLASS_CONSTRAINT];
}
}

View File

@@ -0,0 +1,38 @@
<?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\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class ConfidentialCourseMustHaveReferrerValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (!$value instanceof AccompanyingPeriod) {
throw new UnexpectedTypeException($value, AccompanyingPeriod::class);
}
if (!$constraint instanceof ConfidentialCourseMustHaveReferrer) {
throw new UnexpectedTypeException($constraint, ConfidentialCourseMustHaveReferrer::class);
}
if ($value->isConfidential() && null === $value->getUser()) {
$this->context
->buildViolation($constraint->message)
->atPath('user')
->addViolation();
}
}
}

View File

@@ -1177,6 +1177,44 @@ paths:
422:
description: "object with validation errors"
/1.0/person/accompanying-course/{id}/intensity.json:
post:
tags:
- person
summary: "Toggle intensity status of accompanying course"
parameters:
- name: id
in: path
required: true
description: The accompanying period's id
schema:
type: integer
format: integer
minimum: 1
requestBody:
description: "Intensity toggle"
required: true
content:
application/json:
schema:
type: object
properties:
type:
type: string
enum:
- "accompanying_period"
intensity:
type: string
responses:
401:
description: "Unauthorized"
404:
description: "Not found"
200:
description: "OK"
422:
description: "object with validation errors"
/1.0/person/accompanying-course/by-person/{person_id}.json:
get:
tags:

View File

@@ -1,15 +1,15 @@
services:
Chill\PersonBundle\Form\:
autowire: true
autoconfigure: true
resource: '../../Form/'
Chill\PersonBundle\Form\PersonType:
autowire: true
autoconfigure: true
arguments:
$personFieldsConfiguration: '%chill_person.person_fields%'
$configAltNamesHelper: '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper'
$translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper'
tags:
- { name: form.type, alias: '@chill.person.form.person_creation' }

View File

@@ -19,8 +19,5 @@ Chill\PersonBundle\Entity\AccompanyingPeriod:
Chill\PersonBundle\Entity\PersonPhone:
properties:
phonenumber:
- Regex:
pattern: '/^([\+{1}])([0-9\s*]{4,20})$/'
message: 'Invalid phone number: it should begin with the international prefix starting with "+", hold only digits and be smaller than 20 characters. Ex: +33123456789'
- Chill\MainBundle\Validation\Constraint\PhonenumberConstraint:
type: any

View File

@@ -0,0 +1,86 @@
<?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;
use Exception;
use libphonenumber\PhoneNumberUtil;
use RuntimeException;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
final class Version20220215135509 extends AbstractMigration implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function down(Schema $schema): void
{
throw new Exception('You should not do that.');
}
public function getDescription(): string
{
return 'Update phone numbers for person';
}
public function up(Schema $schema): void
{
$carrier_code = $this->container
->getParameter('chill_main')['phone_helper']['default_carrier_code'];
if (null === $carrier_code) {
throw new RuntimeException('no carrier code');
}
$this->addSql('ALTER TABLE chill_person_person ALTER phonenumber TYPE TEXT');
$this->addSql('ALTER TABLE chill_person_person ALTER phonenumber DROP DEFAULT');
$this->addSql('ALTER TABLE chill_person_person ALTER phonenumber DROP NOT NULL');
$this->addSql('COMMENT ON COLUMN chill_person_person.phonenumber IS NULL');
$this->addSql('ALTER TABLE chill_person_person ALTER mobilenumber TYPE TEXT');
$this->addSql('ALTER TABLE chill_person_person ALTER mobilenumber DROP DEFAULT');
$this->addSql('ALTER TABLE chill_person_person ALTER mobilenumber DROP NOT NULL');
$this->addSql('COMMENT ON COLUMN chill_person_person.mobilenumber IS NULL');
$this->addSql(
'UPDATE chill_person_person SET ' .
$this->buildMigrationPhonenumberClause($carrier_code, 'phonenumber') .
', ' .
$this->buildMigrationPhoneNumberClause($carrier_code, 'mobilenumber')
);
$this->addSql('ALTER TABLE chill_person_phone ALTER phonenumber TYPE TEXT');
$this->addSql('ALTER TABLE chill_person_phone ALTER phonenumber DROP DEFAULT');
$this->addSql('ALTER TABLE chill_person_phone ALTER phonenumber DROP NOT NULL');
$this->addSql('COMMENT ON COLUMN chill_person_phone.phonenumber IS NULL');
$this->addSql(
'UPDATE chill_person_phone SET ' .
$this->buildMigrationPhoneNumberClause($carrier_code, 'phonenumber')
);
}
private function buildMigrationPhoneNumberClause(string $defaultCarriercode, string $field): string
{
$util = PhoneNumberUtil::getInstance();
$countryCode = $util->getCountryCodeForRegion($defaultCarriercode);
return sprintf('%s=CASE
WHEN %s = \'\' THEN NULL
WHEN LEFT(%s, 1) = \'0\'
THEN \'+%s\' || replace(replace(substr(%s, 2), \'(0)\', \'\'), \' \', \'\')
ELSE replace(replace(%s, \'(0)\', \'\'),\' \', \'\')
END', $field, $field, $field, $countryCode, $field, $field);
}
}

View File

@@ -20,6 +20,7 @@ Two addresses has the same validFrom date: La date de validité est identique à
The firstname cannot be empty: Le prénom ne peut pas être vide
The lastname cannot be empty: Le nom de famille ne peut pas être vide
The gender must be set: Le genre doit être renseigné
You are not allowed to perform this action: Vous n'avez pas le droit de changer cette valeur.
#export list
You must select at least one element: Vous devez sélectionner au moins un élément
@@ -51,6 +52,8 @@ household_membership:
A course must contains at least one social issue: 'Un parcours doit être associé à au moins une problématique sociale'
A course must be associated to at least one scope: 'Un parcours doit être associé à au moins un service'
The social %name% issue cannot be deleted because it is associated with an activity or an action: 'La problématique sociale "%name%" ne peut pas être supprimée car elle est associée à une activité ou une action'
A confidential parcours must have a referrer: 'Un parcours confidentiel doit avoir un référent'
Only the referrer can change the confidentiality of a parcours: 'Seul le référent peut modifier la confidentialité'
# resource
You must associate at least one entity: Associez un usager, un tiers ou indiquez une description libre