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

This commit is contained in:
Julien Fastré 2022-02-11 14:24:36 +01:00
commit d3d655293e
35 changed files with 206 additions and 175 deletions

View File

@ -11,8 +11,18 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [person] add a returnPath when clicking on some Person or ThirdParty badge (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/427)
* [person] accompanying course work: fix on-the-fly update of thirdParty
* [on-the-fly] close modal only after validation
* [person] correct thirdparty PATCH url + add email and altnames in AddPerson and serializer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/433)
* change order for accompanying course work list * change order for accompanying course work list
* [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414) * [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414)
* [person]: style fix in parcours listing per person. (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/432)
* ajoute un ordre dans les localisation (api)
* [pick entity]: fix translations in modal (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/419)
* [homepage_widget]: fix translation on emergency badge (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/440)
* [person]: create person and household added to button dropdown (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/454)
* display full address in address.text in normalization. Adapt AddressRenderBox
## Test releases ## Test releases

View File

@ -918,15 +918,6 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php
-
message:
"""
#^Parameter \\$trans of method Chill\\\\PersonBundle\\\\Controller\\\\AccompanyingCourseWorkController\\:\\:__construct\\(\\) has typehint with deprecated interface Symfony\\\\Component\\\\Translation\\\\TranslatorInterface\\:
since Symfony 4\\.2, use Symfony\\\\Contracts\\\\Translation\\\\TranslatorInterface instead$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php
- -
message: message:
""" """

View File

@ -12,6 +12,8 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller; namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
@ -19,7 +21,7 @@ use Symfony\Component\HttpFoundation\Request;
*/ */
class LocationApiController extends ApiController class LocationApiController extends ApiController
{ {
public function customizeQuery(string $action, Request $request, $query): void protected function customizeQuery(string $action, Request $request, $query): void
{ {
$query $query
->leftJoin('e.locationType', 'lt') ->leftJoin('e.locationType', 'lt')
@ -31,4 +33,14 @@ class LocationApiController extends ApiController
) )
); );
} }
/**
* @param QueryBuilder $query
* @param mixed $_format
*/
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
{
return $query
->addOrderBy('e.name', 'ASC');
}
} }

View File

@ -167,6 +167,7 @@ class WorkflowController extends AbstractController
$handler = $this->entityWorkflowManager->getHandler($entityWorkflow); $handler = $this->entityWorkflowManager->getHandler($entityWorkflow);
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
$errors = [];
if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) { if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) {
// possible transition // possible transition
@ -245,7 +246,7 @@ class WorkflowController extends AbstractController
'handler_template_data' => $handler->getTemplateData($entityWorkflow), 'handler_template_data' => $handler->getTemplateData($entityWorkflow),
'transition_form' => isset($transitionForm) ? $transitionForm->createView() : null, 'transition_form' => isset($transitionForm) ? $transitionForm->createView() : null,
'entity_workflow' => $entityWorkflow, 'entity_workflow' => $entityWorkflow,
'transition_form_errors' => $errors ?? [], 'transition_form_errors' => $errors,
//'comment_form' => $commentForm->createView(), //'comment_form' => $commentForm->createView(),
] ]
); );

View File

@ -135,6 +135,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
if (!$this->steps->contains($step)) { if (!$this->steps->contains($step)) {
$this->steps[] = $step; $this->steps[] = $step;
$step->setEntityWorkflow($this); $step->setEntityWorkflow($this);
$this->stepsChainedCache = null;
} }
return $this; return $this;
@ -332,32 +333,26 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
public function isFinal(): bool public function isFinal(): bool
{ {
$steps = $this->getStepsChained(); foreach ($this->getStepsChained() as $step) {
if ($step->isFinal()) {
if (1 === count($steps)) { return true;
// the initial step cannot be finalized }
return false;
} }
/** @var EntityWorkflowStep $last */ return false;
$last = end($steps);
return $last->isFinal();
} }
public function isFreeze(): bool public function isFreeze(): bool
{ {
$steps = $this->getStepsChained(); $steps = $this->getStepsChained();
if (1 === count($steps)) { foreach ($this->getStepsChained() as $step) {
// the initial step cannot be finalized if ($step->isFreezeAfter()) {
return false; return true;
}
} }
/** @var EntityWorkflowStep $last */ return false;
$last = end($steps);
return $last->getPrevious()->isFreezeAfter();
} }
public function isUserSubscribedToFinal(User $user): bool public function isUserSubscribedToFinal(User $user): bool
@ -434,7 +429,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
$newStep->setCurrentStep($step); $newStep->setCurrentStep($step);
// copy the freeze // copy the freeze
if ($this->getCurrentStep()->isFreezeAfter()) { if ($this->isFreeze()) {
$newStep->setFreezeAfter(true); $newStep->setFreezeAfter(true);
} }

View File

@ -56,7 +56,7 @@ class EntityWorkflowStepRepository implements ObjectRepository
public function getClassName() public function getClassName()
{ {
return EntityWorkflow::class; return EntityWorkflowStep::class;
} }
private function buildQueryByUser(User $user): QueryBuilder private function buildQueryByUser(User $user): QueryBuilder

View File

@ -17,7 +17,12 @@ function loadDynamicPicker(element) {
isMultiple = parseInt(el.dataset.multiple) === 1, isMultiple = parseInt(el.dataset.multiple) === 1,
uniqId = el.dataset.uniqid, uniqId = el.dataset.uniqid,
input = element.querySelector('[data-input-uniqid="'+ el.dataset.uniqid +'"]'), input = element.querySelector('[data-input-uniqid="'+ el.dataset.uniqid +'"]'),
picked = (isMultiple) ? (JSON.parse(input.value)) : ((input.value === '[]') ? (null) : ([JSON.parse(input.value)])); picked = isMultiple ?
JSON.parse(input.value) : (
(input.value === '[]' || input.value === '') ?
null : [ JSON.parse(input.value) ]
)
;
if (!isMultiple) { if (!isMultiple) {
if (input.value === '[]'){ if (input.value === '[]'){

View File

@ -32,7 +32,7 @@
</span> </span>
</td> </td>
<td> <td>
<span v-if="c.emergency" class="badge rounded-pill bg-danger">{{ $t('emergency') }}</span> <span v-if="c.emergency" class="badge rounded-pill bg-danger me-1">{{ $t('emergency') }}</span>
<span v-if="c.confidential" class="badge rounded-pill bg-danger">{{ $t('confidential') }}</span> <span v-if="c.confidential" class="badge rounded-pill bg-danger">{{ $t('confidential') }}</span>
</td> </td>
<td> <td>
@ -80,5 +80,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
span.badge.rounded-pill.bg-danger {
text-transform: uppercase;
}
</style> </style>

View File

@ -50,7 +50,9 @@ const appMessages = {
assignated_evaluations: "{n} évaluation assignée | {n} évaluations assignées", assignated_evaluations: "{n} évaluation assignée | {n} évaluations assignées",
alert_tasks: "{n} tâche en rappel | {n} tâches en rappel", alert_tasks: "{n} tâche en rappel | {n} tâches en rappel",
warning_tasks: "{n} tâche à échéance | {n} tâches à échéance", warning_tasks: "{n} tâche à échéance | {n} tâches à échéance",
} },
emergency: "Urgent",
confidential: "Confidentiel",
} }
}; };

View File

@ -90,7 +90,7 @@ export default {
OnTheFlyThirdparty, OnTheFlyThirdparty,
OnTheFlyCreate OnTheFlyCreate
}, },
props: ['type', 'id', 'action', 'buttonText', 'displayBadge', 'isDead', 'parent', 'canCloseModal'], props: ['type', 'id', 'action', 'buttonText', 'displayBadge', 'isDead', 'parent'],
emits: ['saveFormOnTheFly'], emits: ['saveFormOnTheFly'],
data() { data() {
return { return {
@ -160,17 +160,10 @@ export default {
}, },
badgeType() { badgeType() {
return 'entity-' + this.type + ' badge-' + this.type; return 'entity-' + this.type + ' badge-' + this.type;
} },
}, getReturnPath() {
watch: { return `?returnPath=${window.location.pathname}${window.location.search}${window.location.hash}`;
canCloseModal: { },
handler: function(val, oldVal) {
if (val) {
this.closeModal();
}
},
deep: true
}
}, },
methods: { methods: {
closeModal() { closeModal() {
@ -217,9 +210,9 @@ export default {
buildLocation(id, type) { buildLocation(id, type) {
if (type === 'person') { if (type === 'person') {
// TODO i18n // TODO i18n
return `/fr/person/${id}/general`; return encodeURI(`/fr/person/${id}/general${this.getReturnPath}`);
} else if (type === 'thirdparty') { } else if (type === 'thirdparty') {
return `/fr/3party/3party/${id}/view`; return encodeURI(`/fr/3party/3party/${id}/view${this.getReturnPath}`);
} }
} }
} }

View File

@ -1,4 +1,7 @@
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'; import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n';
import { thirdpartyMessages } from 'ChillThirdPartyAssets/vuejs/_js/i18n';
import { addressMessages } from 'ChillMainAssets/vuejs/Address/i18n';
import { ontheflyMessages } from 'ChillMainAssets/vuejs/OnTheFly/i18n';
const appMessages = { const appMessages = {
fr: { fr: {
@ -12,6 +15,6 @@ const appMessages = {
} }
} }
Object.assign(appMessages.fr, personMessages.fr); Object.assign(appMessages.fr, personMessages.fr, thirdpartyMessages.fr, addressMessages.fr, ontheflyMessages.fr );
export { appMessages }; export { appMessages };

View File

@ -42,57 +42,11 @@
class="street"> class="street">
{{ address.text }} {{ address.text }}
</p> </p>
<p v-if="address.postcode"
class="postcode">
{{ address.postcode.code }} {{ address.postcode.name }}
</p>
<p v-if="address.country"
class="country">
{{ address.country.name.fr }}
</p>
</div> </div>
</div> </div>
</component> </component>
<!-- <div v-if="isMultiline === true" class="address-more">
<div v-if="address.floor">
<span class="floor">
<b>{{ $t('floor') }}</b>: {{ address.floor }}
</span>
</div>
<div v-if="address.corridor">
<span class="corridor">
<b>{{ $t('corridor') }}</b>: {{ address.corridor }}
</span>
</div>
<div v-if="address.steps">
<span class="steps">
<b>{{ $t('steps') }}</b>: {{ address.steps }}
</span>
</div>
<div v-if="address.flat">
<span class="flat">
<b>{{ $t('flat') }}</b>: {{ address.flat }}
</span>
</div>
<div v-if="address.buildingName">
<span class="buildingName">
<b>{{ $t('buildingName') }}</b>: {{ address.buildingName }}
</span>
</div>
<div v-if="address.extra">
<span class="extra">
<b>{{ $t('extra') }}</b>: {{ address.extra }}
</span>
</div>
<div v-if="address.distribution">
<span class="distribution">
<b>{{ $t('distribution') }}</b>: {{ address.distribution }}
</span>
</div>
</div> -->
<div v-if="useDatePane === true" class="address-more"> <div v-if="useDatePane === true" class="address-more">
<div v-if="address.validFrom"> <div v-if="address.validFrom">
<span class="validFrom"> <span class="validFrom">

View File

@ -64,7 +64,7 @@ class AddressNormalizer implements ContextAwareNormalizerInterface, NormalizerAw
if ($address instanceof Address) { if ($address instanceof Address) {
$data = [ $data = [
'address_id' => $address->getId(), 'address_id' => $address->getId(),
'text' => $address->isNoAddress() ? null : $this->addressRender->renderStreetLine($address, []), 'text' => $this->addressRender->renderString($address, []),
'street' => $address->getStreet(), 'street' => $address->getStreet(),
'streetNumber' => $address->getStreetNumber(), 'streetNumber' => $address->getStreetNumber(),
'postcode' => [ 'postcode' => [

View File

@ -124,7 +124,7 @@ class AddressRender implements ChillEntityRenderInterface
*/ */
public function renderString($addr, array $options): string public function renderString($addr, array $options): string
{ {
return implode(' - ', $this->renderLines($addr)); return implode(' ', $this->renderLines($addr));
} }
public function supports($entity, array $options): bool public function supports($entity, array $options): bool

View File

@ -24,30 +24,30 @@ final class EntityWorkflowTest extends TestCase
{ {
$entityWorkflow = new EntityWorkflow(); $entityWorkflow = new EntityWorkflow();
$entityWorkflow->getCurrentStep()->setFinalizeAfter(true);
$entityWorkflow->setStep('final'); $entityWorkflow->setStep('final');
$entityWorkflow->getCurrentStep()->setIsFinal(true);
$this->assertTrue($entityWorkflow->isFinalize()); $this->assertTrue($entityWorkflow->isFinal());
} }
public function testIsFinalizeWith4Steps() public function testIsFinalizeWith4Steps()
{ {
$entityWorkflow = new EntityWorkflow(); $entityWorkflow = new EntityWorkflow();
$this->assertFalse($entityWorkflow->isFinalize()); $this->assertFalse($entityWorkflow->isFinal());
$entityWorkflow->setStep('two'); $entityWorkflow->setStep('two');
$this->assertFalse($entityWorkflow->isFinalize()); $this->assertFalse($entityWorkflow->isFinal());
$entityWorkflow->setStep('previous_final'); $entityWorkflow->setStep('previous_final');
$this->assertFalse($entityWorkflow->isFinalize()); $this->assertFalse($entityWorkflow->isFinal());
$entityWorkflow->getCurrentStep()->setFinalizeAfter(true); $entityWorkflow->getCurrentStep()->setIsFinal(true);
$entityWorkflow->setStep('final'); $entityWorkflow->setStep('final');
$this->assertTrue($entityWorkflow->isFinalize()); $this->assertTrue($entityWorkflow->isFinal());
} }
public function testIsFreeze() public function testIsFreeze()
@ -64,11 +64,8 @@ final class EntityWorkflowTest extends TestCase
$this->assertFalse($entityWorkflow->isFreeze()); $this->assertFalse($entityWorkflow->isFreeze());
$entityWorkflow->getCurrentStep()->setFreezeAfter(true);
$this->assertFalse($entityWorkflow->isFreeze());
$entityWorkflow->setStep('freezed'); $entityWorkflow->setStep('freezed');
$entityWorkflow->getCurrentStep()->setFreezeAfter(true);
$this->assertTrue($entityWorkflow->isFreeze()); $this->assertTrue($entityWorkflow->isFreeze());

View File

@ -32,20 +32,20 @@ final class WorkflowByUserCounter implements NotificationCounterInterface, Event
$this->cacheItemPool = $cacheItemPool; $this->cacheItemPool = $cacheItemPool;
} }
public function addNotification(UserInterface $user): int public function addNotification(UserInterface $u): int
{ {
if (!$user instanceof User) { if (!$u instanceof User) {
return 0; return 0;
} }
$key = self::generateCacheKeyWorkflowByUser($user); $key = self::generateCacheKeyWorkflowByUser($u);
$item = $this->cacheItemPool->getItem($key); $item = $this->cacheItemPool->getItem($key);
if ($item->isHit()) { if ($item->isHit()) {
return $item->get(); return $item->get();
} }
$nb = $this->getCountUnreadByUser($user); $nb = $this->getCountUnreadByUser($u);
$item->set($nb) $item->set($nb)
->expiresAfter(60 * 15); ->expiresAfter(60 * 15);

View File

@ -72,17 +72,18 @@ class MetadataExtractor
foreach ($transitions as $transition) { foreach ($transitions as $transition) {
if ($transition->getName() === $transitionName) { if ($transition->getName() === $transitionName) {
break; $metadata = $workflow->getMetadataStore()->getTransitionMetadata($transition);
return [
'name' => $transition->getName(),
'text' => array_key_exists('label', $metadata) ?
$this->translatableStringHelper->localize($metadata['label']) : $transition->getName(),
'isForward' => $metadata['isForward'] ?? null,
];
} }
} }
$metadata = $workflow->getMetadataStore()->getTransitionMetadata($transition);
return [ return [];
'name' => $transition->getName(),
'text' => array_key_exists('label', $metadata) ?
$this->translatableStringHelper->localize($metadata['label']) : $transition->getName(),
'isForward' => $metadata['isForward'] ?? null,
];
} }
public function buildArrayPresentationForWorkflow(WorkflowInterface $workflow): array public function buildArrayPresentationForWorkflow(WorkflowInterface $workflow): array

View File

@ -50,7 +50,6 @@ class HouseholdApiController extends ApiController
*/ */
public function getHouseholdByAddressReference(AddressReference $addressReference): Response public function getHouseholdByAddressReference(AddressReference $addressReference): Response
{ {
// TODO ACL
$this->denyAccessUnlessGranted('ROLE_USER'); $this->denyAccessUnlessGranted('ROLE_USER');
$total = $this->householdACLAwareRepository->countByAddressReference($addressReference); $total = $this->householdACLAwareRepository->countByAddressReference($addressReference);

View File

@ -224,6 +224,8 @@ final class PersonController extends AbstractController
'label' => 'Add the person', 'label' => 'Add the person',
])->add('createPeriod', SubmitType::class, [ ])->add('createPeriod', SubmitType::class, [
'label' => 'Add the person and create an accompanying period', 'label' => 'Add the person and create an accompanying period',
])->add('createHousehold', SubmitType::class, [
'label' => 'Add the person and create a household'
]); ]);
$form->handleRequest($request); $form->handleRequest($request);
@ -252,6 +254,12 @@ final class PersonController extends AbstractController
]); ]);
} }
if ($form->get('createHousehold')->isClicked()) {
return $this->redirectToRoute('chill_person_household_members_editor', [
'persons' => [$person->getId()],
]);
}
return $this->redirectToRoute( return $this->redirectToRoute(
'chill_person_general_edit', 'chill_person_general_edit',
['person_id' => $person->getId()] ['person_id' => $person->getId()]

View File

@ -1153,11 +1153,8 @@ class AccompanyingPeriod implements
$this->removeComment($this->pinnedComment); $this->removeComment($this->pinnedComment);
} }
if ($comment instanceof Comment) { if (null !== $this->pinnedComment) {
if (null !== $this->pinnedComment) { $this->addComment($this->pinnedComment);
$this->addComment($this->pinnedComment);
}
$this->addComment($comment);
} }
$this->pinnedComment = $comment; $this->pinnedComment = $comment;

View File

@ -14,7 +14,7 @@ namespace Chill\PersonBundle\Repository\Household;
use Chill\MainBundle\Entity\AddressReference; use Chill\MainBundle\Entity\AddressReference;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Security\Authorization\HouseholdVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter;
use DateTime; use DateTime;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@ -39,7 +39,7 @@ final class HouseholdACLAwareRepository implements HouseholdACLAwareRepositoryIn
{ {
$centers = $this->authorizationHelper->getReachableCenters( $centers = $this->authorizationHelper->getReachableCenters(
$this->security->getUser(), $this->security->getUser(),
HouseholdVoter::SEE PersonVoter::SEE // the authorization to see a household is the same as seeing a person
); );
if ([] === $centers) { if ([] === $centers) {

View File

@ -10,6 +10,7 @@
addAge : true, addAge : true,
hLevel : 3, hLevel : 3,
isConfidential : false, isConfidential : false,
isMultiline: true,
}" }"
:person="participation.person" :person="participation.person"
:returnPath="getAccompanyingCourseReturnPath"> :returnPath="getAccompanyingCourseReturnPath">
@ -28,7 +29,7 @@
</a> </a>
</li> </li>
<li><on-the-fly :type="participation.person.type" :id="participation.person.id" action="show"></on-the-fly></li> <li><on-the-fly :type="participation.person.type" :id="participation.person.id" action="show"></on-the-fly></li>
<li><on-the-fly :type="participation.person.type" :id="participation.person.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly" :canCloseModal="canCloseOnTheFlyModal"></on-the-fly></li> <li><on-the-fly :type="participation.person.type" :id="participation.person.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly" ref="onTheFly"></on-the-fly></li>
<li> <li>
<button v-if="!participation.endDate" <button v-if="!participation.endDate"
class="btn btn-sm btn-remove" class="btn btn-sm btn-remove"
@ -90,7 +91,6 @@ export default {
hLevel: 1 hLevel: 1
} }
}, },
canCloseOnTheFlyModal: false
} }
}, },
computed: { computed: {
@ -120,12 +120,14 @@ export default {
if (payload.data.birthdate !== null) { body.birthdate = payload.data.birthdate; } if (payload.data.birthdate !== null) { body.birthdate = payload.data.birthdate; }
body.phonenumber = payload.data.phonenumber; body.phonenumber = payload.data.phonenumber;
body.mobilenumber = payload.data.mobilenumber; body.mobilenumber = payload.data.mobilenumber;
body.email = payload.data.email;
body.altNames = payload.data.altNames;
body.gender = payload.data.gender; body.gender = payload.data.gender;
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 => {
this.$store.dispatch('addPerson', { target: payload.target, body: response }) this.$store.dispatch('addPerson', { target: payload.target, body: response });
this.canCloseOnTheFlyModal = true; this.$refs.onTheFly.closeModal();
}) })
.catch((error) => { .catch((error) => {
if (error.name === 'ValidationException') { if (error.name === 'ValidationException') {
@ -143,10 +145,10 @@ export default {
body.telephone = payload.data.phonenumber; body.telephone = payload.data.phonenumber;
body.address = { id: payload.data.address.address_id }; body.address = { id: payload.data.address.address_id };
makeFetch('PATCH', `/api/1.0/third-party/third-party/${payload.data.id}.json`, body) makeFetch('PATCH', `/api/1.0/thirdparty/thirdparty/${payload.data.id}.json`, body)
.then(response => { .then(response => {
this.$store.dispatch('addThirdparty', { target: payload.target, body: response }) this.$store.dispatch('addThirdparty', { target: payload.target, body: response })
this.canCloseOnTheFlyModal = true; this.$refs.onTheFly.closeModal();
}) })
.catch((error) => { .catch((error) => {
if (error.name === 'ValidationException') { if (error.name === 'ValidationException') {

View File

@ -26,7 +26,7 @@
<template v-slot:record-actions> <template v-slot:record-actions>
<ul class="record_actions"> <ul class="record_actions">
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="show"></on-the-fly></li> <li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="show"></on-the-fly></li>
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly"></on-the-fly></li> <li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly" ref="onTheFly"></on-the-fly></li>
</ul> </ul>
</template> </template>
</third-party-render-box> </third-party-render-box>
@ -52,7 +52,7 @@
<template v-slot:record-actions> <template v-slot:record-actions>
<ul class="record_actions"> <ul class="record_actions">
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="show"></on-the-fly></li> <li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="show"></on-the-fly></li>
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly"></on-the-fly></li> <li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly" ref="onTheFly"></on-the-fly></li>
</ul> </ul>
</template> </template>
</person-render-box> </person-render-box>
@ -92,7 +92,7 @@
<template v-slot:record-actions> <template v-slot:record-actions>
<ul class="record_actions"> <ul class="record_actions">
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="show"></on-the-fly></li> <li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="show"></on-the-fly></li>
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly"></on-the-fly></li> <li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly" ref="onTheFly"></on-the-fly></li>
</ul> </ul>
</template> </template>
</third-party-render-box> </third-party-render-box>
@ -114,7 +114,7 @@
<template v-slot:record-actions> <template v-slot:record-actions>
<ul class="record_actions"> <ul class="record_actions">
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="show"></on-the-fly></li> <li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="show"></on-the-fly></li>
<li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly" :canCloseModal="canCloseOnTheFlyModal"></on-the-fly></li> <li><on-the-fly :type="accompanyingCourse.requestor.type" :id="accompanyingCourse.requestor.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly" ref="onTheFly"></on-the-fly></li>
</ul> </ul>
</template> </template>
</person-render-box> </person-render-box>
@ -189,7 +189,6 @@ export default {
uniq: true, uniq: true,
} }
}, },
canCloseOnTheFlyModal: false
} }
}, },
computed: { computed: {
@ -261,12 +260,14 @@ export default {
if (payload.data.birthdate !== null) { body.birthdate = payload.data.birthdate; } if (payload.data.birthdate !== null) { body.birthdate = payload.data.birthdate; }
body.phonenumber = payload.data.phonenumber; body.phonenumber = payload.data.phonenumber;
body.mobilenumber = payload.data.mobilenumber; body.mobilenumber = payload.data.mobilenumber;
body.email = payload.data.email;
body.altNames = payload.data.altNames;
body.gender = payload.data.gender; body.gender = payload.data.gender;
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 => {
this.$store.dispatch('addPerson', { target: payload.target, body: response }) this.$store.dispatch('addPerson', { target: payload.target, body: response })
this.canCloseOnTheFlyModal = true; this.$refs.onTheFly.closeModal();
}) })
.catch((error) => { .catch((error) => {
if (error.name === 'ValidationException') { if (error.name === 'ValidationException') {
@ -284,10 +285,10 @@ export default {
body.telephone = payload.data.phonenumber; body.telephone = payload.data.phonenumber;
body.address = { id: payload.data.address.address_id }; body.address = { id: payload.data.address.address_id };
makeFetch('PATCH', `/api/1.0/third-party/third-party/${payload.data.id}.json`, body) makeFetch('PATCH', `/api/1.0/thirdparty/thirdparty/${payload.data.id}.json`, body)
.then(response => { .then(response => {
this.$store.dispatch('addThirdparty', { target: payload.target, body: response }) this.$store.dispatch('addThirdparty', { target: payload.target, body: response })
this.canCloseOnTheFlyModal = true; this.$refs.onTheFly.closeModal();
}) })
.catch((error) => { .catch((error) => {
if (error.name === 'ValidationException') { if (error.name === 'ValidationException') {

View File

@ -35,7 +35,7 @@
:id="resource.resource.id" :id="resource.resource.id"
action="edit" action="edit"
@saveFormOnTheFly="saveFormOnTheFly" @saveFormOnTheFly="saveFormOnTheFly"
:canCloseModal="canCloseOnTheFlyModal"> ref="onTheFly">
</on-the-fly> </on-the-fly>
</li> </li>
<li> <li>
@ -82,7 +82,7 @@
:id="resource.resource.id" :id="resource.resource.id"
action="edit" action="edit"
@saveFormOnTheFly="saveFormOnTheFly" @saveFormOnTheFly="saveFormOnTheFly"
:canCloseModal="canCloseOnTheFlyModal"> ref="onTheFly">
</on-the-fly> </on-the-fly>
</li> </li>
<li> <li>
@ -116,11 +116,6 @@ export default {
}, },
props: ['resource'], props: ['resource'],
emits: ['remove'], emits: ['remove'],
data() {
return {
canCloseOnTheFlyModal: false
}
},
computed: { computed: {
parent() { parent() {
return { return {
@ -152,12 +147,14 @@ export default {
if (payload.data.birthdate !== null) { body.birthdate = payload.data.birthdate; } if (payload.data.birthdate !== null) { body.birthdate = payload.data.birthdate; }
body.phonenumber = payload.data.phonenumber; body.phonenumber = payload.data.phonenumber;
body.mobilenumber = payload.data.mobilenumber; body.mobilenumber = payload.data.mobilenumber;
body.email = payload.data.email;
body.altNames = payload.data.altNames;
body.gender = payload.data.gender; body.gender = payload.data.gender;
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 => {
this.$store.dispatch('addPerson', { target: payload.target, body: response }) this.$store.dispatch('addPerson', { target: payload.target, body: response })
this.canCloseOnTheFlyModal = true; this.$refs.onTheFly.closeModal();
}) })
.catch((error) => { .catch((error) => {
if (error.name === 'ValidationException') { if (error.name === 'ValidationException') {
@ -175,10 +172,10 @@ export default {
body.telephone = payload.data.phonenumber; body.telephone = payload.data.phonenumber;
body.address = { id: payload.data.address.address_id }; body.address = { id: payload.data.address.address_id };
makeFetch('PATCH', `/api/1.0/third-party/third-party/${payload.data.id}.json`, body) makeFetch('PATCH', `/api/1.0/thirdparty/thirdparty/${payload.data.id}.json`, body)
.then(response => { .then(response => {
this.$store.dispatch('addThirdparty', { target: payload.target, body: response }) this.$store.dispatch('addThirdparty', { target: payload.target, body: response })
this.canCloseOnTheFlyModal = true; this.$refs.onTheFly.closeModal();
}) })
.catch((error) => { .catch((error) => {
if (error.name === 'ValidationException') { if (error.name === 'ValidationException') {

View File

@ -211,7 +211,7 @@
<template v-slot:record-actions> <template v-slot:record-actions>
<ul class="record_actions"> <ul class="record_actions">
<li><on-the-fly :type="thirdparty.type" :id="thirdparty.id" action="show"></on-the-fly></li> <li><on-the-fly :type="thirdparty.type" :id="thirdparty.id" action="show"></on-the-fly></li>
<li><on-the-fly :type="thirdparty.type" :id="thirdparty.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly"></on-the-fly></li> <li><on-the-fly :type="thirdparty.type" :id="thirdparty.id" action="edit" @saveFormOnTheFly="saveFormOnTheFly" ref="onTheFly"></on-the-fly></li>
<li> <li>
<button :title="$t('remove_thirdparty')" class="btn btn-sm btn-remove" @click="removeThirdParty(thirdparty)" /> <button :title="$t('remove_thirdparty')" class="btn btn-sm btn-remove" @click="removeThirdParty(thirdparty)" />
</li> </li>
@ -286,6 +286,8 @@ import ListWorkflowModal from 'ChillMainAssets/vuejs/_components/EntityWorkflow/
import PickWorkflow from 'ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue'; import PickWorkflow from 'ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue';
import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue'; import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue';
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js'; import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js';
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
const i18n = { const i18n = {
messages: { messages: {
@ -476,15 +478,27 @@ export default {
}, },
saveFormOnTheFly(payload) { saveFormOnTheFly(payload) {
console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data); console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data);
payload.target = 'resource';
this.$store.dispatch('patchOnTheFly', payload) let body = { type: payload.type };
.catch(({name, violations}) => { body.name = payload.data.text;
if (name === 'ValidationException' || name === 'AccessException') { body.email = payload.data.email;
violations.forEach((violation) => this.$toast.open({message: violation})); body.telephone = payload.data.phonenumber;
body.address = { id: payload.data.address.address_id };
makeFetch('PATCH', `/api/1.0/thirdparty/thirdparty/${payload.data.id}.json`, body)
.then(response => {
this.$store.dispatch('updateThirdParty', response)
this.$refs.onTheFly.closeModal();
})
.catch((error) => {
if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.$toast.open({message: v });
}
} else { } else {
this.$toast.open({message: 'An error occurred'}) this.$toast.open({message: 'An error occurred'});
} }
}); })
} }
} }
}; };

View File

@ -1,7 +1,9 @@
import { createApp } from 'vue'; import { createApp } from 'vue';
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'; import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
import { store } from './store'; import { store } from './store';
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n' import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n';
import VueToast from 'vue-toast-notification';
import 'vue-toast-notification/dist/theme-sugar.css';
import App from './App.vue'; import App from './App.vue';
const i18n = _createI18n(personMessages); const i18n = _createI18n(personMessages);
@ -10,6 +12,12 @@ const app = createApp({
template: `<app></app>`, template: `<app></app>`,
}) })
.use(store) .use(store)
.use(VueToast, {
position: "bottom-right",
type: "error",
duration: 5000,
dismissible: true
})
.use(i18n) .use(i18n)
.component('app', App) .component('app', App)
.mount('#accompanying_course_work_edit'); .mount('#accompanying_course_work_edit');

View File

@ -266,6 +266,14 @@ const store = createStore({
state.thirdParties.push(unexistings[i]); state.thirdParties.push(unexistings[i]);
} }
}, },
updateThirdParty(state, thirdParty) {
for (let t of state.thirdParties) {
if (t.id === thirdParty.id){
state.thirdParties = state.thirdParties.filter(t => t.id !== thirdParty.id);
state.thirdParties.push(thirdParty);
}
}
},
removeThirdParty(state, thirdParty) { removeThirdParty(state, thirdParty) {
state.thirdParties = state.thirdParties state.thirdParties = state.thirdParties
.filter(t => t.id !== thirdParty.id); .filter(t => t.id !== thirdParty.id);
@ -278,6 +286,10 @@ const store = createStore({
}, },
}, },
actions: { actions: {
updateThirdParty({ commit }, payload) {
console.log(payload);
commit('updateThirdParty', payload);
},
getReachablesGoalsForAction({ getters, commit, dispatch }) { getReachablesGoalsForAction({ getters, commit, dispatch }) {
let let
socialActionId = getters.socialAction.id, socialActionId = getters.socialAction.id,

View File

@ -69,7 +69,7 @@
:buttonText="$t('onthefly.create.button', {q: query})" :buttonText="$t('onthefly.create.button', {q: query})"
action="create" action="create"
@saveFormOnTheFly="saveFormOnTheFly" @saveFormOnTheFly="saveFormOnTheFly"
:canCloseModal="canCloseOnTheFlyModal"> ref="onTheFly">
</on-the-fly> </on-the-fly>
</div> </div>
@ -121,7 +121,6 @@ export default {
selected: [], selected: [],
priorSuggestion: {} priorSuggestion: {}
}, },
canCloseOnTheFlyModal: false
} }
}, },
computed: { computed: {
@ -271,7 +270,7 @@ export default {
makeFetch('POST', '/api/1.0/person/person.json', data) makeFetch('POST', '/api/1.0/person/person.json', data)
.then(response => { .then(response => {
this.newPriorSuggestion(response); this.newPriorSuggestion(response);
this.canCloseOnTheFlyModal = true; this.$refs.onTheFly.closeModal();
}) })
.catch((error) => { .catch((error) => {
if (error.name === 'ValidationException') { if (error.name === 'ValidationException') {
@ -287,7 +286,7 @@ export default {
makeFetch('POST', '/api/1.0/thirdparty/thirdparty.json', data) makeFetch('POST', '/api/1.0/thirdparty/thirdparty.json', data)
.then(response => { .then(response => {
this.newPriorSuggestion(response); this.newPriorSuggestion(response);
this.canCloseOnTheFlyModal = true; this.$refs.onTheFly.closeModal();
}) })
.catch((error) => { .catch((error) => {
if (error.name === 'ValidationException') { if (error.name === 'ValidationException') {
@ -299,7 +298,6 @@ export default {
} }
}) })
} }
this.canCloseOnTheFlyModal = false;
} }
}, },
} }

View File

@ -43,10 +43,11 @@
<label for="firstname">{{ $t('person.firstname') }}</label> <label for="firstname">{{ $t('person.firstname') }}</label>
</div> </div>
<div v-for="(a) in config.altNames" :key="a.key" class="form-floating mb-3"> <div v-for="(a, i) in config.altNames" :key="a.key" class="form-floating mb-3">
<input <input
class="form-control form-control-lg" class="form-control form-control-lg"
:id="a.key" :id="a.key"
:value="personAltNamesLabels[i]"
@input="onAltNameInput" @input="onAltNameInput"
/> />
<label :for="a.key">{{ a.labels.fr }}</label> <label :for="a.key">{{ a.labels.fr }}</label>
@ -199,6 +200,9 @@ export default {
}, },
feminized() { feminized() {
return (this.person.gender === 'woman')? 'e' : ''; return (this.person.gender === 'woman')? 'e' : '';
},
personAltNamesLabels() {
return this.person.altNames.map(a => a ? a.label : '');
} }
}, },
mounted() { mounted() {

View File

@ -41,7 +41,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
<div class="wh-col"> <div class="wh-col" style="align-items: center;">
{% if chill_accompanying_periods.fields.user == 'visible' %} {% if chill_accompanying_periods.fields.user == 'visible' %}
{# the tags `data-referrer-text` is used by module `@ChillPerson/mod/AccompanyingPeriod/setReferrer.js` #} {# the tags `data-referrer-text` is used by module `@ChillPerson/mod/AccompanyingPeriod/setReferrer.js` #}
{% if period.user %} {% if period.user %}

View File

@ -115,6 +115,9 @@
<li> <li>
{{ form_widget(form.editPerson, { 'attr': { 'class': 'dropdown-item' }}) }} {{ form_widget(form.editPerson, { 'attr': { 'class': 'dropdown-item' }}) }}
</li> </li>
<li>
{{ form_widget(form.createHousehold, { 'attr': { 'class': 'dropdown-item' }}) }}
</li>
<li> <li>
{{ form_widget(form.createPeriod, { 'attr': { 'class': 'dropdown-item' }}) }} {{ form_widget(form.createPeriod, { 'attr': { 'class': 'dropdown-item' }}) }}
</li> </li>

View File

@ -91,6 +91,7 @@ class PersonJsonNormalizer implements
'deathdate', 'deathdate',
'center', 'center',
'altNames', 'altNames',
'email',
]; ];
$fields = array_filter( $fields = array_filter(
@ -161,6 +162,11 @@ class PersonJsonNormalizer implements
} }
} }
break;
case 'email':
$person->setEmail($data[$item]);
break; break;
} }
} }
@ -189,6 +195,7 @@ class PersonJsonNormalizer implements
'centers' => $this->normalizer->normalize($this->centerResolverManager->resolveCenters($person), $format, $context), 'centers' => $this->normalizer->normalize($this->centerResolverManager->resolveCenters($person), $format, $context),
'phonenumber' => $person->getPhonenumber(), 'phonenumber' => $person->getPhonenumber(),
'mobilenumber' => $person->getMobilenumber(), 'mobilenumber' => $person->getMobilenumber(),
'email' => $person->getEmail(),
'altNames' => $this->normalizeAltNames($person->getAltNames()), 'altNames' => $this->normalizeAltNames($person->getAltNames()),
'gender' => $person->getGender(), 'gender' => $person->getGender(),
'current_household_address' => $this->normalizer->normalize($person->getCurrentHouseholdAddress(), $format, $context), 'current_household_address' => $this->normalizer->normalize($person->getCurrentHouseholdAddress(), $format, $context),

View File

@ -21,6 +21,7 @@ use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use DateTime; use DateTime;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use function array_map; use function array_map;
@ -58,10 +59,16 @@ final class HouseholdApiControllerTest extends WebTestCase
$centerA = $em->getRepository(Center::class)->findOneBy(['name' => 'Center A']); $centerA = $em->getRepository(Center::class)->findOneBy(['name' => 'Center A']);
$nbReference = $em->createQueryBuilder()->select('count(ar)')->from(AddressReference::class, 'ar') $nbReference = $em->createQueryBuilder()->select('count(ar)')->from(AddressReference::class, 'ar')
->getQuery()->getSingleScalarResult(); ->getQuery()->getSingleScalarResult();
if (0 === $nbReference) {
throw new RuntimeException('any reference found. Add a reference in database to perform this test');
}
$reference = $em->createQueryBuilder()->select('ar')->from(AddressReference::class, 'ar') $reference = $em->createQueryBuilder()->select('ar')->from(AddressReference::class, 'ar')
->setFirstResult(random_int(0, $nbReference)) ->setFirstResult(random_int(0, $nbReference))
->setMaxResults(1) ->setMaxResults(1)
->getQuery()->getSingleResult(); ->getQuery()->getSingleResult();
$p = new Person(); $p = new Person();
$p->setFirstname('test')->setLastName('test lastname') $p->setFirstname('test')->setLastName('test lastname')
->setGender(Person::BOTH_GENDER) ->setGender(Person::BOTH_GENDER)
@ -79,6 +86,7 @@ final class HouseholdApiControllerTest extends WebTestCase
[HouseholdMember::class, $m->getId()], [HouseholdMember::class, $m->getId()],
[User::class, $p->getId()], [User::class, $p->getId()],
[Household::class, $h->getId()], [Household::class, $h->getId()],
[Person::class, $p->getId()],
]; ];
yield [$reference->getId(), $h->getId()]; yield [$reference->getId(), $h->getId()];

View File

@ -129,20 +129,27 @@ final class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase
$this->assertNull($period->getPinnedComment()); $this->assertNull($period->getPinnedComment());
$period->setPinnedComment($comment); $period->setPinnedComment($comment);
$this->assertSame($period->getPinnedComment(), $comment); $this->assertSame($period->getPinnedComment(), $comment);
$this->assertSame($period, $comment->getAccompanyingPeriod()); $this->assertNull($comment->getAccompanyingPeriod());
$this->assertEquals(0, count($period->getComments()), 'The initial comment should not appears in the list of comments'); $this->assertEquals(0, count($period->getComments()));
$period->setPinnedComment($replacingComment); $period->setPinnedComment($replacingComment);
$this->assertSame($period->getPinnedComment(), $replacingComment); $this->assertSame($period->getPinnedComment(), $replacingComment);
$this->assertSame($period, $replacingComment->getAccompanyingPeriod()); $this->assertNull($replacingComment->getAccompanyingPeriod());
$this->assertEquals(0, count($period->getComments()), 'The initial comment should not appears in the list of comments'); $this->assertSame($period, $comment->getAccompanyingPeriod());
$this->assertNull($comment->getAccompanyingPeriod()); $this->assertEquals(1, count($period->getComments()));
$this->assertContains($comment, $period->getComments());
$period->setPinnedComment(null); $period->setPinnedComment(null);
$this->assertNull($period->getPinnedComment()); $this->assertNull($period->getPinnedComment());
$this->assertNull($replacingComment->getAccompanyingPeriod()); $this->assertSame($period, $comment->getAccompanyingPeriod());
$this->assertEquals(0, count($period->getComments()), 'The initial comment should not appears in the list of comments'); $this->assertSame($period, $replacingComment->getAccompanyingPeriod());
$this->assertEquals(2, count($period->getComments()));
$this->assertContains($comment, $period->getComments());
$this->assertContains($replacingComment, $period->getComments());
} }
public function testRequestor() public function testRequestor()

View File

@ -124,7 +124,7 @@ address_country_code: Code pays
'Alreay existing person': 'Dossiers déjà encodés' 'Alreay existing person': 'Dossiers déjà encodés'
'Add the person': 'Ajouter la personne' 'Add the person': 'Ajouter la personne'
'Add the person and create an accompanying period': "Créer la personne & créer une période d'accompagnement" 'Add the person and create an accompanying period': "Créer la personne & créer une période d'accompagnement"
'Add the person and create an household': "Créer la personne & créer un ménage" 'Add the person and create a household': "Créer la personne & créer un ménage"
Show person: Voir le dossier de la personne Show person: Voir le dossier de la personne
'Confirm the creation': 'Confirmer la création' 'Confirm the creation': 'Confirmer la création'
'You will create this person': 'Vous allez créer le dossier suivant' 'You will create this person': 'Vous allez créer le dossier suivant'