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

This commit is contained in:
Julien Fastré 2022-04-06 12:04:54 +02:00
commit 56c3f05ec9
26 changed files with 434 additions and 98 deletions

View File

@ -11,9 +11,15 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [parcours] Display of interlocuteurs changed to flex-table in parcours edit page to prevent cut-off of information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/535)
## Test releases
### continuous release in February and March
* Creation of PickCivilityType, and implementation in PersonType and ThirdpartyType
* [person] Accompanying course evaluation documents: disable the WOPI edit link if mimetype not supported and if no keyInfos * [person] Accompanying course evaluation documents: disable the WOPI edit link if mimetype not supported and if no keyInfos
(https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/585) (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/585)
* [activity] display error messages above the form in creating a new location (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/481) * [activity] display error messages above the form in creating a new location (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/481)
* [activity] show required field in activity edit/new by an asterix in the vuejs fields (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/494) * [activity] show required field in activity edit/new by an asterix in the vuejs fields (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/494)
* [ACL] fix allow to see the course, event if the scope'course does not contains the scope's user * [ACL] fix allow to see the course, event if the scope'course does not contains the scope's user
@ -77,8 +83,7 @@ and this project adheres to
* [parcours] Create document buttons made sticky (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/532) * [parcours] Create document buttons made sticky (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/532)
* [person] Trailing guillemet removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/530) * [person] Trailing guillemet removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/530)
* [notification] Display of social action within workflow notification set to display block (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/537) * [notification] Display of social action within workflow notification set to display block (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/537)
* [onthefly] trim trailing whitespace in email of person and thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/542)
## Test releases
### test release 2022-02-21 ### test release 2022-02-21
@ -106,8 +111,6 @@ and this project adheres to
* [bug]: fix confidential toggle of address in thirdpartyrenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/460) * [bug]: fix confidential toggle of address in thirdpartyrenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/460)
## Test releases
* Creation of PickCivilityType, and implementation in PersonType and ThirdpartyType
### test release 2022-02-14 ### test release 2022-02-14

View File

@ -460,8 +460,3 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillThirdPartyBundle/Search/ThirdPartySearch.php path: src/Bundle/ChillThirdPartyBundle/Search/ThirdPartySearch.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: src/Bundle/ChillThirdPartyBundle/Templating/Entity/ThirdPartyRender.php

View File

@ -23,7 +23,7 @@ use function count;
/** /**
* Helps to find a summary of the budget: the sum of resources and charges. * Helps to find a summary of the budget: the sum of resources and charges.
*/ */
class SummaryBudget class SummaryBudget implements SummaryBudgetInterface
{ {
private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, type FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, type FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type';
@ -52,26 +52,6 @@ class SummaryBudget
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
} }
public function getEmptyChargeArray(): array
{
$keys = $this->configRepository->getChargesKeys();
$labels = $this->chargeLabels;
return array_combine($keys, array_map(function ($i) use ($labels) {
return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i])];
}, $keys));
}
public function getEmptyResourceArray(): array
{
$keys = $this->configRepository->getResourcesKeys();
$labels = $this->resourcesLabels;
return array_combine($keys, array_map(function ($i) use ($labels) {
return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i])];
}, $keys));
}
public function getSummaryForHousehold(?Household $household): array public function getSummaryForHousehold(?Household $household): array
{ {
if (null === $household) { if (null === $household) {
@ -101,8 +81,15 @@ class SummaryBudget
]; ];
} }
public function getSummaryForPerson(Person $person): array public function getSummaryForPerson(?Person $person): array
{ {
if (null === $person) {
return [
'resources' => $this->getEmptyResourceArray(),
'charges' => $this->getEmptyChargeArray(),
];
}
$rsm = $this->buildRsm(); $rsm = $this->buildRsm();
$resources = $this->em->createNativeQuery(self::QUERY_RESOURCE_BY_PERSON, $rsm) $resources = $this->em->createNativeQuery(self::QUERY_RESOURCE_BY_PERSON, $rsm)
@ -128,6 +115,26 @@ class SummaryBudget
return $rsm; return $rsm;
} }
private function getEmptyChargeArray(): array
{
$keys = $this->configRepository->getChargesKeys();
$labels = $this->chargeLabels;
return array_combine($keys, array_map(function ($i) use ($labels) {
return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i])];
}, $keys));
}
private function getEmptyResourceArray(): array
{
$keys = $this->configRepository->getResourcesKeys();
$labels = $this->resourcesLabels;
return array_combine($keys, array_map(function ($i) use ($labels) {
return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i])];
}, $keys));
}
private function rowToArray(array $rows, string $kind): array private function rowToArray(array $rows, string $kind): array
{ {
switch ($kind) { switch ($kind) {

View File

@ -0,0 +1,25 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\BudgetBundle\Service\Summary;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person;
/**
* Helps to find a summary of the budget: the sum of resources and charges.
*/
interface SummaryBudgetInterface
{
public function getSummaryForHousehold(?Household $household): array;
public function getSummaryForPerson(?Person $person): array;
}

View File

@ -33,11 +33,11 @@ class ApiController extends AbstractCRUDController
* Base method for handling api action. * Base method for handling api action.
* *
* @param mixed $id * @param mixed $id
* @param mixed $_format * @param string $_format
* *
* @return void * @return void
*/ */
public function entityApi(Request $request, $id, $_format): Response public function entityApi(Request $request, $id, ?string $_format = 'json'): Response
{ {
switch ($request->getMethod()) { switch ($request->getMethod()) {
case Request::METHOD_GET: case Request::METHOD_GET:

View File

@ -45,7 +45,7 @@ class SearchUserApiProvider implements SearchApiInterface
$query $query
->setSelectKey('user') ->setSelectKey('user')
->setSelectJsonbMetadata("jsonb_build_object('id', u.id)") ->setSelectJsonbMetadata("jsonb_build_object('id', u.id)")
->setSelectPertinence('GREATEST(SIMILARITY(LOWER(UNACCENT(?)), u.label), ->setSelectPertinence('3 + GREATEST(SIMILARITY(LOWER(UNACCENT(?)), u.label),
SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical))', [$pattern, $pattern]) SIMILARITY(LOWER(UNACCENT(?)), u.usernamecanonical))', [$pattern, $pattern])
->setFromClause('users AS u') ->setFromClause('users AS u')
->setWhereClauses(' ->setWhereClauses('

View File

@ -44,13 +44,21 @@ class DateNormalizer implements ContextAwareNormalizerInterface, DenormalizerInt
switch ($type) { switch ($type) {
case DateTime::class: case DateTime::class:
return DateTime::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']); $result = DateTime::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']);
break;
case DateTimeInterface::class: case DateTimeInterface::class:
case DateTimeImmutable::class: case DateTimeImmutable::class:
return DateTimeImmutable::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']); $result = DateTimeImmutable::createFromFormat(DateTimeInterface::ISO8601, $data['datetime']);
break;
} }
if (false === $result) {
return null;
}
return $result;
throw new UnexpectedValueException(); throw new UnexpectedValueException();
} }

View File

@ -32,7 +32,7 @@ class PhonenumberNormalizer implements ContextAwareNormalizerInterface, Denormal
} }
/** /**
* @param mixed $data * @param string|null $data
* @param mixed $type * @param mixed $type
* @param null|mixed $format * @param null|mixed $format
* *
@ -40,7 +40,7 @@ class PhonenumberNormalizer implements ContextAwareNormalizerInterface, Denormal
*/ */
public function denormalize($data, $type, $format = null, array $context = []) public function denormalize($data, $type, $format = null, array $context = [])
{ {
if ('' === trim($data)) { if ('' === trim((string) $data)) {
return null; return null;
} }

View File

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

View File

@ -29,8 +29,12 @@ use Symfony\Contracts\Translation\TranslatorInterface;
final class PersonResourceType extends AbstractType final class PersonResourceType extends AbstractType
{ {
private PersonRenderInterface $personRender;
private ResourceKindRender $resourceKindRender; private ResourceKindRender $resourceKindRender;
private ThirdPartyRender $thirdPartyRender;
private TranslatorInterface $translator; private TranslatorInterface $translator;
public function __construct(ResourceKindRender $resourceKindRender, PersonRenderInterface $personRender, ThirdPartyRender $thirdPartyRender, 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'); throw new LogicException('You must define a household first');
} }
$event = new PersonAddressMoveEvent($person);
$membership = (new HouseholdMember()) $membership = (new HouseholdMember())
->setStartDate($date) ->setStartDate($date)
->setPerson($person) ->setPerson($person)
@ -70,9 +68,15 @@ class MembersEditor
->setHolder($holder) ->setHolder($holder)
->setComment($comment); ->setComment($comment);
$this->household->addMember($membership); $this->household->addMember($membership);
$event->setNextMembership($membership);
if ($position->getShareHousehold()) { 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) { foreach ($person->getHouseholdParticipationsShareHousehold() as $participation) {
if ($participation === $membership) { if ($participation === $membership) {
continue; continue;
@ -82,14 +86,25 @@ class MembersEditor
continue; continue;
} }
++$counter;
if ($participation->getEndDate() === null || $participation->getEndDate() > $date) { if ($participation->getEndDate() === null || $participation->getEndDate() > $date) {
$event->setPreviousMembership($participation);
$participation->setEndDate($date); $participation->setEndDate($date);
$this->membershipsAffected[] = $participation; $this->membershipsAffected[] = $participation;
$this->oldMembershipsHashes[] = spl_object_hash($participation); $this->oldMembershipsHashes[] = spl_object_hash($participation);
if ($participation->getHousehold() !== $this->household) {
$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) { foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) {
if ($participation->getHousehold() === $this->household if ($participation->getHousehold() === $this->household
&& $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate() && $participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate()
@ -98,11 +113,30 @@ class MembersEditor
$participation->setEndDate($membership->getStartDate()); $participation->setEndDate($membership->getStartDate());
} }
} }
} else {
// if a members is moved to the same household than the one he belongs to,
// we should make it leave the household
if ($person->getCurrentHousehold($date) === $this->household) {
$this->leaveMovement($date, $person);
}
// if there are multiple belongings not sharing household, close the others
foreach ($person->getHouseholdParticipationsNotShareHousehold() as $participation) {
if ($participation === $membership) {
continue;
}
if ($participation->getHousehold() === $this->household
&& ($participation->getEndDate() === null || $participation->getEndDate() > $membership->getStartDate())
&& $participation->getStartDate() <= $membership->getStartDate()
) {
$participation->setEndDate($membership->getStartDate());
}
}
} }
$this->membershipsAffected[] = $membership; $this->membershipsAffected[] = $membership;
$this->persistables[] = $membership; $this->persistables[] = $membership;
$this->events[] = $event;
return $this; return $this;
} }

View File

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

View File

@ -133,8 +133,8 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac
$pertinence = []; $pertinence = [];
$pertinenceArgs = []; $pertinenceArgs = [];
$orWhereSearchClause = []; $andWhereSearchClause = [];
$orWhereSearchClauseArgs = []; $andWhereSearchClauseArgs = [];
if ('' !== $default) { if ('' !== $default) {
foreach (explode(' ', $default) as $str) { foreach (explode(' ', $default) as $str) {
@ -145,15 +145,15 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac
'(starts_with(LOWER(UNACCENT(lastname)), UNACCENT(LOWER(?))))::int'; '(starts_with(LOWER(UNACCENT(lastname)), UNACCENT(LOWER(?))))::int';
array_push($pertinenceArgs, $str, $str, $str, $str); array_push($pertinenceArgs, $str, $str, $str, $str);
$orWhereSearchClause[] = $andWhereSearchClause[] =
'(LOWER(UNACCENT(?)) <<% person.fullnamecanonical OR ' . '(LOWER(UNACCENT(?)) <<% person.fullnamecanonical OR ' .
"person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%' )"; "person.fullnamecanonical LIKE '%' || LOWER(UNACCENT(?)) || '%' )";
array_push($orWhereSearchClauseArgs, $str, $str); array_push($andWhereSearchClauseArgs, $str, $str);
} }
$query->andWhereClause( $query->andWhereClause(
implode(' OR ', $orWhereSearchClause), implode(' AND ', $andWhereSearchClause),
$orWhereSearchClauseArgs $andWhereSearchClauseArgs
); );
} else { } else {
$pertinence = ['1']; $pertinence = ['1'];

View File

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

View File

@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Serializer\Normalizer; 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\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper;
use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Civility; use Chill\MainBundle\Entity\Civility;
@ -45,7 +45,7 @@ class PersonDocGenNormalizer implements
private RelationshipRepository $relationshipRepository; private RelationshipRepository $relationshipRepository;
private SummaryBudget $summaryBudget; private SummaryBudgetInterface $summaryBudget;
private TranslatableStringHelper $translatableStringHelper; private TranslatableStringHelper $translatableStringHelper;
@ -56,7 +56,7 @@ class PersonDocGenNormalizer implements
RelationshipRepository $relationshipRepository, RelationshipRepository $relationshipRepository,
TranslatorInterface $translator, TranslatorInterface $translator,
TranslatableStringHelper $translatableStringHelper, TranslatableStringHelper $translatableStringHelper,
SummaryBudget $summaryBudget SummaryBudgetInterface $summaryBudget
) { ) {
$this->personRender = $personRender; $this->personRender = $personRender;
$this->relationshipRepository = $relationshipRepository; $this->relationshipRepository = $relationshipRepository;
@ -214,6 +214,14 @@ class PersonDocGenNormalizer implements
$data['relations'] = []; $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; return $data;
} }
} }

View File

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

View File

@ -65,7 +65,7 @@ final class HouseholdApiControllerTest extends WebTestCase
} }
$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 - 1))
->setMaxResults(1) ->setMaxResults(1)
->getQuery()->getSingleResult(); ->getQuery()->getSingleResult();

View File

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

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Tests\Household; namespace Chill\PersonBundle\Tests\Household;
use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Household\Position; use Chill\PersonBundle\Entity\Household\Position;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent; use Chill\PersonBundle\Event\Person\PersonAddressMoveEvent;
@ -39,6 +40,122 @@ final class MembersEditorTest extends TestCase
$this->factory = $this->buildMembersEditorFactory(); $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() public function testMovePersonWithoutSharedHousehold()
{ {
$person = new Person(); $person = new Person();
@ -126,7 +243,7 @@ final class MembersEditorTest extends TestCase
$this->assertEquals($date, $membership1->getEndDate()); $this->assertEquals($date, $membership1->getEndDate());
} }
public function testPostMove() public function testPostMoveToAPositionNotSharingHousehold()
{ {
$person = new Person(); $person = new Person();
$position = (new Position()) $position = (new Position())
@ -134,6 +251,86 @@ final class MembersEditorTest extends TestCase
$household1 = new Household(); $household1 = new Household();
$household2 = new Household(); $household2 = new Household();
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class); $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 $eventDispatcher
->dispatch(Argument::type(PersonAddressMoveEvent::class)) ->dispatch(Argument::type(PersonAddressMoveEvent::class))
->shouldBeCalled(); ->shouldBeCalled();

View File

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

View File

@ -762,7 +762,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
*/ */
public function setEmail($email = null) public function setEmail($email = null)
{ {
$this->email = $email; $this->email = trim((string) $email);
return $this; return $this;
} }

View File

@ -34,6 +34,12 @@ final class ThirdPartyACLAwareRepository implements ThirdPartyACLAwareRepository
{ {
$qb = $this->thirdPartyRepository->createQueryBuilder('tp'); $qb = $this->thirdPartyRepository->createQueryBuilder('tp');
$qb->leftJoin('tp.parent', 'parent')
->andWhere($qb->expr()->andX(
'tp.active = \'TRUE\'',
$qb->expr()->orX($qb->expr()->isNull('parent'), 'parent.active = \'TRUE\'')
));
if (null !== $filterString) { if (null !== $filterString) {
$qb->andWhere($qb->expr()->like('tp.canonicalized', 'LOWER(UNACCENT(:filterString))')) $qb->andWhere($qb->expr()->like('tp.canonicalized', 'LOWER(UNACCENT(:filterString))'))
->setParameter('filterString', '%' . $filterString . '%'); ->setParameter('filterString', '%' . $filterString . '%');

View File

@ -109,14 +109,15 @@ class ThirdPartyApiSearch implements SearchApiInterface
} }
$query $query
->setSelectPertinence(implode(' + ', $pertinence), array_merge( ->setSelectPertinence(implode(' + ', $pertinence) . ' + 1', array_merge(
[], [],
...$pertinenceArgs ...$pertinenceArgs
)) ))
->andWhereClause(implode(' OR ', $wheres), array_merge( ->andWhereClause(implode(' AND ', $wheres)
[], . ' AND tparty.active IS TRUE and (parent.active IS TRUE OR parent IS NULL)', array_merge(
...$whereArgs [],
)); ...$whereArgs
));
return $query; return $query;
} }

View File

@ -63,7 +63,7 @@ class ThirdPartyNormalizer implements NormalizerAwareInterface, NormalizerInterf
}, $thirdParty->getTypesAndCategories()), }, $thirdParty->getTypesAndCategories()),
'profession' => $this->normalizer->normalize($thirdParty->getProfession(), $format, $context), 'profession' => $this->normalizer->normalize($thirdParty->getProfession(), $format, $context),
'address' => $this->normalizer->normalize($thirdParty->getAddress(), $format, ['address_rendering' => 'short']), 'address' => $this->normalizer->normalize($thirdParty->getAddress(), $format, ['address_rendering' => 'short']),
'telephone' => $this->normalizer->normalize($thirdParty->getTelephone()), 'telephone' => $this->normalizer->normalize($thirdParty->getTelephone(), $format, $context),
'email' => $thirdParty->getEmail(), 'email' => $thirdParty->getEmail(),
'isChild' => $thirdParty->isChild(), 'isChild' => $thirdParty->isChild(),
'parent' => $this->normalizer->normalize($thirdParty->getParent(), $format, $context), 'parent' => $this->normalizer->normalize($thirdParty->getParent(), $format, $context),

View File

@ -71,13 +71,13 @@ class ThirdPartyRender extends AbstractChillEntityRender
$civility = ''; $civility = '';
} }
if (!empty($entity->getAcronym())) { if ('' !== (string) $entity->getAcronym()) {
$acronym = ' (' . $entity->getAcronym() . ')'; $acronym = ' (' . $entity->getAcronym() . ')';
} else { } else {
$acronym = ''; $acronym = '';
} }
$firstname = empty($entity->getFirstname()) ? '' : $entity->getFirstname(); $firstname = ('' === $entity->getFirstname()) ? '' : $entity->getFirstname();
return $civility . $firstname . ' ' . $entity->getName() . $acronym; return $civility . $firstname . ' ' . $entity->getName() . $acronym;
} }