, * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ namespace Chill\PersonBundle\Entity; use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Entity\HasCentersInterface; use Chill\MainBundle\Entity\HasScopesInterface; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Center; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\ThirdPartyBundle\Entity\ThirdParty; use DateTimeInterface; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Chill\MainBundle\Entity\User; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Validator\Constraints as Assert; /** * AccompanyingPeriod Class * * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period") * @DiscriminatorMap(typeProperty="type", mapping={ * "accompanying_period"=AccompanyingPeriod::class * }) */ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface, HasScopesInterface, HasCentersInterface { /** * Mark an accompanying period as "occasional" * * used in INTENSITY */ public const INTENSITY_OCCASIONAL = 'occasional'; /** * Mark an accompanying period as "regular" * * used in INTENSITY */ public const INTENSITY_REGULAR = 'regular'; public const INTENSITIES = [self::INTENSITY_OCCASIONAL, self::INTENSITY_REGULAR]; /** * Mark an accompanying period as "draft". * * This means that the accompanying period is not yet * confirmed by the creator */ public const STEP_DRAFT = 'DRAFT'; /** * Mark an accompanying period as "confirmed". * * This means that the accompanying period **is** * confirmed by the creator */ public const STEP_CONFIRMED = 'CONFIRMED'; /** * @var integer * * @ORM\Id * @ORM\Column(name="id", type="integer") * @ORM\GeneratedValue(strategy="AUTO") * @Groups({"read"}) */ private $id; /** * @var \DateTime * * @ORM\Column(type="date") * @Groups({"read", "write"}) */ private $openingDate; /** * @var \DateTime * * @ORM\Column(type="date", nullable=true) * @Groups({"read", "write"}) */ private $closingDate = null; /** * @var string * * @ORM\Column(type="text") * @Groups({"read", "write"}) */ private $remark = ''; /** * @var Collection * * @ORM\OneToMany(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Comment", * mappedBy="accompanyingPeriod", * cascade={"persist", "remove"}, * orphanRemoval=true * ) */ private $comments; /** * @ORM\ManyToOne( * targetEntity=Comment::class * ) * @Groups({"read"}) */ private ?Comment $initialComment = null; /** * @var Collection * * @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class, * mappedBy="accompanyingPeriod", * cascade={"persist", "refresh", "remove", "merge", "detach"}) * @Groups({"read"}) */ private $participations; /** * @var AccompanyingPeriod\ClosingMotive * * @ORM\ManyToOne( * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive") * @ORM\JoinColumn(nullable=true) * @Groups({"read", "write"}) */ private $closingMotive = null; /** * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(nullable=true) * @Groups({"read", "write"}) */ private $user; /** * @ORM\ManyToOne(targetEntity=User::class) * @ORM\JoinColumn(nullable=true) * @Groups({"read"}) */ private $createdBy; /** * @var string * @ORM\Column(type="string", length=32, nullable=true) * @Groups({"read"}) */ private $step = self::STEP_DRAFT; /** * @ORM\ManyToOne(targetEntity=Origin::class) * @ORM\JoinColumn(nullable=true) * @Groups({"read", "write"}) */ private $origin; /** * @var string * @ORM\Column(type="string", nullable=true) * @Groups({"read", "write"}) */ private $intensity; /** * @var Collection * @ORM\ManyToMany( * targetEntity=Scope::class, * cascade={} * ) * @ORM\JoinTable( * name="accompanying_periods_scopes", * joinColumns={@ORM\JoinColumn(name="accompanying_period_id", referencedColumnName="id")}, * inverseJoinColumns={@ORM\JoinColumn(name="scope_id", referencedColumnName="id")} * ) * @Groups({"read"}) */ private $scopes; /** * @ORM\ManyToOne(targetEntity=Person::class, inversedBy="accompanyingPeriodRequested") * @ORM\JoinColumn(nullable=true) */ private $requestorPerson; /** * @ORM\ManyToOne(targetEntity=ThirdParty::class) * @ORM\JoinColumn(nullable=true) */ private $requestorThirdParty; /** * @var bool * @ORM\Column(type="boolean", options={"default": false} ) * @Groups({"read", "write"}) */ private $requestorAnonymous = false; /** * @var bool * @ORM\Column(type="boolean", options={"default": false} ) * @Groups({"read", "write"}) */ private $emergency = false; /** * @var bool * @ORM\Column(type="boolean", options={"default": false} ) * @Groups({"read", "write"}) */ private $confidential = false; /** * @var Collection * * @ORM\OneToMany( * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Resource", * mappedBy="accompanyingPeriod", * cascade={"persist", "remove"}, * orphanRemoval=true * ) * @Groups({"read"}) */ private $resources; /** * @ORM\ManyToMany( * targetEntity=SocialIssue::class * ) * @ORM\JoinTable( * name="chill_person_accompanying_period_social_issues" * ) * @Groups({"read"}) */ private Collection $socialIssues; /** * @ORM\Column(type="datetime", nullable=true, options={"default": NULL}) */ private \DateTimeInterface $createdAt; /** * @ORM\ManyToOne( * targetEntity=User::class * ) */ private User $updatedBy; /** * @ORM\Column(type="datetime", nullable=true, options={"default": NULL}) */ private \DateTimeInterface $updatedAt; /** * @ORM\OneToMany( * targetEntity=AccompanyingPeriodWork::class, * mappedBy="accompanyingPeriod" * ) * @Assert\Valid(traverse=true) */ private Collection $works; /** * @ORM\ManyToOne( * targetEntity=Person::class, * inversedBy="periodLocatedOn" * ) */ private ?Person $personLocation = null; /** * @ORM\ManyToOne( * targetEntity=Address::class * ) */ private ?Address $addressLocation = null; /** * AccompanyingPeriod constructor. * * @param \DateTime $dateOpening * @uses AccompanyingPeriod::setClosingDate() */ public function __construct(\DateTime $dateOpening = null) { $this->setOpeningDate($dateOpening ?? new \DateTime('now')); $this->participations = new ArrayCollection(); $this->scopes = new ArrayCollection(); $this->socialIssues = new ArrayCollection(); $this->comments = new ArrayCollection(); $this->works = new ArrayCollection(); } /** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Set openingDate * * @param \DateTime $dateOpening * @return AccompanyingPeriod */ public function setOpeningDate($openingDate) { $this->openingDate = $openingDate; return $this; } /** * Get openingDate * * @return \DateTime */ public function getOpeningDate() { return $this->openingDate; } /** * Set closingDate * * For closing a Person file, you should use Person::setClosed instead. * * @param \DateTime $dateClosing * @return AccompanyingPeriod * */ public function setClosingDate($closingDate) { $this->closingDate = $closingDate; return $this; } /** * Get closingDate * * @return \DateTime */ public function getClosingDate(): ?\DateTime { return $this->closingDate; } /** * @return boolean */ public function isOpen(): bool { if ($this->getOpeningDate() > new \DateTimeImmutable('now')) { return false; } if ($this->getClosingDate() === null) { return true; } return false; } public function setRemark(string $remark = null): self { $this->remark = (string) $remark; return $this; } public function getRemark(): string { return $this->remark; } /** * @Groups({"read"}) */ public function getComments(): Collection { return $this->comments->filter(function (Comment $c) { return $c !== $this->initialComment; }); } public function addComment(Comment $comment): self { $this->comments[] = $comment; $comment->setAccompanyingPeriod($this); return $this; } public function removeComment(Comment $comment): void { $comment->setAccompanyingPeriod(null); $this->comments->removeElement($comment); } /** * @Groups({"write"}) */ public function setInitialComment(?Comment $comment = null): self { if (NULL !== $this->initialComment) { $this->removeComment($this->initialComment); } if ($comment instanceof Comment) { $this->addComment($comment); } $this->initialComment = $comment; return $this; } /** * @Groups({"read"}) */ public function getInitialComment(): ?Comment { return $this->initialComment; } /** * Get Participations Collection */ public function getParticipations(): Collection { return $this->participations; } /** * Get the participation containing a person */ public function getParticipationsContainsPerson(Person $person): Collection { return $this ->getParticipations() ->filter( static function(AccompanyingPeriodParticipation $participation) use ($person): bool { return $person === $participation->getPerson(); } ); } /** * Get the opened participation containing a person * * "Open" means that the closed date is NULL */ public function getOpenParticipationContainsPerson(Person $person): ?AccompanyingPeriodParticipation { $collection = $this ->getParticipationsContainsPerson($person) ->filter( static function(AccompanyingPeriodParticipation $participation): bool { return null === $participation->getEndDate(); } ); return $collection->count() > 0 ? $collection->first() : NULL; } public function getOpenParticipations(): Collection { return $this ->getParticipations() ->filter( static function(AccompanyingPeriodParticipation $participation): bool { return null === $participation->getEndDate(); } ); } public function getCurrentParticipations(): Collection { return $this->getOpenParticipations(); } /** * Return an array with open participations sorted by household * [ * [ * "household" => Household x, * "members" => [ * Participation y , Participation z, ... * ] * ], * ] * */ public function actualParticipationsByHousehold(): array { $participations = $this->getOPenParticipations()->toArray(); $households = []; foreach ($participations as $p) { $households[] = $p->getPerson()->getCurrentHousehold(); } $households = array_unique($households, SORT_REGULAR); $array = []; foreach ($households as $household) { $members = []; foreach ($participations as $p) { if ($household === $p->getPerson()->getCurrentHousehold()) { $members[] = array_shift($participations); } else { $participations[] = array_shift($participations); } } $array[] = [ 'household' => $household, 'members' => $members ]; } return $array; } /** * Return true if the accompanying period contains a person. * * **Note**: this participation can be opened or not. */ public function containsPerson(Person $person): bool { return $this->getParticipationsContainsPerson($person)->count() > 0; } /** * Open a new participation for a person */ public function createParticipationFor(Person $person): AccompanyingPeriodParticipation { $participation = new AccompanyingPeriodParticipation($this, $person); $this->participations[] = $participation; return $participation; } public function addPerson(Person $person = null): self { if (NULL !== $person) { $this->createParticipationFor($person); } return $this; } /** * Close a participation for a person * * Search for the person's participation and set the end date at * 'now'. * * @return void */ public function closeParticipationFor($person): ?AccompanyingPeriodParticipation { $participation = $this->getOpenParticipationContainsPerson($person); if ($participation instanceof AccompanyingPeriodParticipation) { $participation->setEndDate(new \DateTimeImmutable('now')); } return $participation; } /** * Remove Person */ public function removePerson(Person $person): self { $this->closeParticipationFor($person); return $this; } public function getClosingMotive(): ?ClosingMotive { return $this->closingMotive; } public function setClosingMotive(ClosingMotive $closingMotive = null): self { $this->closingMotive = $closingMotive; return $this; } /** * If the period can be reopened. * * This function test if the period is closed and if the period is the last * for the given person */ public function canBeReOpened(Person $person): bool { if ($this->isOpen() === true) { return false; } $participation = $this->getOpenParticipationContainsPerson($person); if (null === $participation) { return false; } $periods = $participation->getPerson()->getAccompanyingPeriodsOrdered(); return end($periods) === $this; } /** */ public function reOpen(): void { $this->setClosingDate(null); $this->setClosingMotive(null); } /** * Validation function */ public function isDateConsistent(ExecutionContextInterface $context) { if ($this->isOpen()) { return; } if (! $this->isClosingAfterOpening()) { $context->buildViolation('The date of closing is before the date of opening') ->atPath('dateClosing') ->addViolation(); } } /** * Returns true if the closing date is after the opening date. * * @return boolean */ public function isClosingAfterOpening(): bool { if (null === $this->getClosingDate()) { return false; } $diff = $this->getOpeningDate()->diff($this->getClosingDate()); if ($diff->invert === 0) { return true; } return false; } function getUser(): ?User { return $this->user; } function setUser(User $user): self { $this->user = $user; return $this; } public function getOrigin(): ?Origin { return $this->origin; } public function setOrigin(Origin $origin): self { $this->origin = $origin; return $this; } public function getRequestorPerson(): ?Person { return $this->requestorPerson; } private function setRequestorPerson(Person $requestorPerson = null): self { $this->requestorPerson = $requestorPerson; return $this; } public function getRequestorThirdParty(): ?ThirdParty { return $this->requestorThirdParty; } private function setRequestorThirdParty(ThirdParty $requestorThirdParty = null): self { $this->requestorThirdParty = $requestorThirdParty; return $this; } /** * @return Person|ThirdParty * @Groups({"read"}) */ public function getRequestor() { return $this->requestorPerson ?? $this->requestorThirdParty; } /** * Set a requestor * * The requestor is either an instance of ThirdParty, or an * instance of Person * * @param $requestor Person|ThirdParty * @return self * @throw UnexpectedValueException if the requestor is not a Person or ThirdParty * @Groups({"write"}) */ public function setRequestor($requestor): self { if ($requestor instanceof Person) { $this->setRequestorThirdParty(NULL); $this->setRequestorPerson($requestor); } elseif ($requestor instanceof ThirdParty) { $this->setRequestorThirdParty($requestor); $this->setRequestorPerson(NULL); } elseif (NULL === $requestor) { $this->setRequestorPerson(NULL); $this->setRequestorThirdParty(NULL); } else { throw new \UnexpectedValueException("requestor is not an instance of Person or ThirdParty"); } return $this; } public function isRequestorAnonymous(): bool { return $this->requestorAnonymous; } public function setRequestorAnonymous(bool $requestorAnonymous): self { $this->requestorAnonymous = $requestorAnonymous; return $this; } public function isEmergency(): bool { return $this->emergency; } public function setEmergency(bool $emergency): self { $this->emergency = $emergency; return $this; } public function isConfidential(): bool { return $this->confidential; } public function setConfidential(bool $confidential): self { $this->confidential = $confidential; return $this; } public function getCreatedBy(): ?User { return $this->createdBy; } public function setCreatedBy(User $createdBy): self { $this->createdBy = $createdBy; return $this; } public function getStep(): ?string { return $this->step; } public function setStep(string $step): self { $this->step = $step; return $this; } public function getIntensity(): ?string { return $this->intensity; } public function setIntensity(string $intensity): self { $this->intensity = $intensity; return $this; } /** * @return iterable|Collection */ public function getScopes(): Collection { return $this->scopes; } public function addScope(Scope $scope): self { if (!$this->scopes->contains($scope)) { $this->scopes[] = $scope; } return $this; } public function removeScope(Scope $scope): void { $this->scopes->removeElement($scope); } public function getResources(): Collection { return $this->resources; } public function addResource(Resource $resource): self { $resource->setAccompanyingPeriod($this); $this->resources[] = $resource; return $this; } public function removeResource(Resource $resource): void { $resource->setAccompanyingPeriod(null); $this->resources->removeElement($resource); } public function getSocialIssues(): Collection { return $this->socialIssues; } public function addSocialIssue(SocialIssue $socialIssue): self { if (!$this->socialIssues->contains($socialIssue)) { $this->socialIssues[] = $socialIssue; } return $this; } public function removeSocialIssue(SocialIssue $socialIssue): void { $this->socialIssues->removeElement($socialIssue); } /** * @return Collection|SocialIssues[] All social issues and their descendants */ public function getRecursiveSocialIssues(): Collection { $recursiveSocialIssues = new ArrayCollection(); foreach( $this->socialIssues as $socialIssue) { foreach ($socialIssue->getDescendantsWithThis() as $descendant) { if(! $recursiveSocialIssues->contains($descendant)) { $recursiveSocialIssues->add($descendant); } } } return $recursiveSocialIssues; } /** * @return Collection|SocialAction[] All the descendant social actions of all * the descendants of the entity */ public function getRecursiveSocialActions(): Collection { $recursiveSocialActions = new ArrayCollection(); foreach( $this->socialIssues as $socialIssue) { foreach ($socialIssue->getRecursiveSocialActions() as $descendant) { if(! $recursiveSocialActions->contains($descendant)) { $recursiveSocialActions->add($descendant); } } } return $recursiveSocialActions; } /** * Get a list of all persons which are participating to this course * * @psalm-return Collection */ public function getPersons(): Collection { return $this ->participations ->map( static function(AccompanyingPeriodParticipation $participation): Person { return $participation->getPerson(); } ); } public function setCreatedAt(\DateTimeInterface $datetime): self { $this->createdAt = $datetime; return $this; } public function setUpdatedBy(User $user): self { $this->updatedBy = $user; return $this; } public function setUpdatedAt(\DateTimeInterface $datetime): self { $this->updatedAt = $datetime; return $this; } /** * @return AccompanyingPeriodWork[] */ public function getWorks(): Collection { return $this->works; } public function addWork(AccompanyingPeriodWork $work): self { $this->works[] = $work; $work->setAccompanyingPeriod($this); return $this; } public function removeWork(AccompanyingPeriodWork $work): self { $this->work->removeElement($work); $work->setAccompanyingPeriod(null); return $this; } public function getAddressLocation(): ?Address { return $this->addressLocation; } public function getCenter(): ?Center { if (count($this->getPersons()) === 0){ return null; } else { return $this->getPersons()->first()->getCenter(); } } /** * @Groups({"write"}) */ public function setAddressLocation(?Address $addressLocation = null): self { $this->addressLocation = $addressLocation; return $this; } /** * @Groups({"read"}) */ public function getPersonLocation(): ?Person { return $this->personLocation; } /** * Get a list of person which have an adresse available for a valid location * * @return Collection|Person[] */ public function getAvailablePersonLocation(): Collection { return $this->getOPenParticipations() ->filter(function(AccompanyingPeriodParticipation $p) { return $p->getPerson()->hasCurrentHouseholdAddress(); }) ->map(function(AccompanyingPeriodParticipation $p) { return $p->getPerson(); }); } /** * @Groups({"write"}) */ public function setPersonLocation(?Person $person = null): self { $this->personLocation = $person; return $this; } /** * Get the location, taking precedence into account * * @Groups({"read"}) */ public function getLocation(\DateTimeImmutable $at = null): ?Address { if ($this->getPersonLocation() instanceof Person) { return $this->getPersonLocation()->getCurrentHouseholdAddress($at); } return $this->getAddressLocation(); } /** * Get where the location is * * @Groups({"read"}) */ public function getLocationStatus(): string { if ($this->getPersonLocation() instanceof Person) { return 'person'; } elseif ($this->getAddressLocation() instanceof Address) { return 'address'; } else { return 'none'; } } public function getCenters(): ?iterable { foreach ($this->getPersons() as $person) { if (!in_array($person->getCenter(), $centers ?? []) && NULL !== $person->getCenter()) { $centers[] = $person->getCenter(); } } return $centers ?? null; } }