167 lines
4.4 KiB
PHP
167 lines
4.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Entity;
|
|
|
|
use App\Enum\RecurrenceRule;
|
|
use App\Model\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
|
|
{
|
|
#[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();
|
|
}
|
|
}
|