Files
ephub/src/Entity/BookingSeries.php
T

170 lines
4.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Entity;
use App\Booking\RecurrenceRule;
use App\Booking\TimeRange;
use App\Repository\BookingSeriesRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* A repeating reservation.
*
* The series is a description, not a source of truth for availability: creating one writes real
* Booking rows for every occurrence up to endDate. That is what keeps the conflict check
* database-backed -- a rule that is only expanded at render time is invisible to every other
* booking attempt.
*/
#[ORM\Entity(repositoryClass: BookingSeriesRepository::class)]
class BookingSeries
{
/** A series may not run further out than this; desks are not booked years ahead. */
public const MAX_MONTHS = 12;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private User $user;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false, onDelete: 'RESTRICT')]
private Resource $resource;
#[ORM\Column(enumType: RecurrenceRule::class)]
private RecurrenceRule $rule;
#[ORM\Column(type: 'date_immutable')]
private \DateTimeImmutable $startDate;
#[ORM\Column(type: 'date_immutable')]
private \DateTimeImmutable $endDate;
#[ORM\Column]
private int $startMinute;
#[ORM\Column]
private int $endMinute;
#[ORM\Column(type: 'datetime_immutable')]
private \DateTimeImmutable $createdAt;
/** @var Collection<int, Booking> */
#[ORM\OneToMany(targetEntity: Booking::class, mappedBy: 'series')]
private Collection $bookings;
public function __construct(
User $user,
Resource $resource,
RecurrenceRule $rule,
\DateTimeImmutable $startDate,
\DateTimeImmutable $endDate,
TimeRange $time,
) {
$this->user = $user;
$this->resource = $resource;
$this->rule = $rule;
$this->startDate = $startDate->setTime(0, 0);
$this->endDate = $endDate->setTime(0, 0);
$this->startMinute = $time->startMinute;
$this->endMinute = $time->endMinute;
$this->createdAt = new \DateTimeImmutable();
$this->bookings = new ArrayCollection();
}
/**
* Every day this series falls on, in order.
*
* A rule is only a step size, so this is a stride from the start date. For any rule whose
* interval is a multiple of a week that means every occurrence lands on the same weekday as
* the start -- the series never has to be told which weekday it runs on.
*
* @return list<\DateTimeImmutable>
*/
public function occurrences(): array
{
$dates = [];
$step = \sprintf('+%d days', $this->rule->intervalDays());
for ($day = $this->startDate; $day <= $this->endDate; $day = $day->modify($step)) {
$dates[] = $day;
}
return $dates;
}
public function getId(): ?int
{
return $this->id;
}
public function getUser(): User
{
return $this->user;
}
public function getResource(): Resource
{
return $this->resource;
}
public function getRule(): RecurrenceRule
{
return $this->rule;
}
public function getStartDate(): \DateTimeImmutable
{
return $this->startDate;
}
public function getEndDate(): \DateTimeImmutable
{
return $this->endDate;
}
public function setEndDate(\DateTimeImmutable $endDate): void
{
$this->endDate = $endDate->setTime(0, 0);
}
public function getTimeRange(): TimeRange
{
return TimeRange::fromMinutes($this->startMinute, $this->endMinute);
}
public function getCreatedAt(): \DateTimeImmutable
{
return $this->createdAt;
}
/**
* @return Collection<int, Booking>
*/
public function getBookings(): Collection
{
return $this->bookings;
}
public function describe(): string
{
$description = $this->rule->label();
// Naming the weekday only says something for a rule that actually holds it fixed; for a
// daily series it would just repeat the start date back at the reader.
if ($this->rule->keepsWeekday()) {
$names = ['', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
$description .= ' ('.$names[(int) $this->startDate->format('N')].')';
}
return $description.', '.$this->getTimeRange();
}
}