602 lines
15 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\CalendarBundle\Entity;
use Chill\ActivityBundle\Entity\Activity;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use DateInterval;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
use LogicException;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use function in_array;
/**
* @ORM\Table(
* name="chill_calendar.calendar",
* uniqueConstraints={@ORM\UniqueConstraint(name="idx_calendar_remote", columns={"remoteId"}, options={"where": "remoteId <> ''"})}
* )
* @ORM\Entity
*/
class Calendar implements TrackCreationInterface, TrackUpdateInterface
{
use RemoteCalendarTrait;
use TrackCreationTrait;
use TrackUpdateTrait;
public const SMS_CANCEL_PENDING = 'sms_cancel_pending';
public const SMS_PENDING = 'sms_pending';
public const SMS_SENT = 'sms_sent';
public const STATUS_CANCELED = 'canceled';
public const STATUS_MOVED = 'moved';
public const STATUS_VALID = 'valid';
/**
* a list of invite which have been added during this session.
*
* @var array|Invite[]
*/
public array $newInvites = [];
/**
* a list of invite which have been removed during this session.
*
* @var array|Invite[]
*/
public array $oldInvites = [];
public ?CalendarRange $previousCalendarRange = null;
public ?User $previousMainUser = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod")
* @Serializer\Groups({"read"})
*/
private AccompanyingPeriod $accompanyingPeriod;
/**
* @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\Activity")
*/
private ?Activity $activity = null;
/**
* @ORM\OneToOne(targetEntity="CalendarRange", inversedBy="calendar")
* @Serializer\Groups({"calendar:read", "read"})
*/
private ?CalendarRange $calendarRange = null;
/**
* @ORM\ManyToOne(targetEntity="CancelReason")
*/
private ?CancelReason $cancelReason = null;
/**
* @ORM\Embedded(class=CommentEmbeddable::class, columnPrefix="comment_")
* @Serializer\Groups({"calendar:read", "read"})
*/
private CommentEmbeddable $comment;
/**
* @ORM\Column(type="datetime_immutable", nullable=false)
* @Serializer\Groups({"calendar:read", "read"})
* @Assert\NotNull(message="calendar.An end date is required")
*/
private ?DateTimeImmutable $endDate = null;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({"calendar:read", "read"})
*/
private ?int $id = null;
/**
* @ORM\OneToMany(
* targetEntity=Invite::class,
* mappedBy="calendar",
* orphanRemoval=true,
* cascade={"persist", "remove", "merge", "detach"}
* )
* @ORM\JoinTable(name="chill_calendar.calendar_to_invites")
* @Serializer\Groups({"read"})
*/
private Collection $invites;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
* @Serializer\Groups({"read"})
* @Assert\NotNull(message="calendar.A location is required")
*/
private ?Location $location = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User")
* @Serializer\Groups({"calendar:read", "read"})
* @Assert\NotNull(message="calendar.A main user is mandatory")
*/
private ?User $mainUser = null;
/**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person")
* @ORM\JoinTable(name="chill_calendar.calendar_to_persons")
* @Serializer\Groups({"calendar:read", "read"})
* @Assert\Count(min=1, minMessage="calendar.At least {{ limit }} person is required.")
*/
private Collection $persons;
/**
* @ORM\Embedded(class=PrivateCommentEmbeddable::class, columnPrefix="privateComment_")
* @Serializer\Groups({"calendar:read"})
*/
private PrivateCommentEmbeddable $privateComment;
/**
* @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty")
* @ORM\JoinTable(name="chill_calendar.calendar_to_thirdparties")
* @Serializer\Groups({"calendar:read", "read"})
*/
private Collection $professionals;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
private ?bool $sendSMS = false;
/**
* @ORM\Column(type="text", nullable=false, options={"default": Calendar::SMS_PENDING})
*/
private string $smsStatus = self::SMS_PENDING;
/**
* @ORM\Column(type="datetime_immutable", nullable=false)
* @Serializer\Groups({"calendar:read", "read"})
* @Assert\NotNull(message="calendar.A start date is required")
*/
private ?DateTimeImmutable $startDate = null;
/**
* @ORM\Column(type="string", length=255, nullable=false, options={"default": "valid"})
*/
private string $status = self::STATUS_VALID;
public function __construct()
{
$this->comment = new CommentEmbeddable();
$this->privateComment = new PrivateCommentEmbeddable();
$this->persons = new ArrayCollection();
$this->professionals = new ArrayCollection();
$this->invites = new ArrayCollection();
}
/**
* @internal Use {@link (Calendar::addUser)} instead
*/
public function addInvite(Invite $invite): self
{
if ($invite->getCalendar() instanceof Calendar && $invite->getCalendar() !== $this) {
throw new LogicException('Not allowed to move an invitation to another Calendar');
}
$this->invites[] = $invite;
$this->newInvites[] = $invite;
$invite->setCalendar($this);
return $this;
}
public function addPerson(Person $person): self
{
$this->persons[] = $person;
return $this;
}
public function addProfessional(ThirdParty $professional): self
{
$this->professionals[] = $professional;
return $this;
}
public function addUser(User $user): self
{
if (!$this->getUsers()->contains($user) && $this->getMainUser() !== $user) {
$this->addInvite((new Invite())->setUser($user));
}
return $this;
}
public function getAccompanyingPeriod(): ?AccompanyingPeriod
{
return $this->accompanyingPeriod;
}
public function getActivity(): ?Activity
{
return $this->activity;
}
public function getCalendarRange(): ?CalendarRange
{
return $this->calendarRange;
}
public function getCancelReason(): ?CancelReason
{
return $this->cancelReason;
}
public function getComment(): CommentEmbeddable
{
return $this->comment;
}
public function getDuration(): ?DateInterval
{
if ($this->getStartDate() === null || $this->getEndDate() === null) {
return null;
}
return $this->getStartDate()->diff($this->getEndDate());
}
public function getEndDate(): ?DateTimeImmutable
{
return $this->endDate;
}
public function getId(): ?int
{
return $this->id;
}
public function getInviteForUser(User $user): ?Invite
{
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('user', $user));
$matchings = $this->invites
->matching($criteria);
if (1 === $matchings->count()) {
return $matchings->first();
}
return null;
}
/**
* @return Collection|Invite[]
*/
public function getInvites(): Collection
{
return $this->invites;
}
public function getLocation(): ?Location
{
return $this->location;
}
public function getMainUser(): ?User
{
return $this->mainUser;
}
/**
* @return Collection|Person[]
*/
public function getPersons(): Collection
{
return $this->persons;
}
public function getPersonsAssociated(): array
{
if (null !== $this->accompanyingPeriod) {
$personsAssociated = [];
foreach ($this->accompanyingPeriod->getParticipations() as $participation) {
if ($this->persons->contains($participation->getPerson())) {
$personsAssociated[] = $participation->getPerson();
}
}
return $personsAssociated;
}
return [];
}
public function getPersonsNotAssociated(): array
{
if (null !== $this->accompanyingPeriod) {
$personsNotAssociated = [];
foreach ($this->persons as $person) {
if (!in_array($person, $this->getPersonsAssociated(), true)) {
$personsNotAssociated[] = $person;
}
}
return $personsNotAssociated;
}
return [];
}
public function getPrivateComment(): PrivateCommentEmbeddable
{
return $this->privateComment;
}
/**
* @return Collection|ThirdParty[]
*/
public function getProfessionals(): Collection
{
return $this->professionals;
}
public function getSendSMS(): ?bool
{
return $this->sendSMS;
}
public function getSmsStatus(): string
{
return $this->smsStatus;
}
public function getStartDate(): ?DateTimeImmutable
{
return $this->startDate;
}
public function getStatus(): ?string
{
return $this->status;
}
public function getThirdParties(): Collection
{
return $this->getProfessionals();
}
/**
* @return Collection|User[]
* @Serializer\Groups({"calendar:read", "read"})
*/
public function getUsers(): Collection
{
return $this->getInvites()->map(static function (Invite $i) { return $i->getUser(); });
}
public function hasCalendarRange(): bool
{
return null !== $this->calendarRange;
}
public function hasLocation(): bool
{
return null !== $this->getLocation();
}
/**
* return true if the user is invited.
*/
public function isInvited(User $user): bool
{
if ($this->getMainUser() === $user) {
return false;
}
return $this->getUsers()->contains($user);
}
public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
$metadata->addPropertyConstraint('startDate', new NotBlank());
$metadata->addPropertyConstraint('startDate', new Range([
'min' => '2 years ago',
'max' => '+ 2 years',
]));
$metadata->addPropertyConstraint('endDate', new NotBlank());
$metadata->addPropertyConstraint('endDate', new Range([
'min' => '2 years ago',
'max' => '+ 2 years',
]));
}
/**
* @internal Use {@link (Calendar::removeUser)} instead
*/
public function removeInvite(Invite $invite): self
{
if ($this->invites->removeElement($invite)) {
$invite->setCalendar(null);
$this->oldInvites[] = $invite;
}
return $this;
}
public function removePerson(Person $person): self
{
$this->persons->removeElement($person);
return $this;
}
public function removeProfessional(ThirdParty $professional): self
{
$this->professionals->removeElement($professional);
return $this;
}
public function removeUser(User $user): self
{
if (!$this->getUsers()->contains($user)) {
return $this;
}
$invite = $this->invites
->filter(static function (Invite $invite) use ($user) { return $invite->getUser() === $user; })
->first();
$this->removeInvite($invite);
return $this;
}
public function setAccompanyingPeriod(?AccompanyingPeriod $accompanyingPeriod): self
{
$this->accompanyingPeriod = $accompanyingPeriod;
return $this;
}
public function setActivity(?Activity $activity): self
{
$this->activity = $activity;
return $this;
}
public function setCalendarRange(?CalendarRange $calendarRange): self
{
if ($this->calendarRange !== $calendarRange) {
$this->previousCalendarRange = $this->calendarRange;
if (null !== $this->previousCalendarRange) {
$this->previousCalendarRange->setCalendar(null);
}
}
$this->calendarRange = $calendarRange;
if ($this->calendarRange instanceof CalendarRange) {
$this->calendarRange->setCalendar($this);
}
return $this;
}
public function setCancelReason(?CancelReason $cancelReason): self
{
$this->cancelReason = $cancelReason;
return $this;
}
public function setComment(CommentEmbeddable $comment): self
{
$this->comment = $comment;
return $this;
}
public function setEndDate(DateTimeImmutable $endDate): self
{
$this->endDate = $endDate;
return $this;
}
public function setLocation(?Location $location): Calendar
{
$this->location = $location;
return $this;
}
public function setMainUser(?User $mainUser): self
{
if ($this->mainUser !== $mainUser) {
$this->previousMainUser = $this->mainUser;
}
$this->mainUser = $mainUser;
$this->removeUser($mainUser);
return $this;
}
public function setPrivateComment(PrivateCommentEmbeddable $privateComment): self
{
$this->privateComment = $privateComment;
return $this;
}
public function setSendSMS(?bool $sendSMS): self
{
$this->sendSMS = $sendSMS;
return $this;
}
public function setSmsStatus(string $smsStatus): self
{
$this->smsStatus = $smsStatus;
return $this;
}
public function setStartDate(DateTimeImmutable $startDate): self
{
$this->startDate = $startDate;
return $this;
}
public function setStatus(string $status): self
{
$this->status = $status;
if (self::STATUS_CANCELED === $status && $this->getSmsStatus() === self::SMS_SENT) {
$this->setSmsStatus(self::SMS_CANCEL_PENDING);
}
return $this;
}
}