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

This commit is contained in:
2022-04-13 23:30:51 +02:00
67 changed files with 1649 additions and 347 deletions

View File

@@ -514,6 +514,8 @@ class Household
if ($iterator->valid()) {
$current->setValidTo($iterator->current()->getValidFrom());
} else {
$current->setValidTo(null);
}
}
}

View File

@@ -276,25 +276,21 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/**
* The person's first name.
*
* @var string
*
* @ORM\Column(type="string", length=255)
* @Assert\NotBlank(message="The firstname cannot be empty")
* @Assert\Length(
* max=255,
* )
*/
private $firstName;
private string $firstName = '';
/**
* fullname canonical. Read-only field, which is calculated by
* the database.
*
* @var string
*
* @ORM\Column(type="text", nullable=true)
*/
private $fullnameCanonical;
private string $fullnameCanonical = '';
/**
* The person's gender.
@@ -328,6 +324,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* targetEntity=HouseholdMember::class,
* mappedBy="person"
* )
*
* @var Collection|HouseholdMember[]
*/
private Collection $householdParticipations;
@@ -343,15 +341,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/**
* The person's last name.
*
* @var string
*
* @ORM\Column(type="string", length=255)
* @Assert\NotBlank(message="The lastname cannot be empty")
* @Assert\Length(
* max=255,
* )
*/
private $lastName;
private string $lastName = '';
/**
* The marital status of the person.
@@ -1117,6 +1113,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this->householdAddresses;
}
/**
* @return Collection|HouseholdMember[]
*/
public function getHouseholdParticipations(): Collection
{
return $this->householdParticipations;
@@ -1126,6 +1125,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* Get participation where the person does not share the household.
*
* Order by startDate, desc
*
* @return HouseholdMember[]
*/
public function getHouseholdParticipationsNotShareHousehold(): Collection
{
@@ -1146,6 +1147,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* Get participation where the person does share the household.
*
* Order by startDate, desc
*
* @return Collection|HouseholdMember[]
*/
public function getHouseholdParticipationsShareHousehold(): Collection
{
@@ -1565,18 +1568,14 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
public function setEmail(?string $email): self
{
if (null === $email) {
$email = '';
}
$this->email = $email;
$this->email = trim((string) $email);
return $this;
}
public function setFirstName(string $firstName): self
public function setFirstName(?string $firstName): self
{
$this->firstName = $firstName;
$this->firstName = (string) $firstName;
return $this;
}
@@ -1602,9 +1601,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this;
}
public function setLastName(string $lastName): self
public function setLastName(?string $lastName): self
{
$this->lastName = $lastName;
$this->lastName = (string) $lastName;
return $this;
}

View File

@@ -29,8 +29,12 @@ use Symfony\Contracts\Translation\TranslatorInterface;
final class PersonResourceType extends AbstractType
{
private PersonRenderInterface $personRender;
private ResourceKindRender $resourceKindRender;
private ThirdPartyRender $thirdPartyRender;
private TranslatorInterface $translator;
public function __construct(ResourceKindRender $resourceKindRender, PersonRenderInterface $personRender, ThirdPartyRender $thirdPartyRender, TranslatorInterface $translator)

View File

@@ -61,8 +61,6 @@ class MembersEditor
throw new LogicException('You must define a household first');
}
$event = new PersonAddressMoveEvent($person);
$membership = (new HouseholdMember())
->setStartDate($date)
->setPerson($person)
@@ -70,9 +68,15 @@ class MembersEditor
->setHolder($holder)
->setComment($comment);
$this->household->addMember($membership);
$event->setNextMembership($membership);
if ($position->getShareHousehold()) {
// launch event only if moving to a "share household" position,
// and if the destination household is different than the previous one
$event = new PersonAddressMoveEvent($person);
$event->setNextMembership($membership);
$counter = 0;
foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) {
if ($participation === $membership) {
continue;
@@ -82,14 +86,25 @@ class MembersEditor
continue;
}
++$counter;
if ($participation->getEndDate() === null || $participation->getEndDate() > $date) {
$event->setPreviousMembership($participation);
$participation->setEndDate($date);
$this->membershipsAffected[] = $participation;
$this->oldMembershipsHashes[] = spl_object_hash($participation);
if ($participation->getHousehold() !== $this->household) {
$event->setPreviousMembership($participation);
$this->events[] = $event;
}
}
}
// send also the event if there was no participation before
if (0 === $counter) {
$this->events[] = $event;
}
foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) {
if ($participation->getHousehold() === $this->household
&& $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()
@@ -98,11 +113,30 @@ class MembersEditor
$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());
}
}
}
$this->membershipsAffected[] = $membership;
$this->persistables[] = $membership;
$this->events[] = $event;
return $this;
}

View File

@@ -88,7 +88,7 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
->setExtras([
'order' => 99999,
]);
/*
$menu->addChild($this->translator->trans('Person duplicate'), [
'route' => 'chill_person_duplicate_view',
'routeParameters' => [
@@ -98,7 +98,7 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
->setExtras([
'order' => 99999,
]);
*/
if (
'visible' === $this->showAccompanyingPeriod
&& $this->security->isGranted(AccompanyingPeriodVoter::SEE, $parameters['person'])

View File

@@ -136,8 +136,11 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac
$andWhereSearchClause = [];
$andWhereSearchClauseArgs = [];
if ('' !== $default) {
if ('' !== trim($default)) {
foreach (explode(' ', $default) as $str) {
if ('' === trim($str)) {
continue;
}
$pertinence[] =
'STRICT_WORD_SIMILARITY(LOWER(UNACCENT(?)), person.fullnamecanonical) + ' .
"(person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%')::int + " .

View File

@@ -10,7 +10,7 @@
<label class="chill-no-data-statement">{{ $tc('resources.counter', counter) }}</label>
</div>
<div class="flex-bloc mb-3">
<div class="flex-table mb-3">
<resource-item
v-for="resource in resources"
v-bind:resource="resource"

View File

@@ -138,12 +138,12 @@
@go-to-generate-document="submitBeforeGenerate"
>
<template v-slot:title>
<label class="col-sm-4 col-form-label">{{ $t('evaluation_generate_a_document') }}</label>
<label class="col-form-label">{{ $t('evaluation_generate_a_document') }}</label>
</template>
</pick-template>
<div>
<label class="col-sm-4 col-form-label">{{ $t('document_upload') }}</label>
<ul class="record_actions">
<label class="col-form-label">{{ $t('document_upload') }}</label>
<ul class="record_actions document-upload">
<li>
<add-async-upload
:buttonTitle="$t('browse')"
@@ -411,8 +411,11 @@ export default {
</script>
<style lang="scss" scoped>
input.document-title{
input.document-title {
font-weight: bold;
font-size: 1rem;
}
ul.document-upload {
justify-content: flex-start;
}
</style>

View File

@@ -45,7 +45,7 @@ const searchPersons = ({ query, options }, signal) => {
const searchEntities = ({ query, options }, signal) => {
let queryStr = parametersToString({ query, options });
let url = `/api/1.0/search.json?${queryStr}`;
return fetch(url)
return fetch(url, { signal })
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');

View File

@@ -199,15 +199,26 @@ export default {
return;
}
if (query === this.search.query) {
if (this.currentSearchQueryController !== undefined) {
this.currentSearchQueryController.abort()
if (this.search.currentSearchQueryController !== null) {
this.search.currentSearchQueryController.abort();
}
this.currentSearchQueryController = new AbortController();
searchEntities({ query, options: this.options }, this.currentSearchQueryController)
.then(suggested => new Promise((resolve, reject) => {
this.loadSuggestions(suggested.results);
resolve();
}));
this.search.currentSearchQueryController = new AbortController();
searchEntities({ query, options: this.options }, this.search.currentSearchQueryController.signal)
.then(suggested => new Promise((resolve, reject) => {
this.loadSuggestions(suggested.results);
resolve();
}))
.catch(error => {
if (error instanceof DOMException) {
if (error.name === 'AbortError') {
console.log('request aborted due to user continue typing');
return;
}
}
throw error;
})
;
}
}.bind(this), query.length > 3 ? 300 : 700);
},

View File

@@ -1,16 +1,19 @@
<div class="alert alert-warning alert-with-actions">
<div class="float-button bottom"><div class="box">
<div class="action">
<ul class="record_actions">
<li>
<a class="btn btn-sm btn-update change-icon"
href="{{ path('chill_person_accompanying_course_edit', { 'accompanying_period_id': accompanyingCourse.id, '_fragment': 'section-10' }) }}">
<i class="fa fa-fw fa-crosshairs"></i>
{{ 'fix it'|trans }}
</a>
</li>
</ul>
<div class="float-button bottom">
<div class="box">
<div class="action">
<ul class="record_actions">
<li>
<a class="btn btn-sm btn-update change-icon"
href="{{ path('chill_person_accompanying_course_edit', { 'accompanying_period_id': accompanyingCourse.id, '_fragment': 'section-10' }) }}">
<i class="fa fa-fw fa-crosshairs"></i>
{{ 'fix it'|trans }}
</a>
</li>
</ul>
</div>
<p>{{ 'Some peoples does not belong to any household currently. Add them to an household soon'|trans }}</p>
</div>
{{ 'Some peoples does not belong to any household currently. Add them to an household soon'|trans }}
</div></div>
</div>
</div>

View File

@@ -184,25 +184,27 @@
</div>
</div>
{% endif %}
<div class="mbloc col col-sm-6 col-lg-4">
<div class="notification-counter">
<h4 class="item-key">{{ 'notification.Notifications'|trans }}</h4>
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) %}
{% if notif_counter.total > 0 %}
<div class="my-2">
<a href="#notification-list">
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) }}
{% if accompanyingCourse.step != 'DRAFT' %}
<div class="mbloc col col-sm-6 col-lg-4">
<div class="notification-counter">
<h4 class="item-key">{{ 'notification.Notifications'|trans }}</h4>
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) %}
{% if notif_counter.total > 0 %}
<div class="my-2">
<a href="#notification-list">
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) }}
</a>
</div>
{% endif %}
<div class="d-grid gap-2">
<a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod', 'entityId': accompanyingCourse.id}) }}">
{{ 'notification.Notify'|trans }}
</a>
</div>
{% endif %}
<div class="d-grid gap-2">
<a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod', 'entityId': accompanyingCourse.id}) }}">
{{ 'notification.Notify'|trans }}
</a>
</div>
</div>
</div>
{% endif %}
</div>
<div class="social-actions my-4">
@@ -228,7 +230,7 @@
{% include 'ChillActivityBundle:Activity:list_recent.html.twig' with { 'context': 'accompanyingCourse', 'no_action': true } %}
</div>
{% endblock %}
<div class="notification notification-list">
{% set notifications = chill_list_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) %}
{% if notifications is not empty %}

View File

@@ -128,11 +128,13 @@
'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>
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w) %}
<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>
{% endif %}
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE', w) %}
<li>
<a class="btn btn-delete" title="{{ 'Delete'|trans }}"

View File

@@ -18,13 +18,7 @@
<h1>{{ block('title') }}</h1>
{% if works|length == 0 %}
<p class="chill-no-data-statement">{{ 'accompanying_course_work.Any work'|trans }}
<a class="btn btn-sm btn-create"
title="{{ 'accompanying_course_work.create'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_new', { 'id': accompanyingCourse.id }) }}"
></a>
</p>
<p class="chill-no-data-statement">{{ 'accompanying_course_work.Any work'|trans }}</p>
{% else %}
<div class="flex-table accompanying_course_work-list">
{% for w in works %}
@@ -35,6 +29,7 @@
{{ chill_pagination(paginator) }}
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE', accompanyingCourse) %}
<ul class="record_actions sticky-form-buttons">
<li>
<a href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_new', { 'id': accompanyingCourse.id }) }}"
@@ -43,6 +38,7 @@
</a>
</li>
</ul>
{% endif %}
</div>
{% endblock %}

View File

@@ -116,14 +116,40 @@
</div>
</div>
{% endif %}
<div class="item-row separator">
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %}
{% if notif_counter.total > 0 %}
<div class="item-col item-meta">
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) }}
{% if show_pinned_comment|default(false) and period.pinnedComment is not empty%}
<div class="item-row separator comment">
<div style="width: 100%">
<h4 class="item-key visually-hidden">{{ 'Pinned comment'|trans }}</h4>
<blockquote class="chill-user-quote">
{{ period.pinnedComment.content|u.truncate(750, '…', false)|chill_markdown_to_html }}
{% if period.pinnedComment.content|length > 750 %}
<a href="{{ chill_path_add_return_path('chill_person_accompanying_period_comment_list', {'accompanying_period_id': period.id}) }}">{{ 'Read more'|trans }}</a>
{% endif %}
<div class="metadata">
{{ 'Last updated by'| trans }}
<span class="user">
{{ period.pinnedComment.updatedBy|chill_entity_render_box }}
</span>
{{ 'on'|trans ~ ' ' }}
<span class="date">
{{ period.pinnedComment.updatedAt|format_datetime("medium", "short") }}
</span>
</div>
</blockquote>
</div>
{% endif %}
{% if itemMeta is defined %}
</div>
{% endif %}
<div class="item-row separator">
{% if itemMeta is not defined %}
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %}
{% if notif_counter.total > 0 %}
<div class="item-col item-meta">
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) }}
</div>
{% endif %}
{% else %}
{{ itemMeta }}
{% endif %}
<div class="item-col">

View File

@@ -1,133 +1,139 @@
{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%}
<div class="flex-table accompanying_course_work-list">
<div class="item-bloc evaluation-item bg-chill-llight-gray">
<div class="item-row mb-2">
<h1>{{ doc.title }}</h1>
</div>
<div class="item-row mb-2">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
{{ evaluation.accompanyingPeriodWork.socialAction|chill_entity_render_string }}
<ul class="small_in_title columns mt-1">
<li>
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.accompanyingPeriodWork.startDate|format_date('short') }}</b>
</li>
{% if evaluation.accompanyingPeriodWork.endDate %}
{% if doc is null %}
<div class="alert alert-warning">
{{ 'workflow.doc for evaluation deleted'|trans }}
</div>
{% else %}
<div class="flex-table accompanying_course_work-list">
<div class="item-bloc evaluation-item bg-chill-llight-gray">
<div class="item-row mb-2">
<h1>{{ doc.title }}</h1>
</div>
<div class="item-row mb-2">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
{{ evaluation.accompanyingPeriodWork.socialAction|chill_entity_render_string }}
<ul class="small_in_title columns mt-1">
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.accompanyingPeriodWork.endDate|format_date('short') }}</b>
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.accompanyingPeriodWork.startDate|format_date('short') }}</b>
</li>
{% endif %}
</ul>
</span>
</h2>
</div>
<div class="item-row mb-2">
<div class="item-col" style="width: 17%;">
<h4 class="title_label">
{{ 'Participants'|trans }}
</h4>
</div>
<div class="item-col list">
{% for p in evaluation.accompanyingPeriodWork.persons %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: p.id },
action: 'show',
displayBadge: true,
buttonText: p|chill_entity_render_string,
isDead: p.deathdate is not null
} %}
{% endfor %}
</div>
</div>
<div class="item-row column">
<table class="obj-res-eval my-3" style="font-size: 110% !important;">
<thead>
<tr>
<th class="eval">
<h4 class="title_label">
{{ 'Évaluation'|trans }}
</h4>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="eval">
<ul class="eval_title">
<li class="my-2">
{{ evaluation.evaluation.title|localize_translatable_string }}
<ul class="columns pt-2">
<li>
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.startDate|format_date('short') }}</b>
</li>
{% if evaluation.endDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.endDate|format_date('short') }}</b>
</li>
{% endif %}
{% if evaluation.maxDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.maxDate|format_date('short') }}</b>
</li>
{% endif %}
{% if evaluation.warningInterval and evaluation.warningInterval.d > 0 %}
<li>
{% set days = (evaluation.warningInterval.d + evaluation.warningInterval.m * 30) %}
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
</li>
{% endif %}
<li>
{% if evaluation.createdBy is not null %}
<span class="item-key">créé par</span>
<b>{{ evaluation.createdBy.username }}</b>
{% endif %}
{% if evaluation.createdAt is not null %}
<span class="item-key">{{ 'le'|trans }}</span>
<b>{{ evaluation.createdAt|format_date('short') }}</b>
{% endif %}
</li>
</ul>
{% if evaluation.comment %}
<blockquote class="chill-user-quote" style="margin-left: 0;">
{{ evaluation.comment }}
</blockquote>
{% endif %}
{% if evaluation.accompanyingPeriodWork.endDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.accompanyingPeriodWork.endDate|format_date('short') }}</b>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
<div class="item-row">
{% import '@ChillPerson/Macro/updatedBy.html.twig' as macro %}
{{ macro.updatedBy(evaluation) }}
{% endif %}
</ul>
</span>
</h2>
</div>
<div class="item-row mb-2">
<div class="item-col" style="width: 17%;">
<h4 class="title_label">
{{ 'Participants'|trans }}
</h4>
</div>
<div class="item-col list">
{% for p in evaluation.accompanyingPeriodWork.persons %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: p.id },
action: 'show',
displayBadge: true,
buttonText: p|chill_entity_render_string,
isDead: p.deathdate is not null
} %}
{% endfor %}
</div>
</div>
<div class="item-row column">
<table class="obj-res-eval my-3" style="font-size: 110% !important;">
<thead>
<tr>
<th class="eval">
<h4 class="title_label">
{{ 'Évaluation'|trans }}
</h4>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="eval">
<ul class="eval_title">
<li class="my-2">
{{ evaluation.evaluation.title|localize_translatable_string }}
<ul class="columns pt-2">
<li>
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.startDate|format_date('short') }}</b>
</li>
{% if evaluation.endDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.endDate|format_date('short') }}</b>
</li>
{% endif %}
{% if evaluation.maxDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.maxDate|format_date('short') }}</b>
</li>
{% endif %}
{% if evaluation.warningInterval and evaluation.warningInterval.d > 0 %}
<li>
{% set days = (evaluation.warningInterval.d + evaluation.warningInterval.m * 30) %}
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
</li>
{% endif %}
<li>
{% if evaluation.createdBy is not null %}
<span class="item-key">créé par</span>
<b>{{ evaluation.createdBy.username }}</b>
{% endif %}
{% if evaluation.createdAt is not null %}
<span class="item-key">{{ 'le'|trans }}</span>
<b>{{ evaluation.createdAt|format_date('short') }}</b>
{% endif %}
</li>
</ul>
{% if evaluation.comment %}
<blockquote class="chill-user-quote" style="margin-left: 0;">
{{ evaluation.comment }}
</blockquote>
{% endif %}
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
<div class="item-row">
{% import '@ChillPerson/Macro/updatedBy.html.twig' as macro %}
{{ macro.updatedBy(evaluation) }}
</div>
</div>
</div>
</div>
{% if display_action is defined and display_action == true %}
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', evaluation.accompanyingPeriodWork) %}
<ul class="record_actions">
<li>{{ m.download_button(doc.storedObject, doc.title) }}</li>
{% if chill_document_is_editable(doc.storedObject) %}
{% if display_action is defined and display_action == true %}
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', evaluation.accompanyingPeriodWork) %}
<ul class="record_actions">
<li>{{ m.download_button(doc.storedObject, doc.title) }}</li>
{% if chill_document_is_editable(doc.storedObject) %}
<li>
{{ doc.storedObject|chill_document_edit_button }}
</li>
{% endif %}
<li>
{{ doc.storedObject|chill_document_edit_button }}
<a class="btn btn-show" href="{{ path('chill_person_accompanying_period_work_edit', {'id': evaluation.accompanyingPeriodWork.id}) }}">
{{ 'Show'|trans }}
</a>
</li>
</ul>
{% endif %}
<li>
<a class="btn btn-show" href="{{ path('chill_person_accompanying_period_work_edit', {'id': evaluation.accompanyingPeriodWork.id}) }}">
{{ 'Show'|trans }}
</a>
</li>
</ul>
{% endif %}
{% endif %}

View File

@@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\BudgetBundle\Service\Summary\SummaryBudget;
use Chill\BudgetBundle\Service\Summary\SummaryBudgetInterface;
use Chill\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Civility;
@@ -23,6 +23,7 @@ use Chill\PersonBundle\Repository\Relationships\RelationshipRepository;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use libphonenumber\PhoneNumber;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
@@ -45,7 +46,7 @@ class PersonDocGenNormalizer implements
private RelationshipRepository $relationshipRepository;
private SummaryBudget $summaryBudget;
private SummaryBudgetInterface $summaryBudget;
private TranslatableStringHelper $translatableStringHelper;
@@ -56,7 +57,7 @@ class PersonDocGenNormalizer implements
RelationshipRepository $relationshipRepository,
TranslatorInterface $translator,
TranslatableStringHelper $translatableStringHelper,
SummaryBudget $summaryBudget
SummaryBudgetInterface $summaryBudget
) {
$this->personRender = $personRender;
$this->relationshipRepository = $relationshipRepository;
@@ -71,6 +72,7 @@ class PersonDocGenNormalizer implements
$dateContext = $context;
$dateContext['docgen:expects'] = DateTimeInterface::class;
$addressContext = array_merge($context, ['docgen:expects' => Address::class]);
$phonenumberContext = array_merge($context, ['docgen:expects' => PhoneNumber::class]);
$personResourceContext = array_merge($context, [
'docgen:expects' => Person\PersonResource::class,
// we simplify the list of attributes for the embedded persons
@@ -113,9 +115,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' => $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),
'firstPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber() ?? $person->getMobilenumber(), $format, $phonenumberContext),
'fixPhoneNumber' => $this->normalizer->normalize($person->getPhonenumber(), $format, $phonenumberContext),
'mobilePhoneNumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $phonenumberContext),
'nationality' => null !== ($c = $person->getNationality()) ? $this->translatableStringHelper->localize($c->getName()) : '',
'placeOfBirth' => $person->getPlaceOfBirth(),
'memo' => $person->getMemo(),
@@ -214,6 +216,14 @@ class PersonDocGenNormalizer implements
$data['relations'] = [];
}
if ($context['docgen:person:with-budget'] ?? false) {
$data['budget']['person'] = $this->summaryBudget->getSummaryForPerson(null);
if ($context['docgen:person:with-household'] ?? false) {
$data['budget']['household'] = $this->summaryBudget->getSummaryForHousehold(null);
}
}
return $data;
}
}

View File

@@ -208,8 +208,8 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
'birthdate' => $this->normalizer->normalize($person->getBirthdate(), $format, $context),
'deathdate' => $this->normalizer->normalize($person->getDeathdate(), $format, $context),
'age' => $this->normalizer->normalize($person->getAge(), $format, $context),
'phonenumber' => $this->normalizer->normalize($person->getPhonenumber()),
'mobilenumber' => $this->normalizer->normalize($person->getMobilenumber()),
'phonenumber' => $this->normalizer->normalize($person->getPhonenumber(), $format, $context),
'mobilenumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $context),
'email' => $person->getEmail(),
'gender' => $person->getGender(),
];

View File

@@ -152,8 +152,25 @@ class AccompanyingPeriodContext implements
$options = $template->getOptions();
$persons = $entity->getCurrentParticipations()->map(static function (AccompanyingPeriodParticipation $p) {
return $p->getPerson();
})
->toArray();
});
foreach ($entity->getCurrentParticipations() as $p) {
foreach ($p->getPerson()->getResources() as $r) {
if (null !== $r->getPerson() && !$persons->contains($r->getPerson())) {
$persons->add($r->getPerson());
}
}
}
if (null !== $entity->getRequestorPerson() && !$persons->contains($entity->getRequestorPerson())) {
$persons->add($entity->getRequestorPerson());
}
foreach ($entity->getResources() as $r) {
if (null !== $r->getPerson() && !$persons->contains($r->getPerson())) {
$persons->add($r->getPerson());
}
}
foreach (['mainPerson', 'person1', 'person2'] as $key) {
if ($options[$key] ?? false) {

View File

@@ -65,7 +65,7 @@ final class HouseholdApiControllerTest extends WebTestCase
}
$reference = $em->createQueryBuilder()->select('ar')->from(AddressReference::class, 'ar')
->setFirstResult(random_int(0, $nbReference))
->setFirstResult(random_int(0, $nbReference - 1))
->setMaxResults(1)
->getQuery()->getSingleResult();

View File

@@ -75,6 +75,7 @@ final class HouseholdTest extends TestCase
$lastAddress = new Address();
$lastAddress->setValidFrom($yesterday = new DateTime('yesterday'));
$lastAddress->setValidTo(new DateTime('tomorrow'));
$household->addAddress($lastAddress);
$this->assertNull($lastAddress->getValidTo());
@@ -82,6 +83,7 @@ final class HouseholdTest extends TestCase
$previousAddress = new Address();
$previousAddress->setValidFrom($oneMonthAgo = new DateTime('1 month ago'));
$previousAddress->setValidTo(new DateTime('now'));
$household->addAddress($previousAddress);
$addresses = $household->getAddressesOrdered();
@@ -95,6 +97,7 @@ final class HouseholdTest extends TestCase
$futureAddress = new Address();
$futureAddress->setValidFrom($tomorrow = new DateTime('tomorrow'));
$futureAddress->setValidTo(new DateTime('2150-01-01'));
$household->addAddress($futureAddress);
$addresses = $household->getAddressesOrdered();

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Tests\Household;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Household\Position;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent;
@@ -39,6 +40,122 @@ final class MembersEditorTest extends TestCase
$this->factory = $this->buildMembersEditorFactory();
}
public function testAddingParticipationNotSharingHouseholdCloseTheOldOnes()
{
$person = new Person();
$position = (new Position())->setShareHousehold(false);
$household = new Household();
// set a first time the person in position
$factory = $this->buildMembersEditorFactory();
$editor = $factory->createEditor($household);
$editor->addMovement($aMonthAgo = new DateTimeImmutable('1 month ago'), $person, $position);
// set a second time the person in position
$factory = $this->buildMembersEditorFactory();
$editor = $factory->createEditor($household);
$editor->addMovement($yesterday = new DateTimeImmutable('yesterday'), $person, $position);
$this->assertCount(2, $person->getHouseholdParticipationsNotShareHousehold());
$startDates = [];
$endDates = [];
foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) {
$startDates[] = $participation->getStartDate();
$endDates[] = $participation->getEndDate();
}
$this->assertContains($aMonthAgo, $startDates);
$this->assertContains($yesterday, $startDates);
$this->assertContains($yesterday, $endDates);
$this->assertContains(null, $endDates);
}
/**
* We test here a move for a person:.
*
* * which was in a position "sharing household"
* * which move to the another household, in a position "not sharing household"
*
* The person should stays in the two households
*/
public function testMoveFromSharingHouseholdToNotSharingHousehouldInDifferentHousehold()
{
$person = new Person();
$household = new Household();
$positionSharing = (new Position())->setShareHousehold(true);
$positionNotSharing = (new Position())->setShareHousehold(false);
$factory = $this->buildMembersEditorFactory();
$editor = $factory->createEditor($household);
// we add the member to the household
$editor->addMovement(new DateTimeImmutable('1 month ago'), $person, $positionSharing);
// double check that the person is in the household
$this->assertContains($person, $household->getCurrentPersons());
// we do the move to the position not sharing household
$editor = $factory->createEditor($household2 = new Household());
$editor->addMovement(new DateTimeImmutable('yesterday'), $person, $positionNotSharing);
$sharings = $household->getCurrentMembers()->filter(static function (HouseholdMember $m) {
return $m->getShareHousehold();
});
$notSharing = $household2->getCurrentMembers()->filter(static function (HouseholdMember $m) {
return !$m->getShareHousehold();
});
$this->assertCount(1, $notSharing);
$this->assertCount(1, $sharings);
$getPerson = static function (HouseholdMember $m) { return $m->getPerson(); };
$this->assertContains($person, $notSharing->map($getPerson));
}
/**
* We test here a move for a person:.
*
* * which was in a position "sharing household"
* * which move to the same household, in a position "not sharing household"
*/
public function testMoveFromSharingHouseholdToNotSharingHousehouldInSamehousehold()
{
$person = new Person();
$household = new Household();
$positionSharing = (new Position())->setShareHousehold(true);
$positionNotSharing = (new Position())->setShareHousehold(false);
$factory = $this->buildMembersEditorFactory();
$editor = $factory->createEditor($household);
// we add the member to the household
$editor->addMovement(new DateTimeImmutable('1 month ago'), $person, $positionSharing);
// double check that the person is in the household
$this->assertContains($person, $household->getCurrentPersons());
// we do the move to the position not sharing household
$editor = $factory->createEditor($household);
$editor->addMovement(new DateTimeImmutable('yesterday'), $person, $positionNotSharing);
$sharings = $household->getCurrentMembers()->filter(static function (HouseholdMember $m) {
return $m->getShareHousehold();
});
$notSharing = $household->getCurrentMembers()->filter(static function (HouseholdMember $m) {
return !$m->getShareHousehold();
});
$this->assertCount(1, $notSharing);
$this->assertCount(0, $sharings);
$getPerson = static function (HouseholdMember $m) { return $m->getPerson(); };
$this->assertContains($person, $notSharing->map($getPerson));
}
public function testMovePersonWithoutSharedHousehold()
{
$person = new Person();
@@ -126,7 +243,7 @@ final class MembersEditorTest extends TestCase
$this->assertEquals($date, $membership1->getEndDate());
}
public function testPostMove()
public function testPostMoveToAPositionNotSharingHousehold()
{
$person = new Person();
$position = (new Position())
@@ -134,6 +251,86 @@ final class MembersEditorTest extends TestCase
$household1 = new Household();
$household2 = new Household();
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
$eventDispatcher
->dispatch(Argument::type(PersonAddressMoveEvent::class))
->shouldNotBeCalled();
$factory = $this->buildMembersEditorFactory(
$eventDispatcher->reveal(),
null
);
$editor = $factory->createEditor($household1);
$editor->addMovement(new DateTimeImmutable('now'), $person, $position);
$editor->postMove();
}
public function testPostMoveToAPositionSharingHouseholdAndSameHousehold()
{
$person = new Person();
$position = (new Position())
->setShareHousehold(true);
$position2 = (new Position())
->setShareHousehold(true);
$household1 = new Household();
// set into the first household
$editor = $this->buildMembersEditorFactory()
->createEditor($household1);
$editor->addMovement(new DateTimeImmutable('1 year ago'), $person, $position);
// prepare for next move
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
$eventDispatcher
->dispatch(Argument::type(PersonAddressMoveEvent::class))
->shouldNotBeCalled();
$factory = $this->buildMembersEditorFactory(
$eventDispatcher->reveal(),
null
);
$editor = $factory->createEditor($household1);
$editor->addMovement(new DateTimeImmutable('now'), $person, $position2);
$editor->postMove();
}
public function testPostMoveToAPositionSharingHouseholdFromDifferentHousehold()
{
$person = new Person();
$position = (new Position())
->setShareHousehold(true);
$household1 = new Household();
$household2 = new Household();
// set into the first household
$editor = $this->buildMembersEditorFactory()
->createEditor($household1);
$editor->addMovement(new DateTimeImmutable('1 year ago'), $person, $position);
// perform now the movement
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
$eventDispatcher
->dispatch(Argument::type(PersonAddressMoveEvent::class))
->shouldBeCalled();
$factory = $this->buildMembersEditorFactory(
$eventDispatcher->reveal(),
null
);
$editor = $factory->createEditor($household2);
$editor->addMovement(new DateTimeImmutable('now'), $person, $position);
$editor->postMove();
}
public function testPostMoveToAPositionSharingHouseholdFromNoHousehold()
{
$person = new Person();
$position = (new Position())
->setShareHousehold(true);
$household1 = new Household();
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
$eventDispatcher
->dispatch(Argument::type(PersonAddressMoveEvent::class))
->shouldBeCalled();

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Serializer\Normalizer;
use Chill\BudgetBundle\Service\Summary\SummaryBudgetInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Household\Household;
@@ -72,6 +73,17 @@ final class PersonDocGenNormalizerTest extends KernelTestCase
$this->normalizer = self::$container->get(NormalizerInterface::class);
}
public function dataGeneratorNormalizationNullOrNotNullHaveSameKeys(): iterable
{
yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read']]];
yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read'], 'docgen:person:with-household' => true]];
yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read'], 'docgen:person:with-relations' => true]];
yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read'], 'docgen:person:with-budget' => true]];
}
public function generateData()
{
$person = new Person();
@@ -90,12 +102,16 @@ final class PersonDocGenNormalizerTest extends KernelTestCase
yield [null, self::BLANK, 'normalization for a null person'];
}
public function testNormalizationNullOrNotNullHaveSameKeys()
/**
* @dataProvider dataGeneratorNormalizationNullOrNotNullHaveSameKeys
*
* @param mixed $context
*/
public function testNormalizationNullOrNotNullHaveSameKeys($context)
{
$this->markTestSkipped();
$period = new Person();
$notNullData = $this->buildPersonNormalizer()->normalize($period, 'docgen', ['docgen:expects' => Person::class]);
$nullData = $this->buildPersonNormalizer()->normalize(null, 'docgen', ['docgen:expects' => Person::class]);
$notNullData = $this->buildPersonNormalizer()->normalize($period, 'docgen', $context);
$nullData = $this->buildPersonNormalizer()->normalize(null, 'docgen', $context);
$this->assertEqualsCanonicalizing(
array_keys($notNullData),
@@ -131,7 +147,6 @@ final class PersonDocGenNormalizerTest extends KernelTestCase
public function testNormalizePersonWithHousehold()
{
$this->markTestSkipped();
$household = new Household();
$person = new Person();
$person
@@ -172,7 +187,6 @@ final class PersonDocGenNormalizerTest extends KernelTestCase
public function testNormalizePersonWithRelationships()
{
$this->markTestSkipped();
$person = (new Person())->setFirstName('Renaud')->setLastName('megane');
$father = (new Person())->setFirstName('Clément')->setLastName('megane');
$mother = (new Person())->setFirstName('Mireille')->setLastName('Mathieu');
@@ -235,13 +249,25 @@ final class PersonDocGenNormalizerTest extends KernelTestCase
?RelationshipRepository $relationshipRepository = null,
?TranslatorInterface $translator = null,
?TranslatableStringHelper $translatableStringHelper = null,
?NormalizerInterface $normalizer = null
?NormalizerInterface $normalizer = null,
?SummaryBudgetInterface $summaryBudget = null
): PersonDocGenNormalizer {
if (null === $summaryBudget) {
$summaryBudget = $this->prophesize(SummaryBudgetInterface::class);
$summaryBudget->getSummaryForHousehold(Argument::any())->willReturn(
['resources' => [], 'charges' => []]
);
$summaryBudget->getSummaryForPerson(Argument::any())->willReturn(
['resources' => [], 'charges' => []]
);
}
$personDocGenNormalizer = new PersonDocGenNormalizer(
$personRender ?? self::$container->get(PersonRender::class),
$relationshipRepository ?? self::$container->get(RelationshipRepository::class),
$translator ?? self::$container->get(TranslatorInterface::class),
$translatableStringHelper ?? self::$container->get(TranslatableStringHelperInterface::class)
$translatableStringHelper ?? self::$container->get(TranslatableStringHelperInterface::class),
$summaryBudget->reveal(),
);
if (null === $normalizer) {
@@ -259,13 +285,31 @@ final class PersonDocGenNormalizerTest extends KernelTestCase
?PersonRender $personRender = null,
?RelationshipRepository $relationshipRepository = null,
?TranslatorInterface $translator = null,
?TranslatableStringHelper $translatableStringHelper = null
?TranslatableStringHelper $translatableStringHelper = null,
?SummaryBudgetInterface $summaryBudget = null
): PersonDocGenNormalizer {
if (null === $relationshipRepository) {
$relationshipRepository = $this->prophesize(RelationshipRepository::class);
$relationshipRepository->findByPerson(Argument::type(Person::class))->willReturn([]);
$relationshipRepository = $relationshipRepository->reveal();
}
if (null === $summaryBudget) {
$summaryBudget = $this->prophesize(SummaryBudgetInterface::class);
$summaryBudget->getSummaryForHousehold(Argument::any())->willReturn(
['resources' => [], 'charges' => []]
);
$summaryBudget->getSummaryForPerson(Argument::any())->willReturn(
['resources' => [], 'charges' => []]
);
}
$normalizer = new PersonDocGenNormalizer(
$personRender ?? self::$container->get(PersonRender::class),
$relationshipRepository ?? self::$container->get(RelationshipRepository::class),
$relationshipRepository,
$translator ?? self::$container->get(TranslatorInterface::class),
$translatableStringHelper ?? self::$container->get(TranslatableStringHelperInterface::class)
$translatableStringHelper ?? self::$container->get(TranslatableStringHelperInterface::class),
$summaryBudget->reveal()
);
$normalizerManager = $this->prophesize(NormalizerInterface::class);
$normalizerManager->supportsNormalization(Argument::any(), 'docgen', Argument::any())->willReturn(true);

View File

@@ -48,6 +48,12 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW
{
$doc = $this->getRelatedEntity($entityWorkflow);
if (null === $doc) {
return [
'persons' => [],
];
}
return [
'persons' => $doc->getAccompanyingPeriodWorkEvaluation()
->getAccompanyingPeriodWork()->getPersons(),
@@ -58,6 +64,10 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW
{
$doc = $this->getRelatedEntity($entityWorkflow);
if (null === $doc) {
return $this->translator->trans('workflow.doc for evaluation deleted');
}
return $this->translator->trans(
'workflow.Doc for evaluation (n°%eval%)',
['%eval%' => $entityWorkflow->getRelatedEntityId()]
@@ -98,7 +108,7 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW
return [
'entity_workflow' => $entityWorkflow,
'evaluation' => $doc->getAccompanyingPeriodWorkEvaluation(),
'evaluation' => null !== $doc ? $doc->getAccompanyingPeriodWorkEvaluation() : $doc,
'doc' => $doc,
];
}

View File

@@ -584,6 +584,7 @@ My accompanying periods in draft: Mes parcours brouillons
workflow:
Doc for evaluation (n°%eval%): Document de l'évaluation n°%eval%
doc for evaluation deleted: Document supprimé dans une évaluation
period_by_user_list:
Period by user: Parcours d'accompagnement par utilisateur