AccompanyingPeriod::class])] #[Assert\GroupSequenceProvider] #[ORM\Entity] #[ORM\Table(name: 'chill_person_accompanying_period')] #[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 { final public const INTENSITIES = [self::INTENSITY_OCCASIONAL, self::INTENSITY_REGULAR]; /** * Mark an accompanying period as "occasional". * * used in INTENSITY */ final public const INTENSITY_OCCASIONAL = 'occasional'; /** * Mark an accompanying period as "regular". * * used in INTENSITY */ final public const INTENSITY_REGULAR = 'regular'; /** * Mark an accompanying period as "closed". * * This means that the accompanying period **is** * closed by the creator */ final public const STEP_CLOSED = 'CLOSED'; /** * Mark an accompanying period as "confirmed". * * This means that the accompanying period **is** * confirmed by the creator */ final public const STEP_CONFIRMED = 'CONFIRMED'; /** * Mark an accompanying period as confirmed, but inactive. * * this means that the accompanying period **is** * confirmed, but no activity (Activity, AccompanyingPeriod, ...) * has been associated, or updated, within this accompanying period. */ final public const STEP_CONFIRMED_INACTIVE_SHORT = 'CONFIRMED_INACTIVE_SHORT'; /** * Mark an accompanying period as confirmed, but inactive. * * this means that the accompanying period **is** * confirmed, but no activity (Activity, AccompanyingPeriod, ...) * has been associated, or updated, within this accompanying period. */ final public const STEP_CONFIRMED_INACTIVE_LONG = 'CONFIRMED_INACTIVE_LONG'; /** * Mark an accompanying period as "draft". * * This means that the accompanying period is not yet * confirmed by the creator */ final public const STEP_DRAFT = 'DRAFT'; #[ORM\ManyToOne(targetEntity: Address::class)] private ?Address $addressLocation = null; #[Groups(['read', 'write'])] #[Assert\NotBlank(groups: [AccompanyingPeriod::STEP_CONFIRMED])] #[ORM\ManyToOne(targetEntity: Location::class)] private ?Location $administrativeLocation = null; /** * @var Collection&Selectable */ #[ORM\OneToMany(mappedBy: 'accompanyingPeriod', targetEntity: Calendar::class)] private Collection&Selectable $calendars; #[Groups(['read', 'write', 'docgen:read'])] #[Assert\NotBlank(groups: [AccompanyingPeriod::STEP_CLOSED])] #[Assert\GreaterThanOrEqual(propertyPath: 'openingDate', message: 'The closing date must be later than the date of creation', groups: [AccompanyingPeriod::STEP_CLOSED])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_MUTABLE, nullable: true)] private ?\DateTime $closingDate = null; #[Groups(['read', 'write'])] #[Assert\NotBlank(groups: [AccompanyingPeriod::STEP_CLOSED])] #[ORM\ManyToOne(targetEntity: ClosingMotive::class)] #[ORM\JoinColumn(nullable: true)] private ?ClosingMotive $closingMotive = null; /** * @var Collection */ #[Assert\NotBlank(groups: [AccompanyingPeriod::STEP_DRAFT])] #[ORM\OneToMany(mappedBy: 'accompanyingPeriod', targetEntity: Comment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] #[ORM\OrderBy(['createdAt' => Criteria::DESC, 'id' => 'DESC'])] private Collection $comments; #[Groups(['read', 'write', 'docgen:read'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])] private bool $confidential = false; #[Groups(['docgen:read'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_MUTABLE, nullable: true, options: ['default' => null])] private ?\DateTimeInterface $createdAt = null; #[Groups(['read', 'docgen:read'])] #[ORM\ManyToOne(targetEntity: User::class)] #[ORM\JoinColumn(nullable: true)] private ?User $createdBy = null; #[Groups(['read', 'write', 'docgen:read'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])] private bool $emergency = false; #[Groups(['read', 'docgen:read'])] #[ORM\Id] #[ORM\Column(name: 'id', type: \Doctrine\DBAL\Types\Types::INTEGER)] #[ORM\GeneratedValue(strategy: 'AUTO')] private ?int $id = null; #[Groups(['read'])] #[Assert\NotBlank(groups: [AccompanyingPeriod::STEP_CONFIRMED])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, nullable: true)] private ?string $intensity = self::INTENSITY_OCCASIONAL; #[Groups(['read', 'write'])] #[Assert\NotBlank(groups: [AccompanyingPeriod::STEP_CONFIRMED])] #[ORM\ManyToOne(targetEntity: UserJob::class)] private ?UserJob $job = null; /** * @var Collection */ #[ORM\OneToMany(mappedBy: 'period', targetEntity: AccompanyingPeriodLocationHistory::class, cascade: ['persist', 'remove'], orphanRemoval: true)] private Collection $locationHistories; #[Groups(['read', 'write', 'docgen:read'])] #[Assert\LessThan(value: 'tomorrow', groups: [AccompanyingPeriod::STEP_CONFIRMED])] #[Assert\LessThanOrEqual(propertyPath: 'closingDate', groups: [AccompanyingPeriod::STEP_CONFIRMED])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_MUTABLE)] private ?\DateTime $openingDate = null; #[Groups(['read', 'write'])] #[Assert\NotBlank(groups: [AccompanyingPeriod::STEP_CONFIRMED])] #[ORM\ManyToOne(targetEntity: Origin::class)] #[ORM\JoinColumn(nullable: true)] private ?Origin $origin = null; /** * @var Collection */ #[Groups(['read', 'docgen:read'])] #[ORM\OneToMany(mappedBy: 'accompanyingPeriod', targetEntity: AccompanyingPeriodParticipation::class, cascade: ['persist', 'refresh', 'remove', 'merge', 'detach'], orphanRemoval: true)] #[ParticipationOverlap(groups: [AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED])] private Collection $participations; #[ORM\ManyToOne(targetEntity: Person::class, inversedBy: 'periodLocatedOn')] private ?Person $personLocation = null; #[Groups(['read'])] #[ORM\ManyToOne(targetEntity: Comment::class, cascade: ['persist'])] #[ORM\JoinColumn(onDelete: 'SET NULL')] private ?Comment $pinnedComment = null; private bool $preventUserIsChangedNotification = false; #[Groups(['read', 'write'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)] private string $remark = ''; #[Groups(['read', 'write', 'docgen:read'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])] 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; /** * @var Collection */ #[Groups(['read', 'docgen:read'])] #[ORM\OneToMany(targetEntity: AccompanyingPeriod\Resource::class, mappedBy: 'accompanyingPeriod', cascade: ['persist', 'remove'], orphanRemoval: true)] #[ResourceDuplicateCheck(groups: [AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED, 'Default', 'default'])] private Collection $resources; /** * @var Collection */ #[Groups(['read', 'docgen:read'])] #[Assert\Count(min: 1, groups: [AccompanyingPeriod::STEP_CONFIRMED], minMessage: 'A course must be associated to at least one scope')] #[ORM\ManyToMany(targetEntity: Scope::class, cascade: [])] #[ORM\JoinTable(name: 'accompanying_periods_scopes', joinColumns: [new ORM\JoinColumn(name: 'accompanying_period_id', referencedColumnName: 'id')], inverseJoinColumns: [new ORM\JoinColumn(name: 'scope_id', referencedColumnName: 'id')])] private Collection $scopes; /** * @var Collection */ #[Groups(['read', 'docgen:read'])] #[Assert\Count(min: 1, groups: [AccompanyingPeriod::STEP_CONFIRMED], minMessage: 'A course must contains at least one social issue')] #[ORM\ManyToMany(targetEntity: SocialIssue::class)] #[ORM\JoinTable(name: 'chill_person_accompanying_period_social_issues')] private Collection $socialIssues; /** * @var AccompanyingPeriod::STEP_* */ #[Groups(['read'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 32, nullable: true)] private ?string $step = self::STEP_DRAFT; /** * @var Collection */ #[ORM\OneToMany(mappedBy: 'period', targetEntity: AccompanyingPeriodStepHistory::class, cascade: ['persist', 'remove'], orphanRemoval: true)] private Collection $stepHistories; #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_MUTABLE, nullable: true, options: ['default' => null])] private ?\DateTimeInterface $updatedAt = null; #[ORM\ManyToOne(targetEntity: User::class)] private ?User $updatedBy = null; #[Groups(['read', 'write', 'docgen:read'])] #[ORM\ManyToOne(targetEntity: User::class)] #[ORM\JoinColumn(nullable: true)] private ?User $user = null; /** * @var Collection */ #[ORM\OneToMany(targetEntity: UserHistory::class, mappedBy: 'accompanyingPeriod', orphanRemoval: true, cascade: ['persist', 'remove'])] 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; /** * @var Collection */ #[Assert\Valid(traverse: true)] #[ORM\OneToMany(mappedBy: 'accompanyingPeriod', targetEntity: AccompanyingPeriodWork::class)] private Collection $works; /** * AccompanyingPeriod constructor. * * @uses AccompanyingPeriod::setClosingDate() */ public function __construct(?\DateTime $dateOpening = null) { $this->calendars = new ArrayCollection(); // TODO we cannot add a dependency between AccompanyingPeriod and calendars $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 (self::STEP_DRAFT === $this->getStep()) { return $this; } if (!$this->locationHistories->contains($history)) { $this->locationHistories[] = $history; $history->setPeriod($this); } // ensure continuity of histories $criteria = new Criteria(); $criteria->orderBy(['startDate' => Order::Ascending, 'id' => Order::Ascending]); /** @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(AccompanyingPeriod\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 (true === $this->isOpen()) { 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'. */ public function closeParticipationFor(mixed $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; } public function getOpenWorks(): Collection { return $this->getWorks()->filter( static fn (AccompanyingPeriodWork $work): bool => null === $work->getEndDate() or $work->getEndDate() > new \DateTimeImmutable('today') ); } /** * 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 ReadableCollection<(int|string), Person> */ public function getAvailablePersonLocation(): ReadableCollection { return $this->getOpenParticipations() ->filter( static fn (AccompanyingPeriodParticipation $p): bool => $p->getPerson()->hasCurrentHouseholdAddress() ) ->map( static fn (AccompanyingPeriodParticipation $p): ?Person => $p->getPerson() ); } public function getCalendars(): Collection { return $this->calendars; } public function getCenter(): ?Center { if (0 === $this->getPersons()->count()) { return null; } return $this->getPersons()->first()->getCenter(); } public function getCenters(): ?iterable { $centers = []; foreach ($this->getPersons() as $person) { if ( null !== $person->getCenter() && !\array_key_exists(spl_object_hash($person->getCenter()), $centers) ) { $centers[spl_object_hash($person->getCenter())] = $person->getCenter(); } } return array_values($centers); } /** * Get closingDate. */ public function getClosingDate(): ?\DateTime { return $this->closingDate; } public function getClosingMotive(): ?ClosingMotive { return $this->closingMotive; } /** * @return ReadableCollection */ #[Groups(['read'])] public function getComments(): ReadableCollection { $pinnedComment = $this->pinnedComment; return $this ->comments ->filter( static fn (Comment $c): bool => $c !== $pinnedComment ) ; } public function getCreatedAt(): ?\DateTimeInterface { return $this->createdAt; } public function getCreatedBy(): ?User { return $this->createdBy; } #[Groups(['docgen:read'])] public function getCurrentParticipations(): ReadableCollection { return $this->getOpenParticipations(); } public function getGroupSequence() { if (self::STEP_DRAFT === $this->getStep()) { return [[self::STEP_DRAFT]]; } if (str_starts_with((string) $this->getStep(), 'CONFIRM')) { return [[self::STEP_DRAFT, self::STEP_CONFIRMED]]; } if (self::STEP_CLOSED === $this->getStep()) { 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 */ public function getLocationHistories(): Collection { return $this->locationHistories; } /** * Get where the location is. * * @return 'person'|'address'|'none' */ #[Groups(['read'])] public function getLocationStatus(): string { if ($this->getPersonLocation() instanceof Person) { return 'person'; } if ($this->getAddressLocation() instanceof Address) { return 'address'; } return 'none'; } public function getNextCalendarsForPerson(Person $person, $limit = 5): ReadableCollection { $today = new \DateTimeImmutable('today'); $criteria = Criteria::create(); $expr = Criteria::expr(); $criteria ->where( $expr->gte('startDate', $today), ) ->orderBy(['startDate' => 'ASC']); $criteriaByPerson = Criteria::create(); $criteriaByPerson ->where( $expr->memberOf('persons', $person) ) ->setMaxResults($limit); return $this->calendars->matching($criteria)->matching($criteriaByPerson); } /** * Get openingDate. */ 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 fn (AccompanyingPeriodParticipation $participation): bool => null === $participation->getEndDate() ); return $collection->count() > 0 ? $collection->first() : null; } /** * @return ReadableCollection<(int|string), AccompanyingPeriodParticipation> */ public function getOpenParticipations(): ReadableCollection { return $this ->getParticipations() ->filter( static fn (AccompanyingPeriodParticipation $participation): bool => null === $participation->getEndDate() ); } public function getOrigin(): ?Origin { return $this->origin; } /** * Get Participations Collection. * * @return Collection */ public function getParticipations(): Collection { return $this->participations; } /** * Get the participation containing a person. * * @return ReadableCollection<(int|string), AccompanyingPeriodParticipation> */ public function getParticipationsContainsPerson(Person $person): ReadableCollection { 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 */ 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; } #[Groups(['read'])] public function getRequestor(): Person|ThirdParty|null { 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; } /** * @return Collection */ 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 (null === $this->getClosingDate()) { 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->works->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) { $this->setPersonLocation(null); $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. * * @return AccompanyingPeriod */ public function setClosingDate(mixed $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. * * @return AccompanyingPeriod */ public function setOpeningDate(mixed $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) { $this->setAddressLocation(null); $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, array $context = []): 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, $context); } 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; } public function getUserHistories(): ReadableCollection { return $this->userHistories; } public function getCurrentUserHistory(): ?UserHistory { return $this->getUserHistories()->findFirst(fn (int $key, UserHistory $userHistory) => null === $userHistory->getEndDate()); } private function addStepHistory(AccompanyingPeriodStepHistory $stepHistory, array $context = []): self { if (!$this->stepHistories->contains($stepHistory)) { $this->stepHistories[] = $stepHistory; $stepHistory->setPeriod($this); if (($context['closing_motive'] ?? null) instanceof ClosingMotive) { $stepHistory->setClosingMotive($context['closing_motive']); } $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' => Order::Ascending, 'id' => Order::Ascending]); /** @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; } }