mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
618 lines
17 KiB
PHP
618 lines
17 KiB
PHP
<?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\PersonBundle\Entity\Household;
|
|
|
|
use ArrayIterator;
|
|
use Chill\MainBundle\Entity\Address;
|
|
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
|
use Chill\PersonBundle\Validator\Constraints\Household\MaxHolder;
|
|
use DateTime;
|
|
use DateTimeImmutable;
|
|
use DateTimeInterface;
|
|
use Doctrine\Common\Collections\ArrayCollection;
|
|
use Doctrine\Common\Collections\Collection;
|
|
use Doctrine\Common\Collections\Criteria;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
|
use Symfony\Component\Validator\Constraints as Assert;
|
|
|
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|
use function count;
|
|
|
|
/**
|
|
* @ORM\Entity
|
|
* @ORM\Table(
|
|
* name="chill_person_household"
|
|
* )
|
|
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
|
|
* "household": Household::class
|
|
* })
|
|
* @MaxHolder(groups={"household_memberships"})
|
|
*/
|
|
class Household
|
|
{
|
|
/**
|
|
* Addresses.
|
|
*
|
|
* @ORM\ManyToMany(
|
|
* targetEntity="Chill\MainBundle\Entity\Address",
|
|
* cascade={"persist", "remove", "merge", "detach"})
|
|
* @ORM\JoinTable(name="chill_person_household_to_addresses")
|
|
* @ORM\OrderBy({"validFrom": "DESC", "id": "DESC"})
|
|
* @Serializer\Groups({"write"})
|
|
*/
|
|
private Collection $addresses;
|
|
|
|
/**
|
|
* @ORM\Embedded(class=CommentEmbeddable::class, columnPrefix="comment_members_")
|
|
*/
|
|
private CommentEmbeddable $commentMembers;
|
|
|
|
/**
|
|
* @ORM\OneToMany(
|
|
* targetEntity=HouseholdComposition::class,
|
|
* mappedBy="household",
|
|
* orphanRemoval=true,
|
|
* cascade={"persist"}
|
|
* )
|
|
* @ORM\OrderBy({"startDate": "DESC"})
|
|
* @Assert\Valid(traverse=true, groups={"household_composition"})
|
|
*/
|
|
private Collection $compositions;
|
|
|
|
/**
|
|
* @ORM\Id
|
|
* @ORM\GeneratedValue
|
|
* @ORM\Column(type="integer")
|
|
* @Serializer\Groups({"read", "docgen:read"})
|
|
*/
|
|
private ?int $id = null;
|
|
|
|
/**
|
|
* @ORM\OneToMany(
|
|
* targetEntity=HouseholdMember::class,
|
|
* mappedBy="household"
|
|
* )
|
|
* @Serializer\Groups({"read", "docgen:read"})
|
|
*/
|
|
private Collection $members;
|
|
|
|
/**
|
|
* @ORM\Column(type="boolean", name="waiting_for_birth", options={"default": false})
|
|
* @Serializer\Groups({"docgen:read"})
|
|
*/
|
|
private bool $waitingForBirth = false;
|
|
|
|
/**
|
|
* @ORM\Column(type="date_immutable", name="waiting_for_birth_date", nullable=true, options={"default": null})
|
|
* @Serializer\Groups({"docgen:read"})
|
|
*/
|
|
private ?DateTimeImmutable $waitingForBirthDate = null;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->addresses = new ArrayCollection();
|
|
$this->members = new ArrayCollection();
|
|
$this->commentMembers = new CommentEmbeddable();
|
|
$this->compositions = new ArrayCollection();
|
|
}
|
|
|
|
public function addAddress(Address $address): self
|
|
{
|
|
if (!$this->addresses->contains($address)) {
|
|
$this->addresses[] = $address;
|
|
$this->makeAddressConsistent();
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function addComposition(HouseholdComposition $composition): self
|
|
{
|
|
if (!$this->compositions->contains($composition)) {
|
|
$composition->setHousehold($this);
|
|
$this->compositions[] = $composition;
|
|
}
|
|
|
|
$this->householdCompositionConsistency();
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function addMember(HouseholdMember $member): self
|
|
{
|
|
if (!$this->members->contains($member)) {
|
|
$this->members[] = $member;
|
|
$member->setHousehold($this);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* By default, the addresses are ordered by date, descending (the most
|
|
* recent first).
|
|
*
|
|
* @Assert\Callback(methods={"validate"})
|
|
*
|
|
* @return \Chill\MainBundle\Entity\Address[]
|
|
*/
|
|
public function getAddresses()
|
|
{
|
|
return $this->addresses;
|
|
}
|
|
|
|
public function getAddressesOrdered(): array
|
|
{
|
|
$addresses = $this->getAddresses()->toArray();
|
|
usort($addresses, static function (Address $a, Address $b) {
|
|
$validFromA = $a->getValidFrom()->format('Y-m-d');
|
|
$validFromB = $b->getValidFrom()->format('Y-m-d');
|
|
|
|
if ($a === $b) {
|
|
if (null === $a->getId()) {
|
|
return 1;
|
|
}
|
|
|
|
if (null === $b->getId()) {
|
|
return -1;
|
|
}
|
|
|
|
return $a->getId() <=> $b->getId();
|
|
}
|
|
|
|
return $validFromA <=> $validFromB;
|
|
});
|
|
|
|
return $addresses;
|
|
}
|
|
|
|
public function getCommentMembers(): CommentEmbeddable
|
|
{
|
|
return $this->commentMembers;
|
|
}
|
|
|
|
/**
|
|
* @return ArrayCollection|Collection|HouseholdComposition[]
|
|
*/
|
|
public function getCompositions(): Collection
|
|
{
|
|
return $this->compositions;
|
|
}
|
|
|
|
/**
|
|
* @Serializer\Groups({"read", "docgen:read"})
|
|
* @Serializer\SerializedName("current_address")
|
|
*/
|
|
public function getCurrentAddress(?DateTime $at = null): ?Address
|
|
{
|
|
$at = null === $at ? new DateTime('today') : $at;
|
|
|
|
$addrs = $this->getAddresses()->filter(static function (Address $a) use ($at) {
|
|
return $a->getValidFrom() <= $at && (
|
|
null === $a->getValidTo() || $a->getValidTo() > $at
|
|
);
|
|
});
|
|
|
|
if ($addrs->count() > 0) {
|
|
return $addrs->first();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getCurrentComposition(?DateTimeImmutable $at = null): ?HouseholdComposition
|
|
{
|
|
$at ??= new DateTimeImmutable('today');
|
|
$criteria = new Criteria();
|
|
$expr = Criteria::expr();
|
|
|
|
$criteria->where(
|
|
$expr->andX(
|
|
$expr->orX(
|
|
$expr->isNull('endDate'),
|
|
$expr->gt('endDate', $at)
|
|
),
|
|
$expr->lte('startDate', $at)
|
|
)
|
|
);
|
|
|
|
$compositions = $this->compositions->matching($criteria);
|
|
|
|
if ($compositions->count() > 0) {
|
|
return $compositions->first();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @Serializer\Groups({"docgen:read"})
|
|
*/
|
|
public function getCurrentMembers(?DateTimeImmutable $now = null): Collection
|
|
{
|
|
return $this->getMembers()->matching($this->buildCriteriaCurrentMembers($now));
|
|
}
|
|
|
|
public function getCurrentMembersByPosition(Position $position, ?DateTimeInterface $now = null)
|
|
{
|
|
$criteria = new Criteria();
|
|
$expr = Criteria::expr();
|
|
|
|
$criteria->where($expr->eq('position', $position));
|
|
|
|
return $this->getCurrentMembers($now)->matching($criteria);
|
|
}
|
|
|
|
/**
|
|
* get current members ids.
|
|
*
|
|
* Used in serialization
|
|
*
|
|
* @Serializer\Groups({"read"})
|
|
* @Serializer\SerializedName("current_members_id")
|
|
*/
|
|
public function getCurrentMembersIds(?DateTimeImmutable $now = null): Collection
|
|
{
|
|
return $this->getCurrentMembers($now)->map(
|
|
static fn (HouseholdMember $m) => $m->getId()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return HouseholdMember[]
|
|
*/
|
|
public function getCurrentMembersOrdered(?DateTimeImmutable $now = null): Collection
|
|
{
|
|
$members = $this->getCurrentMembers($now);
|
|
|
|
$members->getIterator()
|
|
->uasort(
|
|
static function (HouseholdMember $a, HouseholdMember $b) {
|
|
if ($a->getPosition() === null) {
|
|
if ($b->getPosition() === null) {
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
if ($b->getPosition() === null) {
|
|
return 1;
|
|
}
|
|
|
|
if ($a->getPosition()->getOrdering() < $b->getPosition()->getOrdering()) {
|
|
return -1;
|
|
}
|
|
|
|
if ($a->getPosition()->getOrdering() > $b->getPosition()->getOrdering()) {
|
|
return 1;
|
|
}
|
|
|
|
if ($a->isHolder() && !$b->isHolder()) {
|
|
return 1;
|
|
}
|
|
|
|
if (!$a->isHolder() && $b->isHolder()) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
);
|
|
|
|
return $members;
|
|
}
|
|
|
|
public function getCurrentMembersWithoutPosition(?DateTimeInterface $now = null)
|
|
{
|
|
$criteria = new Criteria();
|
|
$expr = Criteria::expr();
|
|
|
|
$criteria->where($expr->isNull('position'));
|
|
|
|
return $this->getCurrentMembers($now)->matching($criteria);
|
|
}
|
|
|
|
/**
|
|
* Get the persons currently associated to the household.
|
|
*
|
|
* Return a list of Person, instead of a list of HouseholdMembers
|
|
*
|
|
* @return Person[]
|
|
*/
|
|
public function getCurrentPersons(?DateTimeImmutable $now = null): Collection
|
|
{
|
|
return $this->getCurrentMembers($now)
|
|
->map(static function (HouseholdMember $m) {
|
|
return $m->getPerson();
|
|
});
|
|
}
|
|
|
|
public function getId(): ?int
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
/**
|
|
* @return Collection|HouseholdMember[]
|
|
*/
|
|
public function getMembers(): Collection
|
|
{
|
|
return $this->members;
|
|
}
|
|
|
|
public function getMembersDuringMembership(HouseholdMember $membership)
|
|
{
|
|
return $this->getMembersOnRange(
|
|
$membership->getStartDate(),
|
|
$membership->getEndDate()
|
|
)->filter(
|
|
static function (HouseholdMember $m) use ($membership) {
|
|
return $m->getPerson() !== $membership->getPerson();
|
|
}
|
|
);
|
|
}
|
|
|
|
public function getMembersHolder(): Collection
|
|
{
|
|
$criteria = new Criteria();
|
|
$expr = Criteria::expr();
|
|
|
|
$criteria->where(
|
|
$expr->eq('holder', true)
|
|
);
|
|
|
|
return $this->getMembers()->matching($criteria);
|
|
}
|
|
|
|
public function getMembersOnRange(DateTimeImmutable $from, ?DateTimeImmutable $to): Collection
|
|
{
|
|
return $this->getMembers()->filter(static function (HouseholdMember $m) use ($from, $to) {
|
|
if (null === $m->getEndDate() && null !== $to) {
|
|
return $m->getStartDate() <= $to;
|
|
}
|
|
|
|
if (null === $to) {
|
|
return $m->getStartDate() >= $from || null === $m->getEndDate();
|
|
}
|
|
|
|
if (null !== $m->getEndDate() && $m->getEndDate() < $from) {
|
|
return false;
|
|
}
|
|
|
|
if ($m->getStartDate() <= $to) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
|
|
public function getNonCurrentMembers(?DateTimeImmutable $now = null): Collection
|
|
{
|
|
$criteria = new Criteria();
|
|
$expr = Criteria::expr();
|
|
$date = null === $now ? (new DateTimeImmutable('today')) : $now;
|
|
|
|
$criteria
|
|
->where(
|
|
$expr->gt('startDate', $date)
|
|
)
|
|
->orWhere(
|
|
$expr->andX(
|
|
$expr->lte('endDate', $date),
|
|
$expr->neq('endDate', null)
|
|
)
|
|
);
|
|
|
|
return $this->getMembers()->matching($criteria);
|
|
}
|
|
|
|
public function getNonCurrentMembersByPosition(Position $position, ?DateTimeInterface $now = null)
|
|
{
|
|
$criteria = new Criteria();
|
|
$expr = Criteria::expr();
|
|
|
|
$criteria->where($expr->eq('position', $position));
|
|
|
|
return $this->getNonCurrentMembers($now)->matching($criteria);
|
|
}
|
|
|
|
public function getNonCurrentMembersWithoutPosition(?DateTimeInterface $now = null)
|
|
{
|
|
$criteria = new Criteria();
|
|
$expr = Criteria::expr();
|
|
|
|
$criteria->where($expr->isNull('position'));
|
|
|
|
return $this->getNonCurrentMembers($now)->matching($criteria);
|
|
}
|
|
|
|
public function getPreviousAddressOf(Address $address): ?Address
|
|
{
|
|
$iterator = new ArrayIterator($this->getAddressesOrdered());
|
|
$iterator->rewind();
|
|
|
|
while ($iterator->valid()) {
|
|
$current = $iterator->current();
|
|
$iterator->next();
|
|
|
|
if ($iterator->valid()) {
|
|
if ($iterator->current() === $address) {
|
|
return $current;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getWaitingForBirth(): bool
|
|
{
|
|
return $this->waitingForBirth;
|
|
}
|
|
|
|
public function getWaitingForBirthDate(): ?DateTimeImmutable
|
|
{
|
|
return $this->waitingForBirthDate;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
public function householdCompositionConsistency(): void
|
|
{
|
|
$compositionOrdered = $this->compositions->toArray();
|
|
|
|
usort(
|
|
$compositionOrdered,
|
|
static function (HouseholdComposition $a, HouseholdComposition $b) {
|
|
return $a->getStartDate() <=> $b->getStartDate();
|
|
}
|
|
);
|
|
|
|
$iterator = new ArrayIterator($compositionOrdered);
|
|
$iterator->rewind();
|
|
|
|
/** @var ?HouseholdComposition $previous */
|
|
$previous = null;
|
|
|
|
do {
|
|
/** @var ?HouseholdComposition $current */
|
|
$current = $iterator->current();
|
|
|
|
if (null !== $previous) {
|
|
if (null === $previous->getEndDate() || $previous->getEndDate() > $current->getStartDate()) {
|
|
$previous->setEndDate($current->getStartDate());
|
|
}
|
|
}
|
|
$previous = $current;
|
|
$iterator->next();
|
|
} while ($iterator->valid());
|
|
}
|
|
|
|
public function makeAddressConsistent(): void
|
|
{
|
|
$iterator = new ArrayIterator($this->getAddressesOrdered());
|
|
|
|
$iterator->rewind();
|
|
|
|
while ($iterator->valid()) {
|
|
$current = $iterator->current();
|
|
|
|
$iterator->next();
|
|
|
|
if ($iterator->valid()) {
|
|
$current->setValidTo($iterator->current()->getValidFrom());
|
|
}
|
|
}
|
|
}
|
|
|
|
public function removeAddress(Address $address)
|
|
{
|
|
$this->addresses->removeElement($address);
|
|
}
|
|
|
|
public function removeComposition(HouseholdComposition $composition): self
|
|
{
|
|
if ($this->compositions->removeElement($composition)) {
|
|
$composition->setHousehold(null);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function removeMember(HouseholdMember $member): self
|
|
{
|
|
if ($this->members->removeElement($member)) {
|
|
// set the owning side to null (unless already changed)
|
|
if ($member->getHousehold() === $this) {
|
|
$member->setHousehold(null);
|
|
}
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function setCommentMembers(CommentEmbeddable $commentMembers): self
|
|
{
|
|
$this->commentMembers = $commentMembers;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Force an address starting at the current day
|
|
* on the Household.
|
|
*
|
|
* This will force the startDate's address on today.
|
|
*
|
|
* Used on household creation.
|
|
*
|
|
* @Serializer\Groups({"create"})
|
|
*/
|
|
public function setForceAddress(Address $address)
|
|
{
|
|
$address->setValidFrom(new DateTime('today'));
|
|
$this->addAddress($address);
|
|
}
|
|
|
|
public function setWaitingForBirth(bool $waitingForBirth): self
|
|
{
|
|
$this->waitingForBirth = $waitingForBirth;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function setWaitingForBirthDate(?DateTimeImmutable $waitingForBirthDate): self
|
|
{
|
|
$this->waitingForBirthDate = $waitingForBirthDate;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function validate(ExecutionContextInterface $context, $payload)
|
|
{
|
|
$addresses = $this->getAddresses();
|
|
$cond = true;
|
|
|
|
for ($i = 0; count($addresses) - 1 > $i; ++$i) {
|
|
if ($addresses[$i]->getValidFrom() !== $addresses[$i + 1]->getValidTo()) {
|
|
$cond = false;
|
|
$context->buildViolation('The address are not sequentials. The validFrom date of one address should be equal to the validTo date of the previous address.')
|
|
->atPath('addresses')
|
|
->addViolation();
|
|
}
|
|
}
|
|
}
|
|
|
|
private function buildCriteriaCurrentMembers(?DateTimeImmutable $now = null): Criteria
|
|
{
|
|
$criteria = new Criteria();
|
|
$expr = Criteria::expr();
|
|
$date = null === $now ? (new DateTimeImmutable('today')) : $now;
|
|
|
|
$criteria
|
|
->where($expr->orX(
|
|
$expr->isNull('startDate'),
|
|
$expr->lte('startDate', $date)
|
|
))
|
|
->andWhere($expr->orX(
|
|
$expr->isNull('endDate'),
|
|
$expr->gt('endDate', $date)
|
|
));
|
|
|
|
return $criteria;
|
|
}
|
|
}
|