Merge branch 'issue557_address_civility_in_form_person' into 'master'

Issue557 address and civility in form person

See merge request Chill-Projet/chill-bundles!410
This commit is contained in:
Julien Fastré 2022-05-06 11:01:20 +00:00
commit f55c06d5c5
24 changed files with 433 additions and 113 deletions

View File

@ -11,15 +11,20 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [person] add civility when creating a person (with the on-the-fly component or in the php form) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/557)
* [person] add address when creating a person (with the on-the-fly component or in the php form) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/557)
* [person] add household creation API point (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/557)
## Test releases
### 2021-04-29
* [person] prevent circular references in PersonDocGenNormalizer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/527) * [person] prevent circular references in PersonDocGenNormalizer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/527)
* [person] add maritalStatusComment to PersonDocGenNormalizer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/582) * [person] add maritalStatusComment to PersonDocGenNormalizer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/582)
* Load relationships without gender in french fixtures * Load relationships without gender in french fixtures
* Add command to remove old draft accompanying periods * Add command to remove old draft accompanying periods
## Test releases
### 2021-04-28 ### 2021-04-28
* [address] fix bug when editing address: update location and addressreferenceId + better update of the map in edition (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/593) * [address] fix bug when editing address: update location and addressreferenceId + better update of the map in edition (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/593)

View File

@ -210,10 +210,10 @@ export default {
let let
type = this.type, type = this.type,
data = {} ; data = {} ;
switch (type) { switch (type) {
case 'person': case 'person':
data = this.$refs.castPerson.$data.person; data = this.$refs.castPerson.$data.person;
console.log('person data are', data);
break; break;
case 'thirdparty': case 'thirdparty':
@ -238,7 +238,7 @@ export default {
if (typeof data.civility !== 'undefined' && null !== data.civility) { if (typeof data.civility !== 'undefined' && null !== data.civility) {
data.civility = data.civility !== null ? {type: 'chill_main_civility', id: data.civility.id} : null; data.civility = data.civility !== null ? {type: 'chill_main_civility', id: data.civility.id} : null;
} }
if (typeof data.civility !== 'undefined' && null !== data.profession) { if (typeof data.profession !== 'undefined' && null !== data.profession) {
data.profession = data.profession !== null ? {type: 'third_party_profession', id: data.profession.id} : null; data.profession = data.profession !== null ? {type: 'third_party_profession', id: data.profession.id} : null;
} }
// console.log('onthefly data', data); // console.log('onthefly data', data);

View File

@ -36,33 +36,35 @@
{# Flash messages ! #} {# Flash messages ! #}
{% if app.session.flashbag.keys()|length > 0 %} {% if app.session.flashbag.keys()|length > 0 %}
<div class="col-8 mb-5 flash_message"> <div class="row justify-content-center">
<div class="col-10 mb-5 flash_message">
{% for flashMessage in app.session.flashbag.get('success') %}
<div class="alert alert-success flash_message">
<span>{{ flashMessage|raw }}</span>
</div>
{% endfor %}
{% for flashMessage in app.session.flashbag.get('error') %}
<div class="alert alert-danger flash_message">
<span>{{ flashMessage|raw }}</span>
</div>
{% endfor %}
{% for flashMessage in app.session.flashbag.get('notice') %}
<div class="alert alert-warning flash_message">
<span>{{ flashMessage|raw }}</span>
</div>
{% endfor %}
{% for flashMessage in app.session.flashbag.get('success') %}
<div class="alert alert-success flash_message">
<span>{{ flashMessage|raw }}</span>
</div> </div>
{% endfor %} </div>
{% for flashMessage in app.session.flashbag.get('error') %}
<div class="alert alert-danger flash_message">
<span>{{ flashMessage|raw }}</span>
</div>
{% endfor %}
{% for flashMessage in app.session.flashbag.get('notice') %}
<div class="alert alert-warning flash_message">
<span>{{ flashMessage|raw }}</span>
</div>
{% endfor %}
</div>
{% endif %} {% endif %}
{% block content %} {% block content %}
<div class="col-8 main_search"> <div class="col-8 main_search">
<h2>{{ 'Search'|trans }}</h2> <h2>{{ 'Search'|trans }}</h2>
<form action="{{ path('chill_main_search') }}" method="get"> <form action="{{ path('chill_main_search') }}" method="get">
<input class="form-control form-control-lg" name="q" type="search" placeholder="{{ 'Search persons, ...'|trans }}" /> <input class="form-control form-control-lg" name="q" type="search" placeholder="{{ 'Search persons, ...'|trans }}" />
<center> <center>
@ -75,11 +77,11 @@
</center> </center>
</form> </form>
</div> </div>
{# DISABLED {{ chill_widget('homepage', {} ) }} #} {# DISABLED {{ chill_widget('homepage', {} ) }} #}
{% include '@ChillMain/Homepage/index.html.twig' %} {% include '@ChillMain/Homepage/index.html.twig' %}
{% endblock %} {% endblock %}
</div> </div>

View File

@ -12,12 +12,15 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller; namespace Chill\PersonBundle\Controller;
use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\CreationPersonType; use Chill\PersonBundle\Form\CreationPersonType;
use Chill\PersonBundle\Form\PersonType; use Chill\PersonBundle\Form\PersonType;
use Chill\PersonBundle\Privacy\PrivacyEvent; use Chill\PersonBundle\Privacy\PrivacyEvent;
use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Search\SimilarPersonMatcher; use Chill\PersonBundle\Search\SimilarPersonMatcher;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
@ -31,8 +34,8 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function count; use function count;
use function hash; use function hash;
use function implode; use function implode;
@ -248,6 +251,31 @@ final class PersonController extends AbstractController
$this->em->flush(); $this->em->flush();
$this->lastPostDataReset(); $this->lastPostDataReset();
$address = $form->get('address')->getData();
$addressForm = (bool) $form->get('addressForm')->getData();
if (null !== $address && $addressForm) {
$household = new Household();
$member = new HouseholdMember();
$member->setPerson($person);
$member->setStartDate(new DateTimeImmutable());
$household->addMember($member);
$household->setForceAddress($address);
$this->em->persist($member);
$this->em->persist($household);
$this->em->flush();
if ($form->get('createHousehold')->isClicked()) {
return $this->redirectToRoute('chill_person_household_members_editor', [
'persons' => [$person->getId()],
'household' => $household->getId(),
]);
}
}
if ($form->get('createPeriod')->isClicked()) { if ($form->get('createPeriod')->isClicked()) {
return $this->redirectToRoute('chill_person_accompanying_course_new', [ return $this->redirectToRoute('chill_person_accompanying_course_new', [
'person_id' => [$person->getId()], 'person_id' => [$person->getId()],

View File

@ -555,6 +555,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
'methods' => [ 'methods' => [
Request::METHOD_GET => true, Request::METHOD_GET => true,
Request::METHOD_HEAD => true, Request::METHOD_HEAD => true,
Request::METHOD_POST => true,
], ],
], ],
'suggestHouseholdByAccompanyingPeriodParticipation' => [ 'suggestHouseholdByAccompanyingPeriodParticipation' => [

View File

@ -35,7 +35,7 @@ class HouseholdMember
/** /**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) * @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"read", "docgen:read"})
* @Assert\GreaterThan( * @Assert\GreaterThanOrEqual(
* propertyPath="startDate", * propertyPath="startDate",
* message="household_membership.The end date must be after start date", * message="household_membership.The end date must be after start date",
* groups={"household_memberships"} * groups={"household_memberships"}
@ -82,14 +82,13 @@ class HouseholdMember
/** /**
* @ORM\ManyToOne(targetEntity=Position::class) * @ORM\ManyToOne(targetEntity=Position::class)
* @Serializer\Groups({"read", "docgen:read"}) * @Serializer\Groups({"read", "docgen:read"})
* @Assert\NotNull(groups={"household_memberships_created"})
*/ */
private ?Position $position = null; private ?Position $position = null;
/** /**
* @ORM\Column(type="boolean", name="sharedhousehold") * @ORM\Column(type="boolean", name="sharedhousehold")
*/ */
private bool $shareHousehold = false; private bool $shareHousehold = true;
/** /**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) * @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
@ -201,15 +200,18 @@ class HouseholdMember
return $this; return $this;
} }
public function setPosition(Position $position): self public function setPosition(?Position $position): self
{ {
if ($this->position instanceof Position) { if ($this->position instanceof Position && $this->position !== $position) {
throw new LogicException('The position is already set. You cannot change ' . throw new LogicException('The position is already set. You cannot change ' .
'a position of a membership'); 'a position of a membership');
} }
$this->position = $position; $this->position = $position;
$this->shareHousehold = $position->getShareHousehold();
if (null !== $position) {
$this->shareHousehold = $position->getShareHousehold();
}
return $this; return $this;
} }

View File

@ -11,10 +11,13 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Form; namespace Chill\PersonBundle\Form;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Form\Event\CustomizeFormEvent; use Chill\MainBundle\Form\Event\CustomizeFormEvent;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\ChillPhoneNumberType; use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
use Chill\MainBundle\Form\Type\PickAddressType;
use Chill\MainBundle\Form\Type\PickCenterType; use Chill\MainBundle\Form\Type\PickCenterType;
use Chill\MainBundle\Form\Type\PickCivilityType;
use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\Type\GenderType; use Chill\PersonBundle\Form\Type\GenderType;
@ -24,9 +27,12 @@ use libphonenumber\PhoneNumberType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
final class CreationPersonType extends AbstractType final class CreationPersonType extends AbstractType
{ {
@ -55,6 +61,11 @@ final class CreationPersonType extends AbstractType
$builder $builder
->add('firstName') ->add('firstName')
->add('lastName') ->add('lastName')
->add('civility', PickCivilityType::class, [
'required' => false,
'label' => 'Civility',
'placeholder' => 'choose civility',
])
->add('gender', GenderType::class, [ ->add('gender', GenderType::class, [
'required' => true, 'placeholder' => null, 'required' => true, 'placeholder' => null,
]) ])
@ -71,6 +82,17 @@ final class CreationPersonType extends AbstractType
]) ])
->add('email', EmailType::class, [ ->add('email', EmailType::class, [
'required' => false, 'required' => false,
])
->add('addressForm', CheckboxType::class, [
'label' => 'Create a household and add an address',
'required' => false,
'mapped' => false,
'help' => 'A new household will be created. The person will be member of this household.',
])
->add('address', PickAddressType::class, [
'required' => false,
'mapped' => false,
'label' => false,
]); ]);
if ($this->askCenters) { if ($this->askCenters) {
@ -97,6 +119,9 @@ final class CreationPersonType extends AbstractType
{ {
$resolver->setDefaults([ $resolver->setDefaults([
'data_class' => Person::class, 'data_class' => Person::class,
'constraints' => [
new Callback([$this, 'validateCheckedAddress']),
],
]); ]);
} }
@ -107,4 +132,18 @@ final class CreationPersonType extends AbstractType
{ {
return self::NAME; return self::NAME;
} }
public function validateCheckedAddress($data, ExecutionContextInterface $context, $payload): void
{
/** @var bool $addressFrom */
$addressFrom = $context->getObject()->get('addressForm')->getData();
/** @var ?Address $address */
$address = $context->getObject()->get('address')->getData();
if ($addressFrom && null === $address) {
$context->buildViolation('person_creation.If you want to create an household, an address is required')
->atPath('addressForm')
->addViolation();
}
}
} }

View File

@ -26,12 +26,15 @@ class HouseholdMemberType extends AbstractType
'input' => 'datetime_immutable', 'input' => 'datetime_immutable',
]); ]);
if (!$options['data']->getPosition()->getShareHousehold()) { if (null !== $options['data']->getPosition()) {
$builder->add('endDate', ChillDateType::class, [ if (!$options['data']->getPosition()->getShareHousehold()) {
'label' => 'household.End date', $builder->add('endDate', ChillDateType::class, [
'input' => 'datetime_immutable', 'label' => 'household.End date',
]); 'input' => 'datetime_immutable',
]);
}
} }
$builder $builder
->add('comment', ChillTextareaType::class, [ ->add('comment', ChillTextareaType::class, [
'label' => 'household.Comment', 'label' => 'household.Comment',

View File

@ -55,7 +55,7 @@ class MembersEditor
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
} }
public function addMovement(DateTimeImmutable $date, Person $person, Position $position, ?bool $holder = false, ?string $comment = null): self public function addMovement(DateTimeImmutable $date, Person $person, ?Position $position, ?bool $holder = false, ?string $comment = null): self
{ {
if (null === $this->household) { if (null === $this->household) {
throw new LogicException('You must define a household first'); throw new LogicException('You must define a household first');
@ -69,68 +69,70 @@ class MembersEditor
->setComment($comment); ->setComment($comment);
$this->household->addMember($membership); $this->household->addMember($membership);
if ($position->getShareHousehold()) { if (null !== $position) {
// launch event only if moving to a "share household" position, if ($position->getShareHousehold()) {
// and if the destination household is different than the previous one // launch event only if moving to a "share household" position,
$event = new PersonAddressMoveEvent($person); // and if the destination household is different than the previous one
$event->setNextMembership($membership); $event = new PersonAddressMoveEvent($person);
$event->setNextMembership($membership);
$counter = 0; $counter = 0;
foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) { foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) {
if ($participation === $membership) { if ($participation === $membership) {
continue; continue;
} }
if ($participation->getStartDate() > $membership->getStartDate()) { if ($participation->getStartDate() > $membership->getStartDate()) {
continue; continue;
} }
++$counter; ++$counter;
if ($participation->getEndDate() === null || $participation->getEndDate() > $date) { if ($participation->getEndDate() === null || $participation->getEndDate() > $date) {
$participation->setEndDate($date); $participation->setEndDate($date);
$this->membershipsAffected[] = $participation; $this->membershipsAffected[] = $participation;
$this->oldMembershipsHashes[] = spl_object_hash($participation); $this->oldMembershipsHashes[] = spl_object_hash($participation);
if ($participation->getHousehold() !== $this->household) { if ($participation->getHousehold() !== $this->household) {
$event->setPreviousMembership($participation); $event->setPreviousMembership($participation);
$this->events[] = $event; $this->events[] = $event;
}
} }
} }
}
// send also the event if there was no participation before // send also the event if there was no participation before
if (0 === $counter) { if (0 === $counter) {
$this->events[] = $event; $this->events[] = $event;
}
foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) {
if ($participation->getHousehold() === $this->household
&& $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()
&& $participation->getStartDate() <= $membership->getStartDate()
) {
$participation->setEndDate($membership->getStartDate());
}
}
} else {
// if a members is moved to the same household than the one he belongs to,
// we should make it leave the household
if ($person->getCurrentHousehold($date) === $this->household) {
$this->leaveMovement($date, $person);
}
// if there are multiple belongings not sharing household, close the others
foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) {
if ($participation === $membership) {
continue;
} }
if ($participation->getHousehold() === $this->household foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) {
&& ($participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()) if ($participation->getHousehold() === $this->household
&& $participation->getStartDate() <= $membership->getStartDate() && $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()
) { && $participation->getStartDate() <= $membership->getStartDate()
$participation->setEndDate($membership->getStartDate()); ) {
$participation->setEndDate($membership->getStartDate());
}
}
} else {
// if a members is moved to the same household than the one he belongs to,
// we should make it leave the household
if ($person->getCurrentHousehold($date) === $this->household) {
$this->leaveMovement($date, $person);
}
// if there are multiple belongings not sharing household, close the others
foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) {
if ($participation === $membership) {
continue;
}
if ($participation->getHousehold() === $this->household
&& ($participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate())
&& $participation->getStartDate() <= $membership->getStartDate()
) {
$participation->setEndDate($membership->getStartDate());
}
} }
} }
} }

View File

@ -0,0 +1,18 @@
import { ShowHide } from 'ShowHide';
const addressForm = document.getElementById("addressForm");
const address = document.getElementById("address");
new ShowHide({
froms: [addressForm],
container: [address],
test: function(froms) {
for (let f of froms.values()) {
for (let input of f.querySelectorAll('input').values()) {
return input.checked;
}
}
return false;
},
event_name: 'change'
});

View File

@ -123,6 +123,7 @@ export default {
body.email = payload.data.email; body.email = payload.data.email;
body.altNames = payload.data.altNames; body.altNames = payload.data.altNames;
body.gender = payload.data.gender; body.gender = payload.data.gender;
body.civility = payload.data.civility;
makeFetch('PATCH', `/api/1.0/person/person/${payload.data.id}.json`, body) makeFetch('PATCH', `/api/1.0/person/person/${payload.data.id}.json`, body)
.then(response => { .then(response => {

View File

@ -150,6 +150,7 @@ export default {
body.email = payload.data.email; body.email = payload.data.email;
body.altNames = payload.data.altNames; body.altNames = payload.data.altNames;
body.gender = payload.data.gender; body.gender = payload.data.gender;
body.civility = payload.data.civility;
makeFetch('PATCH', `/api/1.0/person/person/${payload.data.id}.json`, body) makeFetch('PATCH', `/api/1.0/person/person/${payload.data.id}.json`, body)
.then(response => { .then(response => {

View File

@ -14,8 +14,13 @@ const getPersonAltNames = () =>
fetch('/api/1.0/person/config/alt_names.json').then(response => { fetch('/api/1.0/person/config/alt_names.json').then(response => {
if (response.ok) { return response.json(); } if (response.ok) { return response.json(); }
throw Error('Error with request resource response'); throw Error('Error with request resource response');
});; });
const getCivilities = () =>
fetch('/api/1.0/main/civility.json').then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
/* /*
* POST a new person * POST a new person
@ -56,6 +61,7 @@ const patchPerson = (id, body) => {
export { export {
getPerson, getPerson,
getPersonAltNames, getPersonAltNames,
getCivilities,
postPerson, postPerson,
patchPerson patchPerson
}; };

View File

@ -277,12 +277,79 @@ export default {
} }
}, },
saveFormOnTheFly({ type, data }) { saveFormOnTheFly({ type, data }) {
// console.log('saveFormOnTheFly from addPersons, type', type, ', data', data); console.log('saveFormOnTheFly from addPersons, type', type, ', data', data);
if (type === 'person') { if (type === 'person') {
makeFetch('POST', '/api/1.0/person/person.json', data) makeFetch('POST', '/api/1.0/person/person.json', data)
.then(response => { .then(responsePerson => {
this.newPriorSuggestion(response);
this.newPriorSuggestion(responsePerson);
this.$refs.onTheFly.closeModal(); this.$refs.onTheFly.closeModal();
if (null !== data.addressId) {
const household = {
'type': 'household'
};
const address = {
'id': data.addressId
};
makeFetch('POST', '/api/1.0/person/household.json', household)
.then(responseHousehold => {
const member = {
'concerned': [
{
'person': {
'type': 'person',
'id': responsePerson.id
},
'start_date': {
// TODO: use date.js methods (low priority)
'datetime': `${new Date().toISOString().split('T')[0]}T00:00:00+02:00`
},
'holder': false,
'comment': null
}
],
'destination': {
'type': 'household',
'id': responseHousehold.id
},
'composition': null
};
return makeFetch('POST', '/api/1.0/person/household/members/move.json', member)
.then(_response => {
makeFetch('POST', `/api/1.0/person/household/${responseHousehold.id}/address.json`, address)
.then(_response => {})
.catch((error) => {
if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.$toast.open({message: v });
}
} else {
this.$toast.open({message: 'An error occurred'});
}
});
})
.catch((error) => {
if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.$toast.open({message: v });
}
} else {
this.$toast.open({message: 'An error occurred'});
}
});
})
.catch((error) => {
if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.$toast.open({message: v });
}
} else {
this.$toast.open({message: 'An error occurred'});
}
});
}
}) })
.catch((error) => { .catch((error) => {
if (error.name === 'ValidationException') { if (error.name === 'ValidationException') {
@ -292,7 +359,8 @@ export default {
} else { } else {
this.$toast.open({message: 'An error occurred'}); this.$toast.open({message: 'An error occurred'});
} }
}) });
} }
else if (type === 'thirdparty') { else if (type === 'thirdparty') {
makeFetch('POST', '/api/1.0/thirdparty/thirdparty.json', data) makeFetch('POST', '/api/1.0/thirdparty/thirdparty.json', data)

View File

@ -87,6 +87,20 @@
<label>{{ $t('person.gender.title') }}</label> <label>{{ $t('person.gender.title') }}</label>
</div> </div>
<div class="form-floating mb-3">
<select
class="form-select form-select-lg"
id="civility"
v-model="civility"
>
<option selected disabled >{{ $t('person.civility.placeholder') }}</option>
<option v-for="c in config.civilities" :value="c.id" :key="c.id">
{{ c.name.fr }}
</option>
</select>
<label>{{ $t('person.civility.title') }}</label>
</div>
<div class="input-group mb-3"> <div class="input-group mb-3">
<span class="input-group-text" id="birthdate"><i class="fa fa-fw fa-birthday-cake"></i></span> <span class="input-group-text" id="birthdate"><i class="fa fa-fw fa-birthday-cake"></i></span>
<input type="date" <input type="date"
@ -124,6 +138,24 @@
aria-describedby="email" /> aria-describedby="email" />
</div> </div>
<div class="input-group mb-3 form-check">
<input class="form-check-input"
type='checkbox'
v-model="showAddressForm"
name='showAddressForm'/>
<label class="form-check-label">{{ $t('person.address.show_address_form') }}</label>
</div>
<div v-if="action === 'create' && showAddressFormValue" class="form-floating mb-3">
<p>{{ $t('person.address.warning') }}</p>
<add-address
:context="addAddress.context"
:options="addAddress.options"
:addressChangedCallback="submitNewAddress"
ref="addAddress">
</add-address>
</div>
<div class="alert alert-warning" v-if="errors.length"> <div class="alert alert-warning" v-if="errors.length">
<ul> <ul>
<li v-for="(e, i) in errors" :key="i">{{ e }}</li> <li v-for="(e, i) in errors" :key="i">{{ e }}</li>
@ -134,24 +166,43 @@
</template> </template>
<script> <script>
import { getPerson, getPersonAltNames } from '../../_api/OnTheFly'; import { getCivilities, getPerson, getPersonAltNames } from '../../_api/OnTheFly';
import PersonRenderBox from '../Entity/PersonRenderBox.vue'; import PersonRenderBox from '../Entity/PersonRenderBox.vue';
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
export default { export default {
name: "OnTheFlyPerson", name: "OnTheFlyPerson",
props: ['id', 'type', 'action', 'query'], props: ['id', 'type', 'action', 'query'],
//emits: ['createAction'], //emits: ['createAction'],
components: { components: {
PersonRenderBox PersonRenderBox,
AddAddress
}, },
data() { data() {
return { return {
person: { person: {
type: 'person', type: 'person',
altNames: [] altNames: [],
addressId: null
}, },
config: { config: {
altNames: [] altNames: [],
civilities: []
},
showAddressFormValue: false,
addAddress: {
options: {
button: {
text: { create: 'person.address.create_address' },
size: 'btn-sm'
},
title: { create: 'person.address.create_address' },
},
context: {
target: {}, // boilerplate for getting the address id
edit: false,
addressId: null
}
}, },
errors: [] errors: []
} }
@ -171,6 +222,10 @@ export default {
set(value) { this.person.gender = value; }, set(value) { this.person.gender = value; },
get() { return this.person.gender; } get() { return this.person.gender; }
}, },
civility: {
set(value) { this.person.civility = {id: value, type: 'chill_main_civility'}; },
get() { return this.person.civility ? this.person.civility.id : null; }
},
birthDate: { birthDate: {
set(value) { set(value) {
if (this.person.birthdate) { if (this.person.birthdate) {
@ -195,6 +250,10 @@ export default {
set(value) { this.person.email = value; }, set(value) { this.person.email = value; },
get() { return this.person.email; } get() { return this.person.email; }
}, },
showAddressForm: {
set(value) { this.showAddressFormValue = value; },
get() { return this.showAddressFormValue; }
},
genderClass() { genderClass() {
switch (this.person.gender) { switch (this.person.gender) {
case 'woman': case 'woman':
@ -230,6 +289,13 @@ export default {
.then(altNames => { .then(altNames => {
this.config.altNames = altNames; this.config.altNames = altNames;
}); });
getCivilities()
.then(civilities => {
if ('results' in civilities) {
this.config.civilities = civilities.results;
}
});
if (this.action !== 'create') { if (this.action !== 'create') {
this.loadData(); this.loadData();
} }
@ -273,6 +339,9 @@ export default {
this.person.firstName = queryItem; this.person.firstName = queryItem;
break; break;
} }
},
submitNewAddress(payload) {
this.person.addressId = payload.addressId;
} }
} }
} }
@ -293,4 +362,9 @@ dl {
margin-left: 1em; margin-left: 1em;
} }
} }
div.form-check {
label {
margin-left: 0.5em!important;
}
}
</style> </style>

View File

@ -38,6 +38,15 @@ const personMessages = {
man: "Masculin", man: "Masculin",
neuter: "Neutre, non binaire", neuter: "Neutre, non binaire",
undefined: "Non renseigné" undefined: "Non renseigné"
},
civility: {
title: "Civilité",
placeholder: "Choisissez la civilité",
},
address: {
create_address: "Ajouter une adresse",
show_address_form: "Créer un ménage et ajouter une adresse",
warning: "Un nouveau ménage va être créé. L'usager sera membre de ce ménage."
} }
}, },
error_only_one_person: "Une seule personne peut être sélectionnée !" error_only_one_person: "Une seule personne peut être sélectionnée !"

View File

@ -93,6 +93,8 @@
{{ form_row(form.gender, { 'label' : 'Gender'|trans }) }} {{ form_row(form.gender, { 'label' : 'Gender'|trans }) }}
{{ form_row(form.civility, { 'label' : 'Civility'|trans }) }}
{{ form_row(form.birthdate, { 'label' : 'Date of birth'|trans }) }} {{ form_row(form.birthdate, { 'label' : 'Date of birth'|trans }) }}
{{ form_row(form.phonenumber, { 'label' : 'Phonenumber'|trans }) }} {{ form_row(form.phonenumber, { 'label' : 'Phonenumber'|trans }) }}
@ -105,6 +107,13 @@
{{ form_row(form.center) }} {{ form_row(form.center) }}
{% endif %} {% endif %}
<div id=addressForm>
{{ form_row(form.addressForm) }}
</div>
<div id=address>
{{ form_row(form.address) }}
</div>
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="dropdown"> <li class="dropdown">
<a class="btn btn-create dropdown-toggle" <a class="btn btn-create dropdown-toggle"
@ -132,4 +141,10 @@
{% block js %} {% block js %}
{{ encore_entry_script_tags('page_suggest_names') }} {{ encore_entry_script_tags('page_suggest_names') }}
{{ encore_entry_script_tags('page_create_person') }}
{{ encore_entry_script_tags('mod_input_address') }}
{% endblock js %} {% endblock js %}
{% block css %}
{{ encore_entry_link_tags('mod_input_address') }}
{% endblock %}

View File

@ -48,7 +48,9 @@
</div> </div>
<div class="wl-col list"> <div class="wl-col list">
<p class="item"> <p class="item">
{{ p.position.label|localize_translatable_string }} {% if p.position %}
{{ p.position.label|localize_translatable_string }}
{% endif %}
{% if p.holder %} {% if p.holder %}
<span class="fa-stack fa-holder" title="{{ 'houshold.holder'|trans|e('html_attr') }}"> <span class="fa-stack fa-holder" title="{{ 'houshold.holder'|trans|e('html_attr') }}">
<i class="fa fa-circle fa-stack-1x text-success"></i> <i class="fa fa-circle fa-stack-1x text-success"></i>

View File

@ -116,12 +116,18 @@ class MembersEditorNormalizer implements DenormalizerAwareInterface, Denormalize
$format, $format,
$context $context
); );
$position = $this->denormalizer->denormalize(
$concerned['position'] ?? null, if (array_key_exists('position', $concerned)) {
Position::class, $position = $this->denormalizer->denormalize(
$format, $concerned['position'] ?? null,
$context Position::class,
); $format,
$context
);
} else {
$position = null;
}
$startDate = $this->denormalizer->denormalize( $startDate = $this->denormalizer->denormalize(
$concerned['start_date'] ?? null, $concerned['start_date'] ?? null,
DateTimeImmutable::class, DateTimeImmutable::class,

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Serializer\Normalizer; namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface; use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension; use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension;
@ -101,6 +102,7 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
'center', 'center',
'altNames', 'altNames',
'email', 'email',
'civility',
]; ];
$fields = array_filter( $fields = array_filter(
@ -176,6 +178,13 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
case 'email': case 'email':
$person->setEmail($data[$item]); $person->setEmail($data[$item]);
break;
case 'civility':
$civility = $this->denormalizer->denormalize($data[$item], Civility::class, $format, []);
$person->setCivility($civility);
break; break;
} }
} }
@ -212,6 +221,7 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
'mobilenumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $context), 'mobilenumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $context),
'email' => $person->getEmail(), 'email' => $person->getEmail(),
'gender' => $person->getGender(), 'gender' => $person->getGender(),
'civility' => $this->normalizer->normalize($person->getCivility(), $format, $context),
]; ];
if (in_array('minimal', $groups, true) && 1 === count($groups)) { if (in_array('minimal', $groups, true) && 1 === count($groups)) {

View File

@ -1137,7 +1137,7 @@ paths:
200: 200:
description: "OK" description: "OK"
400: 400:
description: "transition cannot be applyed" description: "transition cannot be applied"
/1.0/person/accompanying-course/{id}/confidential.json: /1.0/person/accompanying-course/{id}/confidential.json:
post: post:
@ -1327,6 +1327,28 @@ paths:
responses: responses:
200: 200:
description: "ok" description: "ok"
post:
tags:
- household
requestBody:
description: "A household"
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Household"
summary: Post a new household
responses:
401:
description: "Unauthorized"
404:
description: "Not found"
200:
description: "OK"
422:
description: "Unprocessable entity (validation errors)"
400:
description: "transition cannot be applied"
/1.0/person/household/{id}.json: /1.0/person/household/{id}.json:
get: get:
@ -1507,7 +1529,7 @@ paths:
422: 422:
description: "Unprocessable entity (validation errors)" description: "Unprocessable entity (validation errors)"
400: 400:
description: "transition cannot be applyed" description: "transition cannot be applied"
/1.0/person/household/{id}/address.json: /1.0/person/household/{id}/address.json:
post: post:
@ -1543,7 +1565,7 @@ paths:
422: 422:
description: "Unprocessable entity (validation errors)" description: "Unprocessable entity (validation errors)"
400: 400:
description: "transition cannot be applyed" description: "transition cannot be applied"
/1.0/person/social/social-action.json: /1.0/person/social/social-action.json:
get: get:

View File

@ -22,4 +22,5 @@ module.exports = function(encore, entries)
encore.addEntry('page_accompanying_course_index_masonry', __dirname + '/Resources/public/page/accompanying_course_index/masonry.js'); encore.addEntry('page_accompanying_course_index_masonry', __dirname + '/Resources/public/page/accompanying_course_index/masonry.js');
encore.addEntry('page_person_resource_showhide_input', __dirname + '/Resources/public/page/person_resource/showhide-input.js'); encore.addEntry('page_person_resource_showhide_input', __dirname + '/Resources/public/page/person_resource/showhide-input.js');
encore.addEntry('page_suggest_names', __dirname + '/Resources/public/page/person/suggest-names.js'); encore.addEntry('page_suggest_names', __dirname + '/Resources/public/page/person/suggest-names.js');
encore.addEntry('page_create_person', __dirname + '/Resources/public/page/person/create-person.js');
}; };

View File

@ -86,6 +86,8 @@ Civility: Civilité
choose civility: -- choose civility: --
All genders: tous les genres All genders: tous les genres
Any person selected: Aucune personne sélectionnée Any person selected: Aucune personne sélectionnée
Create a household and add an address: Créer un ménage et ajouter une adresse
A new household will be created. The person will be member of this household.: Un nouveau ménage va être créé. L'usager sera membre de ce ménage.
# dédoublonnage # dédoublonnage
Old person: Doublon Old person: Doublon

View File

@ -65,4 +65,7 @@ The person where the course is located must be associated to the course. Change
#relationship #relationship
relationship: relationship:
duplicate: Une relation de filiation existe déjà entre ces 2 personnes duplicate: Une relation de filiation existe déjà entre ces 2 personnes
person_creation:
If you want to create an household, an address is required: Pour la création d'un ménage, une adresse est requise