Julien Fastré 59e21b6819 Feature: [acp] record the step history of each accompanying period
Each time a step is changed on an history, a record is stored in a
dedicated table.

When the acp's opening date is moved, the first row is adapted to match the new opening's date. This
mechanisme does not work if the opening date is move beyon the first end
date (if any), nor on the closing date.
2022-10-14 14:36:40 +02:00

1474 lines
39 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\Entity;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\HasCentersInterface;
use Chill\MainBundle\Entity\HasScopesInterface;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodLocationHistory;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodStepHistory;
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\AccompanyingPeriod\UserHistory;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\AccompanyingPeriodValidity;
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ConfidentialCourseMustHaveReferrer;
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\LocationValidity;
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ParticipationOverlap;
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ResourceDuplicateCheck;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
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 Iterator;
use LogicException;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\GroupSequenceProviderInterface;
use UnexpectedValueException;
use function in_array;
use const SORT_REGULAR;
/**
* AccompanyingPeriod Class.
*
* @ORM\Entity
* @ORM\Table(name="chill_person_accompanying_period")
* @DiscriminatorMap(typeProperty="type", mapping={
* "accompanying_period": AccompanyingPeriod::class
* })
* @Assert\GroupSequenceProvider
* @AccompanyingPeriodValidity(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
* @LocationValidity(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
* @ConfidentialCourseMustHaveReferrer(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
*/
class AccompanyingPeriod implements
GroupSequenceProviderInterface,
HasCentersInterface,
HasScopesInterface,
TrackCreationInterface,
TrackUpdateInterface
{
public const INTENSITIES = [self::INTENSITY_OCCASIONAL, self::INTENSITY_REGULAR];
/**
* 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';
/**
* Mark an accompanying period as "closed".
*
* This means that the accompanying period **is**
* closed by the creator
*/
public const STEP_CLOSED = 'CLOSED';
/**
* Mark an accompanying period as "confirmed".
*
* This means that the accompanying period **is**
* confirmed by the creator
*/
public const STEP_CONFIRMED = 'CONFIRMED';
/**
* Mark an accompanying period as "draft".
*
* This means that the accompanying period is not yet
* confirmed by the creator
*/
public const STEP_DRAFT = 'DRAFT';
/**
* @ORM\ManyToOne(
* targetEntity=Address::class
* )
*/
private ?Address $addressLocation = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
* @Groups({"read", "write"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED})
*/
private ?Location $administrativeLocation = null;
/**
* @var DateTime
*
* @ORM\Column(type="date", nullable=true)
* @Groups({"read", "write", "docgen:read"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CLOSED})
* @Assert\GreaterThanOrEqual(
* propertyPath="openingDate",
* groups={AccompanyingPeriod::STEP_CLOSED},
* message="The closing date must be later than the date of creation"
* )
*/
private ?DateTime $closingDate = null;
/**
* @var AccompanyingPeriod\ClosingMotive
*
* @ORM\ManyToOne(
* targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive")
* @ORM\JoinColumn(nullable=true)
* @Groups({"read", "write"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CLOSED})
*/
private ?ClosingMotive $closingMotive = null;
/**
* @ORM\OneToMany(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Comment",
* mappedBy="accompanyingPeriod",
* cascade={"persist", "remove"},
* orphanRemoval=true
* )
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_DRAFT})
*/
private Collection $comments;
/**
* @ORM\Column(type="boolean", options={"default": false})
* @Groups({"read", "write", "docgen:read"})
*/
private bool $confidential = false;
/**
* @ORM\Column(type="datetime", nullable=true, options={"default": NULL})
* @Groups({"docgen:read"})
*/
private ?DateTimeInterface $createdAt = null;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=true)
* @Groups({"read", "docgen:read"})
*/
private ?User $createdBy = null;
/**
* @ORM\Column(type="boolean", options={"default": false})
* @Groups({"read", "write", "docgen:read"})
*/
private bool $emergency = false;
/**
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
* @Groups({"read", "docgen:read"})
*/
private ?int $id = null;
/**
* @var string
* @ORM\Column(type="string", nullable=true)
* @Groups({"read"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED})
*/
private $intensity = self::INTENSITY_OCCASIONAL;
/**
* @ORM\ManyToOne(
* targetEntity=UserJob::class
* )
* @Groups({"read", "write"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED})
*/
private ?UserJob $job = null;
/**
* @ORM\OneToMany(targetEntity=AccompanyingPeriodLocationHistory::class,
* mappedBy="period", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private Collection $locationHistories;
/**
* @var DateTime
*
* @ORM\Column(type="date")
* @Groups({"read", "write", "docgen:read"})
* @Assert\LessThan(value="tomorrow", groups={AccompanyingPeriod::STEP_CONFIRMED})
* @Assert\LessThanOrEqual(propertyPath="closingDate", groups={AccompanyingPeriod::STEP_CONFIRMED})
*/
private ?DateTime $openingDate = null;
/**
* @ORM\ManyToOne(targetEntity=Origin::class)
* @ORM\JoinColumn(nullable=true)
* @Groups({"read", "write"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED})
*/
private ?Origin $origin = null;
/**
* @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class,
* mappedBy="accompanyingPeriod", orphanRemoval=true,
* cascade={"persist", "refresh", "remove", "merge", "detach"})
* @Groups({"read", "docgen:read"})
* @ParticipationOverlap(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
*/
private Collection $participations;
/**
* @ORM\ManyToOne(
* targetEntity=Person::class,
* inversedBy="periodLocatedOn"
* )
*/
private ?Person $personLocation = null;
/**
* @ORM\ManyToOne(
* targetEntity=Comment::class,
* cascade={"persist"},
* )
* @Groups({"read"})
* @ORM\JoinColumn(onDelete="SET NULL")
*/
private ?Comment $pinnedComment = null;
private bool $preventUserIsChangedNotification = false;
/**
* @ORM\Column(type="text")
* @Groups({"read", "write"})
*/
private string $remark = '';
/**
* @ORM\Column(type="boolean", options={"default": false})
* @Groups({"read", "write", "docgen:read"})
*/
private bool $requestorAnonymous = false;
/**
* @ORM\ManyToOne(targetEntity=Person::class, inversedBy="accompanyingPeriodRequested")
* @ORM\JoinColumn(nullable=true)
*/
private ?Person $requestorPerson = null;
/**
* @ORM\ManyToOne(targetEntity=ThirdParty::class)
* @ORM\JoinColumn(nullable=true)
*/
private ?ThirdParty $requestorThirdParty = null;
/**
* @ORM\OneToMany(
* targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Resource",
* mappedBy="accompanyingPeriod",
* cascade={"persist", "remove"},
* orphanRemoval=true
* )
* @Groups({"read", "docgen:read"})
* @ResourceDuplicateCheck(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED, "Default", "default"})
*/
private Collection $resources;
/**
* @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", "docgen:read"})
* @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}, minMessage="A course must be associated to at least one scope")
*/
private Collection $scopes;
/**
* @ORM\ManyToMany(
* targetEntity=SocialIssue::class
* )
* @ORM\JoinTable(
* name="chill_person_accompanying_period_social_issues"
* )
* @Groups({"read", "docgen:read"})
* @Assert\Count(min=1, groups={AccompanyingPeriod::STEP_CONFIRMED}, minMessage="A course must contains at least one social issue")
*/
private Collection $socialIssues;
/**
* @ORM\Column(type="string", length=32, nullable=true)
* @Groups({"read"})
*/
private string $step = self::STEP_DRAFT;
/**
* @ORM\OneToMany(targetEntity=AccompanyingPeriodStepHistory::class,
* mappedBy="period", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private Collection $stepHistories;
/**
* @ORM\Column(type="datetime", nullable=true, options={"default": NULL})
*/
private ?DateTimeInterface $updatedAt = null;
/**
* @ORM\ManyToOne(
* targetEntity=User::class
* )
*/
private ?User $updatedBy = null;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=true)
* @Groups({"read", "write", "docgen:read"})
*/
private ?User $user = null;
/**
* @ORM\OneToMany(targetEntity=UserHistory::class, mappedBy="accompanyingPeriod", orphanRemoval=true,
* cascade={"persist", "remove"})
*
* @var Collection|UserHistory[]
*/
private Collection $userHistories;
private bool $userIsChanged = false;
/**
* Temporary field, which is filled when the user is changed.
*
* Used internally for listener when user change
*/
private ?User $userPrevious = null;
/**
* @ORM\OneToMany(
* targetEntity=AccompanyingPeriodWork::class,
* mappedBy="accompanyingPeriod"
* )
* @Assert\Valid(traverse=true)
*/
private Collection $works;
/**
* AccompanyingPeriod constructor.
*
* @param DateTime $dateOpening
*
* @uses AccompanyingPeriod::setClosingDate()
*/
public function __construct(?DateTime $dateOpening = null)
{
$this->participations = new ArrayCollection();
$this->scopes = new ArrayCollection();
$this->socialIssues = new ArrayCollection();
$this->comments = new ArrayCollection();
$this->works = new ArrayCollection();
$this->resources = new ArrayCollection();
$this->userHistories = new ArrayCollection();
$this->locationHistories = new ArrayCollection();
$this->stepHistories = new ArrayCollection();
$this->setOpeningDate($dateOpening ?? new DateTime('now'));
}
/**
* 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 ($p->getPerson()->getCurrentHousehold() === $household) {
$members[] = array_shift($participations);
} else {
$participations[] = array_shift($participations);
}
}
$array[] = ['household' => $household, 'members' => $members];
}
return $array;
}
public function addComment(Comment $comment): self
{
$this->comments[] = $comment;
$comment->setAccompanyingPeriod($this);
return $this;
}
public function addLocationHistory(AccompanyingPeriodLocationHistory $history): self
{
if ($this->getStep() === self::STEP_DRAFT) {
return $this;
}
if (!$this->locationHistories->contains($history)) {
$this->locationHistories[] = $history;
$history->setPeriod($this);
}
// ensure continuity of histories
$criteria = new Criteria();
$criteria->orderBy(['startDate' => Criteria::ASC, 'id' => Criteria::ASC]);
/** @var Iterator $locations */
$locations = $this->getLocationHistories()->matching($criteria)->getIterator();
$locations->rewind();
do {
/** @var AccompanyingPeriodLocationHistory $current */
$current = $locations->current();
$locations->next();
if ($locations->valid()) {
$next = $locations->current();
$current->setEndDate($next->getStartDate());
}
} while ($locations->valid());
return $this;
}
public function addPerson(?Person $person = null): self
{
if (null !== $person) {
$this->createParticipationFor($person);
}
return $this;
}
public function addResource(Resource $resource): self
{
$resource->setAccompanyingPeriod($this);
$this->resources[] = $resource;
return $this;
}
public function addScope(Scope $scope): self
{
if (!$this->scopes->contains($scope)) {
$this->scopes[] = $scope;
}
return $this;
}
public function addSocialIssue(SocialIssue $socialIssue): self
{
if (!$this->socialIssues->contains($socialIssue)) {
$this->socialIssues[] = $socialIssue;
}
return $this;
}
public function addWork(AccompanyingPeriodWork $work): self
{
$this->works[] = $work;
$work->setAccompanyingPeriod($this);
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;
}
/**
* Close a participation for a person.
*
* Search for the person's participation and set the end date at
* 'now'.
*
* @param mixed $person
*/
public function closeParticipationFor($person): ?AccompanyingPeriodParticipation
{
$participation = $this->getOpenParticipationContainsPerson($person);
if ($participation instanceof AccompanyingPeriodParticipation) {
$participation->setEndDate(new DateTime('now'));
}
return $participation;
}
/**
* 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 getAddressLocation(): ?Address
{
return $this->addressLocation;
}
public function getAdministrativeLocation(): ?Location
{
return $this->administrativeLocation;
}
/**
* 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(
static fn (AccompanyingPeriodParticipation $p): bool => $p->getPerson()->hasCurrentHouseholdAddress()
)
->map(
static fn (AccompanyingPeriodParticipation $p): ?Person => $p->getPerson()
);
}
public function getCenter(): ?Center
{
if ($this->getPersons()->count() === 0) {
return null;
}
return $this->getPersons()->first()->getCenter();
}
public function getCenters(): ?iterable
{
foreach ($this->getPersons() as $person) {
if (
!in_array($person->getCenter(), $centers ?? [], true)
&& null !== $person->getCenter()
) {
$centers[] = $person->getCenter();
}
}
return $centers ?? null;
}
/**
* Get closingDate.
*
* @return DateTime
*/
public function getClosingDate(): ?DateTime
{
return $this->closingDate;
}
public function getClosingMotive(): ?ClosingMotive
{
return $this->closingMotive;
}
/**
* @Groups({"read"})
*/
public function getComments(): Collection
{
$pinnedComment = $this->pinnedComment;
return $this
->comments
->filter(
static fn (Comment $c): bool => $c !== $pinnedComment
);
}
public function getCreatedAt(): ?DateTime
{
return $this->createdAt;
}
public function getCreatedBy(): ?User
{
return $this->createdBy;
}
/**
* @Groups({"docgen:read"})
*/
public function getCurrentParticipations(): Collection
{
return $this->getOpenParticipations();
}
public function getGroupSequence()
{
if ($this->getStep() === self::STEP_DRAFT) {
return [[self::STEP_DRAFT]];
}
if ($this->getStep() === self::STEP_CONFIRMED) {
return [[self::STEP_DRAFT, self::STEP_CONFIRMED]];
}
if ($this->getStep() === self::STEP_CLOSED) {
return [[self::STEP_DRAFT, self::STEP_CONFIRMED, self::STEP_CLOSED]];
}
throw new LogicException('no validation group permitted with this step: ' . $this->getStep());
}
public function getId(): ?int
{
return $this->id;
}
public function getIntensity(): ?string
{
return $this->intensity;
}
public function getJob(): ?UserJob
{
return $this->job;
}
public function getLastLocationHistory(): ?AccompanyingPeriodLocationHistory
{
foreach ($this->getLocationHistories() as $locationHistory) {
if (null === $locationHistory->getEndDate()) {
return $locationHistory;
}
}
return null;
}
/**
* Get the location, taking precedence into account.
*
* @Groups({"read"})
*/
public function getLocation(?DateTimeImmutable $at = null): ?Address
{
if ($this->getPersonLocation() instanceof Person) {
return $this->getPersonLocation()->getCurrentPersonAddress();
}
return $this->getAddressLocation();
}
/**
* @return Collection|AccompanyingPeriodLocationHistory[]
*/
public function getLocationHistories(): Collection
{
return $this->locationHistories;
}
/**
* Get where the location is.
*
* @Groups({"read"})
*/
public function getLocationStatus(): string
{
if ($this->getPersonLocation() instanceof Person) {
return 'person';
}
if ($this->getAddressLocation() instanceof Address) {
return 'address';
}
return 'none';
}
/**
* Get openingDate.
*
* @return DateTime
*/
public function getOpeningDate(): ?DateTime
{
return $this->openingDate;
}
/**
* 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 getOrigin(): ?Origin
{
return $this->origin;
}
/**
* 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 fn (AccompanyingPeriodParticipation $participation): bool => $participation->getPerson() === $person
);
}
/**
* @Groups({"read"})
*/
public function getPersonLocation(): ?Person
{
return $this->personLocation;
}
/**
* Get a list of all persons which are participating to this course.
*
* @psalm-return Collection<int, Person>
*/
public function getPersons(): Collection
{
return $this
->participations
->map(
static fn (AccompanyingPeriodParticipation $participation): ?Person => $participation->getPerson()
);
}
/**
* @Groups({"read"})
*/
public function getPinnedComment(): ?Comment
{
return $this->pinnedComment;
}
public function getPreviousUser(): ?User
{
return $this->userPrevious;
}
/**
* @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;
}
/**
* @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;
}
public function getRemark(): string
{
return $this->remark;
}
/**
* @return Person|ThirdParty
* @Groups({"read"})
*/
public function getRequestor()
{
return $this->requestorPerson ?? $this->requestorThirdParty;
}
/**
* @return string 'person' if requestor is an instanceof @see{Person::class}, 'thirdparty' if this is an instanceof @see{ThirdParty::class}, or 'none'
*/
public function getRequestorKind(): string
{
if ($this->getRequestor() instanceof ThirdParty) {
return 'thirdparty';
}
if ($this->getRequestor() instanceof Person) {
return 'person';
}
return 'none';
}
public function getRequestorPerson(): ?Person
{
return $this->requestorPerson;
}
public function getRequestorThirdParty(): ?ThirdParty
{
return $this->requestorThirdParty;
}
public function getResources(): Collection
{
return $this->resources;
}
/**
* @return Collection|iterable
*/
public function getScopes(): Collection
{
return $this->scopes;
}
public function getSocialIssues(): Collection
{
return $this->socialIssues;
}
public function getStep(): ?string
{
return $this->step;
}
public function getStepHistories(): Collection
{
return $this->stepHistories;
}
public function getUser(): ?User
{
return $this->user;
}
/**
* @return AccompanyingPeriodWork[]
*/
public function getWorks(): Collection
{
return $this->works;
}
public function hasPreviousUser(): bool
{
return null !== $this->userPrevious;
}
public function hasUser(): bool
{
return null !== $this->user;
}
public function isChangedUser(): bool
{
return $this->userIsChanged && $this->user !== $this->userPrevious;
}
/**
* Returns true if the closing date is after the opening date.
*/
public function isClosingAfterOpening(): bool
{
if (null === $this->getClosingDate()) {
return false;
}
$diff = $this->getOpeningDate()->diff($this->getClosingDate());
if (0 === $diff->invert) {
return true;
}
return false;
}
public function isConfidential(): bool
{
return $this->confidential;
}
/**
* Validation functions.
*/
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();
}
}
public function isEmergency(): bool
{
return $this->emergency;
}
public function isOpen(): bool
{
if ($this->getOpeningDate() > new DateTimeImmutable('now')) {
return false;
}
if ($this->getClosingDate() === null) {
return true;
}
return false;
}
public function isPreventUserIsChangedNotification(): bool
{
return $this->preventUserIsChangedNotification;
}
public function isRequestorAnonymous(): bool
{
return $this->requestorAnonymous;
}
public function removeComment(Comment $comment): void
{
$comment->setAccompanyingPeriod(null);
$this->comments->removeElement($comment);
}
public function removeLocationHistory(AccompanyingPeriodLocationHistory $history): self
{
if ($this->locationHistories->removeElement($history)) {
$history->setPeriod(null);
}
return $this;
}
/**
* Remove Participation.
*/
public function removeParticipation(AccompanyingPeriodParticipation $participation)
{
$participation->setAccompanyingPeriod(null);
}
/**
* Remove Person.
*/
public function removePerson(Person $person): self
{
$this->closeParticipationFor($person);
return $this;
}
public function removeResource(Resource $resource): void
{
$resource->setAccompanyingPeriod(null);
$this->resources->removeElement($resource);
}
public function removeScope(Scope $scope): void
{
$this->scopes->removeElement($scope);
}
public function removeSocialIssue(SocialIssue $socialIssue): void
{
$this->socialIssues->removeElement($socialIssue);
}
public function removeWork(AccompanyingPeriodWork $work): self
{
$this->work->removeElement($work);
$work->setAccompanyingPeriod(null);
return $this;
}
public function reOpen(): void
{
$this->setClosingDate(null);
$this->setClosingMotive(null);
$this->setStep(AccompanyingPeriod::STEP_CONFIRMED);
}
public function resetPreviousUser(): self
{
$this->userPrevious = null;
$this->userIsChanged = false;
return $this;
}
/**
* @Groups({"write"})
*/
public function setAddressLocation(?Address $addressLocation = null): self
{
if ($this->addressLocation !== $addressLocation) {
$this->addressLocation = $addressLocation;
if (null !== $addressLocation) {
$locationHistory = new AccompanyingPeriodLocationHistory();
$locationHistory
->setStartDate(new DateTimeImmutable('now'))
->setAddressLocation($addressLocation);
$this->addLocationHistory($locationHistory);
}
}
return $this;
}
public function setAdministrativeLocation(?Location $administrativeLocation): AccompanyingPeriod
{
$this->administrativeLocation = $administrativeLocation;
return $this;
}
/**
* Set closingDate.
*
* For closing a Person file, you should use Person::setClosed instead.
*
* @param mixed $closingDate
*
* @return AccompanyingPeriod
*/
public function setClosingDate($closingDate)
{
$this->closingDate = $closingDate;
return $this;
}
public function setClosingMotive(?ClosingMotive $closingMotive = null): self
{
$this->closingMotive = $closingMotive;
return $this;
}
public function setConfidential(bool $confidential): self
{
$this->confidential = $confidential;
return $this;
}
public function setCreatedAt(DateTimeInterface $datetime): self
{
$this->createdAt = $datetime;
return $this;
}
public function setCreatedBy(User $createdBy): self
{
$this->createdBy = $createdBy;
return $this;
}
public function setEmergency(bool $emergency): self
{
$this->emergency = $emergency;
return $this;
}
public function setIntensity(string $intensity): self
{
$this->intensity = $intensity;
return $this;
}
public function setJob(?UserJob $job): self
{
$this->job = $job;
return $this;
}
/**
* Set openingDate.
*
* @param mixed $openingDate
*
* @return AccompanyingPeriod
*/
public function setOpeningDate($openingDate)
{
if ($this->openingDate !== $openingDate) {
$this->openingDate = $openingDate;
$this->ensureStepContinuity();
}
return $this;
}
public function setOrigin(Origin $origin): self
{
$this->origin = $origin;
return $this;
}
/**
* @Groups({"write"})
*/
public function setPersonLocation(?Person $person = null): self
{
if ($this->personLocation !== $person) {
$this->personLocation = $person;
if (null !== $person) {
$locationHistory = new AccompanyingPeriodLocationHistory();
$locationHistory
->setStartDate(new DateTimeImmutable('now'))
->setPersonLocation($person);
$this->addLocationHistory($locationHistory);
}
}
return $this;
}
/**
* @Groups({"write"})
*/
public function setPinnedComment(?Comment $comment = null): self
{
if (null !== $this->pinnedComment) {
$this->addComment($this->pinnedComment);
}
$this->pinnedComment = $comment;
return $this;
}
public function setRemark(?string $remark = null): self
{
$this->remark = (string) $remark;
return $this;
}
/**
* Set a requestor.
*
* The requestor is either an instance of ThirdParty, or an
* instance of Person
*
* @param $requestor Person|ThirdParty
* @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 setRequestorAnonymous(bool $requestorAnonymous): self
{
$this->requestorAnonymous = $requestorAnonymous;
return $this;
}
public function setStep(string $step): self
{
$previous = $this->step;
$this->step = $step;
if (self::STEP_DRAFT === $previous && self::STEP_DRAFT !== $step) {
$this->bootPeriod();
}
if (self::STEP_DRAFT !== $this->step && $previous !== $step) {
// we create a new history
$history = new AccompanyingPeriodStepHistory();
$history->setStep($this->step)->setStartDate(new DateTimeImmutable('now'));
$this->addStepHistory($history);
}
return $this;
}
public function setUpdatedAt(DateTimeInterface $datetime): self
{
$this->updatedAt = $datetime;
return $this;
}
public function setUpdatedBy(User $user): self
{
$this->updatedBy = $user;
return $this;
}
public function setUser(?User $user, bool $preventNotification = false): self
{
if ($this->user !== $user) {
$this->userPrevious = $this->user;
$this->userIsChanged = true;
$this->preventUserIsChangedNotification = $preventNotification;
foreach ($this->userHistories as $history) {
if (null === $history->getEndDate()) {
$history->setEndDate(new DateTimeImmutable('now'));
}
}
if (null !== $user) {
$this->userHistories->add(new UserHistory($this, $user));
}
}
$this->user = $user;
return $this;
}
private function addStepHistory(AccompanyingPeriodStepHistory $stepHistory): self
{
if (!$this->stepHistories->contains($stepHistory)) {
$this->stepHistories[] = $stepHistory;
$stepHistory->setPeriod($this);
$this->ensureStepContinuity();
}
return $this;
}
private function bootPeriod(): void
{
// first location history
$locationHistory = new AccompanyingPeriodLocationHistory();
$locationHistory
->setStartDate(new DateTimeImmutable('now'))
->setPersonLocation($this->getPersonLocation())
->setAddressLocation($this->getAddressLocation());
$this->addLocationHistory($locationHistory);
}
private function ensureStepContinuity(): void
{
// ensure continuity of histories
$criteria = new Criteria();
$criteria->orderBy(['startDate' => Criteria::ASC, 'id' => Criteria::ASC]);
/** @var Iterator $steps */
$steps = $this->getStepHistories()->matching($criteria)->getIterator();
$steps->rewind();
// we set the start date of the first step as the opening date, only if it is
// not greater than the end date
/** @var AccompanyingPeriodStepHistory $current */
$current = $steps->current();
if (null === $current) {
return;
}
if ($this->getOpeningDate()->format('Y-m-d') !== $current->getStartDate()->format('Y-m-d')
&& ($this->getOpeningDate() <= $current->getEndDate() || null === $current->getEndDate())) {
$current->setStartDate(DateTimeImmutable::createFromMutable($this->getOpeningDate()));
}
// then we set all the end date to the start date of the next one
do {
/** @var AccompanyingPeriodStepHistory $current */
$current = $steps->current();
$steps->next();
if ($steps->valid()) {
$next = $steps->current();
$current->setEndDate($next->getStartDate());
}
} while ($steps->valid());
}
private function setRequestorPerson(?Person $requestorPerson = null): self
{
$this->requestorPerson = $requestorPerson;
return $this;
}
private function setRequestorThirdParty(?ThirdParty $requestorThirdParty = null): self
{
$this->requestorThirdParty = $requestorThirdParty;
return $this;
}
}