Merge branch 'master' into issue303_household_fixes

This commit is contained in:
nobohan 2022-01-04 16:06:15 +01:00
commit 06f8014b76
7 changed files with 197 additions and 101 deletions

View File

@ -34,6 +34,7 @@ and this project adheres to
* Household: show date validFrom and validTo when moving * Household: show date validFrom and validTo when moving
* address reference: add index for refid * address reference: add index for refid
* [accompanyingCourse_work] fix styles conflicts + fix bug with remove goal (remove goals one at a time) * [accompanyingCourse_work] fix styles conflicts + fix bug with remove goal (remove goals one at a time)
* [accompanyingCourse] improve masonry on resume page, add origin
## Test releases ## Test releases

View File

@ -11,8 +11,6 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Config; namespace Chill\PersonBundle\Config;
use function count;
/** /**
* Give help to interact with the config for alt names. * Give help to interact with the config for alt names.
*/ */
@ -69,6 +67,6 @@ class ConfigPersonAltNamesHelper
*/ */
public function hasAltNames(): bool public function hasAltNames(): bool
{ {
return count($this->config) > 0; return [] !== $this->config;
} }
} }

View File

@ -15,15 +15,14 @@ use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use function in_array;
use function array_filter;
use function array_values;
class PersonApiController extends ApiController class PersonApiController extends ApiController
{ {
@ -57,7 +56,9 @@ class PersonApiController extends ApiController
$configAltNamesChoices, $configAltNamesChoices,
array_keys($configAltNamesChoices) array_keys($configAltNamesChoices)
), ),
Response::HTTP_OK, [], ['groups' => ['read']] Response::HTTP_OK,
[],
['groups' => ['read']]
); );
} }
@ -79,23 +80,51 @@ class PersonApiController extends ApiController
{ {
$this->denyAccessUnlessGranted(PersonVoter::SEE, $person); $this->denyAccessUnlessGranted(PersonVoter::SEE, $person);
$addresses = []; $seenAddressIds = [];
// collect addresses from location in courses // collect addresses from location in courses
foreach ($person->getAccompanyingPeriodParticipations() as $participation) { $addresses = $person
if (null !== $participation->getAccompanyingPeriod()->getAddressLocation()) { ->getAccompanyingPeriodParticipations()
$a = $participation->getAccompanyingPeriod()->getAddressLocation(); ->filter(
$addresses[$a->getId()] = $a; static function (AccompanyingPeriodParticipation $accompanyingPeriodParticipation): bool {
} return null !== $accompanyingPeriodParticipation->getAccompanyingPeriod()->getAddressLocation();
} }
)
->map(
static function (AccompanyingPeriodParticipation $accompanyingPeriodParticipation): ?Address {
return $accompanyingPeriodParticipation->getAccompanyingPeriod()->getAddressLocation();
}
)
->filter(
// We remove potential null addresses.
static fn (?Address $address): bool => null !== $address
)
->filter(
static function (Address $address) use (&$seenAddressIds): bool {
$id = $address->getId();
if (in_array($id, $seenAddressIds, true)) {
return false;
}
$seenAddressIds[] = $id;
return true;
}
);
// remove the actual address // remove the actual address
$actual = $person->getCurrentHouseholdAddress(); $actual = $person->getCurrentHouseholdAddress();
if (null !== $actual) { if (null !== $actual) {
$addresses = array_filter($addresses, static fn ($a) => $a !== $actual); $addresses = $addresses->filter(static fn (Address $address): bool => $address !== $actual);
} }
return $this->json(array_values($addresses), Response::HTTP_OK, [], ['groups' => ['read']]); return $this->json(
$addresses->getValues(),
Response::HTTP_OK,
[],
['groups' => ['read']]
);
} }
} }

View File

@ -254,3 +254,24 @@ abbr.referrer { // still used ?
border: 1px solid black; border: 1px solid black;
padding: 10px; padding: 10px;
} }
/// Masonry blocs on AccompanyingCourse resume page
div#dashboards {
div.mbloc {
& > div:not(.warnings) {
border: 1px solid $chill-light-gray;
background-color: $chill-llight-gray;
border-radius: 0.35rem;
padding: 1rem;
}
& > div.warnings .alert {
margin-bottom: 0;
}
blockquote.chill-user-quote {
margin: -1.2em;
p {
margin: 0;
}
}
}
}

View File

@ -21,104 +21,134 @@
{% block content %} {% block content %}
<div class="accompanyingcourse-resume"> <div class="accompanyingcourse-resume">
<div id="dashboards" class="row" data-masonry='{"percentPosition": true }'> <div id="dashboards" class="row g-3" data-masonry='{"percentPosition": true }'>
{% if 'DRAFT' == accompanyingCourse.step %} {% if 'DRAFT' == accompanyingCourse.step %}
<div class="col-4 warnings mb-4"> <div class="mbloc col col-sm-6 col-lg-4">
{% include '@ChillPerson/AccompanyingCourse/_still_draft.html.twig' %} <div class="warnings">
{% include '@ChillPerson/AccompanyingCourse/_still_draft.html.twig' %}
</div>
</div> </div>
{% endif %} {% endif %}
{% if 'DRAFT' != accompanyingCourse.step %} {% if 'DRAFT' != accompanyingCourse.step %}
{% if withoutHousehold|length > 0 %} {% if withoutHousehold|length > 0 %}
<div class="col-4 warnings mb-4"> <div class="mbloc col col-sm-6 col-lg-4">
{% include '@ChillPerson/AccompanyingCourse/_join_household.html.twig' %} <div class="warnings">
{% include '@ChillPerson/AccompanyingCourse/_join_household.html.twig' %}
</div>
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if accompanyingCourse.locationStatus == 'address' {% if accompanyingCourse.locationStatus == 'address'
or accompanyingCourse.locationStatus == 'none' %} or accompanyingCourse.locationStatus == 'none' %}
<div class="col-4 warnings mb-4"> <div class="mbloc col col-sm-6 col-lg-4">
{% include '@ChillPerson/AccompanyingCourse/_warning_address.html.twig' %} <div class="warnings">
{% include '@ChillPerson/AccompanyingCourse/_warning_address.html.twig' %}
</div>
</div> </div>
{% endif %} {% endif %}
<div class="col col-sm-6 col-lg-4 location mb-4"> <div class="mbloc col col-sm-6 col-lg-4">
{% if accompanyingCourse.locationStatus == 'person' %} <div class="location">
<h2>{{ 'This course is located by'|trans }}</h2> {% if accompanyingCourse.locationStatus == 'person' %}
<h4>{{ accompanyingCourse.personLocation|chill_entity_render_string }}</h4> <h2>{{ 'This course is located by'|trans }}</h2>
{% elseif accompanyingCourse.locationStatus == 'address' %} <h4>{{ accompanyingCourse.personLocation|chill_entity_render_string }}</h4>
<h4>{{ 'This course has a temporarily location'|trans }}</h4> {% elseif accompanyingCourse.locationStatus == 'address' %}
{% endif %} <h4>{{ 'This course has a temporarily location'|trans }}</h4>
{% endif %}
{% if accompanyingCourse.locationStatus != 'none' %} {% if accompanyingCourse.locationStatus != 'none' %}
{{ accompanyingCourse.location|chill_entity_render_box }} {{ accompanyingCourse.location|chill_entity_render_box }}
{% endif %} {% endif %}
</div>
</div> </div>
{% if accompanyingCourse.participations is not empty %}
<div class="col col-sm-6 col-lg-4 persons mb-4">
<h4 class="item-key">{{ 'Persons associated'|trans }}</h4>
{% for r in accompanyingCourse.participations %}
{{ _self.insert_onthefly('person', r.person) }}
{% endfor %}
</div>
{% endif %}
{% if accompanyingCourse.resources is not empty %}
<div class="col col-sm-6 col-lg-4 resources mb-4">
<h4 class="item-key">{{ 'Resources'|trans }}</h4>
{% for r in accompanyingCourse.resources %}
{% if r.person is not null %}
{{ _self.insert_onthefly('person', r.person) }}
{% elseif r.thirdParty is not null %}
{{ _self.insert_onthefly('thirdparty', r.thirdParty) }}
{% endif %}
{% endfor %}
</div>
{% endif %}
{% if accompanyingCourse.pinnedComment is not empty %} {% if accompanyingCourse.pinnedComment is not empty %}
<div class="col col-sm-6 col-lg-4 comment mb-4"> <div class="mbloc col col-sm-6 col-lg-8">
<h4 class="item-key">{{ 'Pinned comment'|trans }}</h4> <div class="comment">
<blockquote class="chill-user-quote"> <h4 class="item-key visually-hidden">{{ 'Pinned comment'|trans }}</h4>
{{ accompanyingCourse.pinnedComment.content }} <blockquote class="chill-user-quote">
<div class="metadata"> <i class="fa fa-flag float-end text-chill-gray" title="{{ 'pinned'|trans }}"></i>
{{ 'Last updated by'| trans }} {{ accompanyingCourse.pinnedComment.content|chill_markdown_to_html }}
<span class="user"> <div class="metadata">
{{ 'Last updated by'| trans }}
<span class="user">
{{ accompanyingCourse.pinnedComment.updatedBy|chill_entity_render_box }} {{ accompanyingCourse.pinnedComment.updatedBy|chill_entity_render_box }}
</span> </span>
{{ 'on'|trans ~ ' ' }} {{ 'on'|trans ~ ' ' }}
<span class="date"> <span class="date">
{{ accompanyingCourse.pinnedComment.updatedAt|format_datetime("medium", "short") }} {{ accompanyingCourse.pinnedComment.updatedAt|format_datetime("medium", "short") }}
</span> </span>
</div> </div>
</blockquote> </blockquote>
</div>
</div> </div>
{% endif %} {% endif %}
{% if accompanyingCourse.scopes is not empty %} {% if accompanyingCourse.participations is not empty %}
<div class="col col-sm-6 col-lg-4 scopes mb-4"> <div class="mbloc col col-sm-6 col-lg-4">
<h4 class="item-key">{{ 'Scopes'|trans }}</h4> <div class="persons">
<div> <h4 class="item-key">{{ 'Persons associated'|trans }}</h4>
{% for s in accompanyingCourse.scopes %} {% for r in accompanyingCourse.participations %}
<span>{{ s.name|localize_translatable_string|capitalize }}</span>{% if not loop.last %}, {% endif %} {{ _self.insert_onthefly('person', r.person) }}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if accompanyingCourse.requestorPerson is not null or accompanyingCourse.requestorThirdParty is not null %} {% if accompanyingCourse.resources is not empty %}
<div class="col col-sm-6 col-lg-4 requestor mb-4"> <div class="mbloc col col-sm-6 col-lg-4">
{% if accompanyingCourse.requestorPerson is not null %} <div class="resources">
<h4 class="item-key">{{ 'Requestor'|trans }}</h4> <h4 class="item-key">{{ 'Resources'|trans }}</h4>
{{ _self.insert_onthefly('person', accompanyingCourse.requestorPerson) }} {% for r in accompanyingCourse.resources %}
{% elseif accompanyingCourse.requestorThirdParty is not null %} {% if r.person is not null %}
<h4 class="item-key">{{ 'Requestor'|trans }}</h4> {{ _self.insert_onthefly('person', r.person) }}
{{ _self.insert_onthefly('thirdparty', accompanyingCourse.requestorThirdParty) }} {% elseif r.thirdParty is not null %}
{% endif %} {{ _self.insert_onthefly('thirdparty', r.thirdParty) }}
{% endif %}
{% endfor %}
</div>
</div> </div>
{% endif %} {% endif %}
{% if accompanyingCourse.scopes is not empty %}
<div class="mbloc col col-sm-6 col-lg-4">
<div class="scopes">
<h4 class="item-key">{{ 'Scopes'|trans }}</h4>
<div>
{% for s in accompanyingCourse.scopes %}
<span>{{ s.name|localize_translatable_string|capitalize }}</span>{% if not loop.last %}, {% endif %}
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if accompanyingCourse.requestorPerson is not null or accompanyingCourse.requestorThirdParty is not null %}
<div class="mbloc col col-sm-6 col-lg-4">
<div class="requestor">
{% if accompanyingCourse.requestorPerson is not null %}
<h4 class="item-key">{{ 'Requestor'|trans }}</h4>
{{ _self.insert_onthefly('person', accompanyingCourse.requestorPerson) }}
{% elseif accompanyingCourse.requestorThirdParty is not null %}
<h4 class="item-key">{{ 'Requestor'|trans }}</h4>
{{ _self.insert_onthefly('thirdparty', accompanyingCourse.requestorThirdParty) }}
{% endif %}
</div>
</div>
{% endif %}
{% if accompanyingCourse.origin is not empty %}
<div class="mbloc col col-sm-6 col-lg-4">
<div class="origin">
<h4 class="item-key">{{ 'Origin'|trans }}</h4>
{{ accompanyingCourse.origin.label|localize_translatable_string|capitalize }}
</div>
</div>
{% endif %}
</div> </div>
<div class="social-actions my-4"> <div class="social-actions my-4">

View File

@ -19,7 +19,7 @@ use Chill\PersonBundle\Entity\PersonAltName;
use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Repository\PersonRepository;
use DateTime; use DateTime;
use DateTimeImmutable; use DateTimeImmutable;
use LogicException; use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
@ -79,13 +79,24 @@ class PersonJsonNormalizer implements
$person = new Person(); $person = new Person();
} }
foreach (['firstName', 'lastName', 'phonenumber', 'mobilenumber', 'gender', $fields = [
'birthdate', 'deathdate', 'center', 'altNames', ] 'firstName',
as $item) { 'lastName',
if (!array_key_exists($item, $data)) { 'phonenumber',
continue; 'mobilenumber',
} 'gender',
'birthdate',
'deathdate',
'center',
'altNames',
];
$fields = array_filter(
$fields,
static fn (string $field): bool => array_key_exists($field, $data)
);
foreach ($fields as $item) {
switch ($item) { switch ($item) {
case 'firstName': case 'firstName':
$person->setFirstName($data[$item]); $person->setFirstName($data[$item]);
@ -149,9 +160,6 @@ class PersonJsonNormalizer implements
} }
break; break;
default:
throw new LogicException("item not defined: {$item}");
} }
} }
@ -194,14 +202,22 @@ class PersonJsonNormalizer implements
return $data instanceof Person && 'json' === $format; return $data instanceof Person && 'json' === $format;
} }
protected function normalizeAltNames($altNames): array /**
* @param Collection<array-key, PersonAltName> $altNames
*
* @return array<array-key, array<string, string>>
*/
protected function normalizeAltNames(Collection $altNames): array
{ {
$r = []; return $altNames
->map(
foreach ($altNames as $n) { static function (PersonAltName $personAltName): array {
$r[] = ['key' => $n->getKey(), 'label' => $n->getLabel()]; return [
} 'key' => $personAltName->getKey(),
'label' => $personAltName->getLabel(),
return $r; ];
}
)
->toArray();
} }
} }

View File

@ -215,6 +215,7 @@ Some peoples does not belong to any household currently. Add them to an househol
Add to household now: Ajouter à un ménage Add to household now: Ajouter à un ménage
Any resource for this accompanying course: Aucun interlocuteur privilégié pour ce parcours Any resource for this accompanying course: Aucun interlocuteur privilégié pour ce parcours
course.draft: Brouillon course.draft: Brouillon
Origin: Origine de la demande
# pickAPersonType # pickAPersonType
Pick a person: Choisir une personne Pick a person: Choisir une personne