219 lines
7.5 KiB
PHP

<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\PersonBundle\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;
use Doctrine\Common\Collections\Criteria;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class MembersEditor
{
final public const VALIDATION_GROUP_AFFECTED = 'household_memberships';
final public const VALIDATION_GROUP_COMPOSITION = 'household_composition';
final public const VALIDATION_GROUP_CREATED = 'household_memberships_created';
private array $events = [];
private array $membershipsAffected = [];
private array $oldMembershipsHashes = [];
private array $persistables = [];
public function __construct(
private readonly ValidatorInterface $validator,
private readonly ?Household $household,
private readonly EventDispatcherInterface $eventDispatcher,
) {}
/**
* Add a person to the household.
*
* The person is added to the household associated with this editor's instance.
*
* If the person is also a member of another household, or the same household at the same position, the person
* is not associated any more with the previous household.
*/
public function addMovement(\DateTimeImmutable $date, Person $person, ?Position $position, ?bool $holder = false, ?string $comment = null): self
{
if (null === $this->household) {
throw new \LogicException('You must define a household first');
}
$membership = (new HouseholdMember())
->setStartDate($date)
->setPerson($person)
->setPosition($position)
->setHolder($holder)
->setComment($comment);
$this->household->addMember($membership);
if ($membership->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;
}
if ($participation->getStartDate() > $membership->getStartDate()) {
continue;
}
++$counter;
if (null === $participation->getEndDate() || $participation->getEndDate() > $date) {
$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
&& null === $participation->getEndDate() || $participation->getEndDate() > $membership->getStartDate()
&& $participation->getStartDate() <= $membership->getStartDate()
) {
$participation->setEndDate($membership->getStartDate());
}
}
} else {
// if there are multiple belongings not sharing household, close the others
foreach ($person->getHouseholdParticipations() as $participation) {
if ($participation === $membership) {
continue;
}
if ($participation->getHousehold() === $this->household
&& (null === $participation->getEndDate() || $participation->getEndDate() > $membership->getStartDate())
&& $participation->getStartDate() <= $membership->getStartDate()
) {
if ($participation->getShareHousehold()) {
// if a members is moved to the same household than the one he belongs to,
// we should make it leave the household
$this->leaveMovement($date, $person);
} else {
$participation->setEndDate($membership->getStartDate());
}
}
}
}
$this->membershipsAffected[] = $membership;
$this->persistables[] = $membership;
return $this;
}
public function getHousehold(): ?Household
{
return $this->household;
}
public function getPersistable(): array
{
return $this->persistables;
}
public function hasHousehold(): bool
{
return null !== $this->household;
}
/**
* Makes a person leave the household.
*
* Makes a person leave the household **associated with this editor**.
*
* @return $this
*/
public function leaveMovement(
\DateTimeImmutable $date,
Person $person,
): self {
$criteria = new Criteria();
$expr = Criteria::expr();
$criteria->where(
$expr->andX(
$expr->lte('startDate', $date),
$expr->isNull('endDate'),
$expr->eq('shareHousehold', true)
)
);
$participations = $person->getHouseholdParticipations()
->matching($criteria);
foreach ($participations as $participation) {
$this->events[] = $event = new PersonAddressMoveEvent($person);
$event->setPreviousMembership($participation);
$participation->setEndDate($date);
$this->membershipsAffected[] = $participation;
}
return $this;
}
public function postMove(): void
{
foreach ($this->events as $event) {
$this->eventDispatcher->dispatch($event);
}
}
public function validate(): ConstraintViolationListInterface
{
if ($this->hasHousehold()) {
$list = $this->validator
->validate($this->getHousehold(), null, [self::VALIDATION_GROUP_AFFECTED, self::VALIDATION_GROUP_COMPOSITION]);
} else {
$list = new ConstraintViolationList();
}
foreach ($this->membershipsAffected as $m) {
if (\in_array(\spl_object_hash($m), $this->oldMembershipsHashes, true)) {
$list->addAll($this->validator->validate($m, null, [self::VALIDATION_GROUP_AFFECTED]));
} else {
$list->addAll($this->validator->validate($m, null, [self::VALIDATION_GROUP_CREATED,
self::VALIDATION_GROUP_AFFECTED, ]));
}
}
return $list;
}
}