<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Fiken\Api\FikenEnv;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Expression;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
/**
* @ApiResource(
* attributes={"security"="is_granted('ROLE_USER')"},
* itemOperations = {
* "get" = {
* "normalization_context" = {"groups" = {
* "property:read", "property:item:get"
* }}
* },
* "put" = {
* "denormalization_context" = {"groups" = {
* "property:item:put"
* }}
* }
* },
* collectionOperations = {
* "get" = {
* "normalization_context" = {"groups" = {
* "property:read", "property:item:get"
* }}
* }
* }
* )
* @ApiFilter(OrderFilter::class, properties={"section": "desc"})
* @ORM\Entity(repositoryClass="App\Repository\PropertyRepository")
*/
class Property
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups({
* "property:read", "property:item:get",
* "booking:read", "booking:item:get"
* })
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
* @Groups({
* "property:read", "property:item:get",
* "booking:read", "booking:item:get"
* })
*/
private $section;
/**
* @ORM\Column(type="json")
* @Groups({
* "property:read", "property:item:get",
* "booking:read", "booking:item:get"
* })
*/
private $address = [];
/**
* @ORM\Column(type="text")
* @Groups({"property:read", "property:item:get"})
* @Groups({
* "property:read", "property:item:get",
* "booking:read", "booking:item:get"
* })
*/
private $entryDescription;
/**
* @ORM\Column(type="time")
* @Groups({
* "property:read", "property:item:get",
* "booking:read", "booking:item:get"
* })
*/
private $checkinTime;
/**
* @ORM\Column(type="time")
* @Groups({
* "property:read", "property:item:get",
* "booking:read", "booking:item:get"
* })
*/
private $checkoutTime;
/**
* @ORM\OneToMany(targetEntity="App\Entity\Booking", mappedBy="property", cascade={"persist"})
*/
private $bookings;
/**
* @ORM\Column(type="json")
* @Groups({
* "property:read", "property:item:get",
* })
*/
private $cal = [];
/**
* @ORM\Column(type="json", nullable=true)
*/
private $notes = [];
/**
* @ORM\Column(type="boolean")
*/
private $disabled = false;
/**
* @ORM\ManyToOne(targetEntity=User::class)
*/
private $responsible;
/**
* @ORM\Column(type="dateinterval", nullable=true)
*/
private $cleaning_duration;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $guests;
/**
* @ORM\Column(type="integer")
*/
private $double_bedrooms;
/**
* @ORM\Column(type="integer")
*/
private $single_rooms;
/**
* @ORM\Column(type="dateinterval")
*/
private $driving_duration;
/**
* @ORM\Column(type="datetime_immutable")
*/
private $created_at;
/**
* @ORM\OneToMany(targetEntity=Invoice::class, mappedBy="property", orphanRemoval=true)
*/
private $invoices;
/**
* @ORM\Column(type="float")
*/
private $base_fee_cleaning;
/**
* @ORM\Column(type="boolean")
*/
private $vat_registered;
/**
* @ORM\Column(type="smallint")
*/
private $host_share;
/**
* @ORM\ManyToOne(targetEntity=PropertyOwner::class, inversedBy="properties")
*/
private $owner;
/**
* @ORM\Column(type="bigint", nullable=true)
*/
private $projectId;
/**
* @ORM\Column(type="integer")
*/
private $importAttempt;
/**
* @ORM\ManyToMany(targetEntity=MqttDoorLock::class, inversedBy="properties")
*/
private $doorLocks;
/**
* @ORM\OneToMany(targetEntity=User::class, mappedBy="propertyUser")
*/
private $users;
/**
* @ORM\OneToMany(targetEntity=BookingProvider::class, mappedBy="property", cascade={"persist"})
*/
private $bookingProviders;
/**
* @ORM\OneToOne(targetEntity=PropertyAddress::class, mappedBy="property", cascade={"persist", "remove"})
*/
private $propertyAddress;
public function __construct() {
$this->bookings = new ArrayCollection();
$this->invoices = new ArrayCollection();
$this->doorLocks = new ArrayCollection();
$this->users = new ArrayCollection();
$this->bookingProviders = new ArrayCollection();
}
public function getId(): ?int {
return $this->id;
}
public function getAddress(): ?array {
return $this->address;
}
public function getStreetAddr(): ?string {
$addr = $this->getAddress() ? $this->getAddress()['address'] : "";
$addr = explode(',', $addr);
return trim($addr[0], ',');
}
public function getPostalCode(): ?string {
$addr = $this->getAddress() ? $this->getAddress()['address'] : "";
$addr = explode(',', $addr);
if (sizeof($addr) == 1)
return '9000';
return trim(explode(' ', trim($addr[1]))[0], ',');
}
public function getPostalCity(): ?string {
$addr = $this->getAddress() ? $this->getAddress()['address'] : "";
$addr = explode(',', $addr);
//dump($this, $addr);
if (sizeof($addr) == 1)
return 'Tromsø';
return trim(explode(" ", $addr[sizeof($addr)-1])[1]);
}
public function setAddress(array $address): self {
$this->address = $address;
return $this;
}
public function getEntryDescription(): ?string {
return $this->entryDescription;
}
public function setEntryDescription(string $entryDescription): self {
$this->entryDescription = $entryDescription;
return $this;
}
private static function diffTimeOfDay(\DateTimeInterface $now): ?\DateInterval {
return static::setTimeToMidnight($now)?->diff($now);
}
private static function setTimeToDiff(?\DateTimeInterface $dateTime, ?\DateInterval $diff): ?\DateTimeImmutable {
return $diff && $dateTime ? static::setTimeToMidnight($dateTime)?->add($diff) : $diff;
}
private static function setTimeToMidnight(?\DateTimeInterface $dateTime): ?\DateTimeImmutable {
return $dateTime ? \DateTimeImmutable::createFromInterface($dateTime)->setTime(...Booking::START_OF_DAY) : null;
}
public function getCheckinTime(): ?\DateTimeInterface {
return $this->checkinTime;
}
public function getTodaysCheckinTime(?\DateTimeInterface $dateTime = null): ?\DateTimeImmutable {
return static::setTimeToDiff($dateTime ??= $this->checkinTime, static::diffTimeOfDay($this->checkinTime));
}
public function setCheckinTime(\DateTimeImmutable $checkinTime): self {
$this->checkinTime = $checkinTime;
return $this;
}
public function getCheckoutTime(): ?\DateTimeInterface {
return $this->checkoutTime;
}
public function getTodaysCheckoutTime(?\DateTimeInterface $dateTime = null): ?\DateTimeImmutable {
return static::setTimeToDiff($dateTime ??= $this->checkoutTime, static::diffTimeOfDay($this->checkoutTime));
}
public function setCheckoutTime(\DateTimeImmutable $checkoutTime): self {
$this->checkoutTime = $checkoutTime;
return $this;
}
/**
* @return Collection|Booking[]
*/
public function getBookings(null|string|Expression ...$criterias): Collection {
return $this->selectBookings()->matching(self::getBookingsCriteria(...$criterias));
}
public function getBookingsCriteria(null|string|Expression ...$criterias): Criteria {
$criteria = Criteria::create();
$expressions = array_filter($criterias, fn(null|string|Expression $x): bool => $x instanceof Expression);
$dates = array_filter($criterias, fn(null|string|Expression $x): bool => is_string($x));
if (($dn = sizeof($dates)) > 2)
throw new \InvalidArgumentException(sprintf("To many string types for 'getBookings', max: 2, got: %d", $dn));
$types = ['checkin', 'checkout'];
$dates = sizeof($dates = array_map(fn($t): Expression => sizeof($res = array_map(
fn ($d, $id): Expression => Criteria::expr()->{($id % 2) ? 'lte' : 'gte'}($t,
(new \DateTimeImmutable($d))?->setTime(
...($id % 2 ? Booking::END_OF_DAY : Booking::START_OF_DAY)
)),
$dates, array_keys($dates)
)) > 1 ? Criteria::expr()->andX(...$res) : $res[0] ?? null,
array_splice($types, sizeof($types) - sizeof($dates), sizeof($dates)),
)) > 1 ? Criteria::expr()->orX(...$dates) : $dates[0] ?? null;
$where = array_filter([$dates, ...$expressions], fn($a) => $a != null);
if ($where)
$criteria->andWhere(Criteria::expr()->andX(...$where));
return $criteria->orderBy(['checkin' => 'ASC']);
}
public function getConflictingBookings(string $fromFormat = null): Collection {
return $this->getBookings($fromFormat,
Criteria::expr()->neq('blocking', 1),
Criteria::expr()->isNull('cancelledAt')
)->filter(
fn(Booking $b): bool => !$b->getConflictingBookings()->isEmpty()
);
}
public function getUncleanBookings(?string $dateFormat = null, Expression ...$criterias): Collection {
return $this->getBookings(
Criteria::expr()->neq('blocking', 1), Booking::cleanCriteria(false),
Criteria::expr()->lte('checkout', (new \DateTimeImmutable($dateFormat))->setTime(...Booking::END_OF_DAY)), ...$criterias
);
}
public function isClean(?string $dateFormat = null): bool {
return $this->getUncleanBookings($dateFormat)->isEmpty();
}
public function selectBookings(): Selectable {
return $this->bookings;
}
public function addBooking(Booking $booking): self {
if (!$this->bookings->contains($booking)) {
$this->bookings[] = $booking;
$booking->setProperty($this);
}
return $this;
}
public function removeBooking(Booking $booking): self {
if ($this->bookings->contains($booking)) {
$this->bookings->removeElement($booking);
// set the owning side to null (unless already changed)
if ($booking->getProperty() === $this) {
$booking->setProperty(null);
}
}
return $this;
}
public function getBookingProviderUrls(): ?array {
return $this->getBookingProviders()->map(fn(BookingProvider $bp): string => $bp->getCalendarUrl())->toArray();
}
public function getCal(): ?array {
return $this->getBookingProviderUrls();
}
public function setCal(array $cal): self {
$this->cal = $cal;
return $this;
}
public function getSection(): ?string {
return $this->section; // . ($this->disabled ? " (!! Contract ended, SKIP !!) " : "");
}
public function isDisabled(): bool {
return $this->disabled === true;
}
public function setSection(string $Section): self {
$this->section = $Section;
return $this;
}
/**
* @Groups({
* "booking:read", "booking:item:get", "booking:item:put",
* "property:read", "property:item:get"
* })
*/
public function getNotes(): ?array {
return $this->notes;
}
/**
* @Groups({"booking:item:put"})
*/
public function setNote(string $text): self {
$match = [];
preg_match('/^((?:[^\/]*\/)*[\d]+):(.+)$/', $text, $match);
if (count($match) == 3) {
$this->notes[$match[1]] = $match[2];
try {
$this->getBookings(
Criteria::expr()->eq('id', intval(substr($match[1], strrpos($match[1], '/')+1)))
)->first()?->setEmployeeNote($match[2]);
} catch(\Exception $e) {
// It didn't work obviusly
}
}
return $this;
}
/**
* @Groups({"booking:item:put"})
*/
public function setNotes(array $array): self {
$this->notes = sizeof($array) == 0 ? null : $array;
return $this;
}
public function getResponsible(): ?User {
return $this->responsible;
}
public function getStartCleaningBy(): ?\DateTimeImmutable {
return (
fn(\DateTimeImmutable $d): \DateTimeImmutable => $d->sub($this->getCleaningDuration())
)($this->getTodaysCheckinTime());
}
public function getCleaningDuration(): ?\DateInterval {
return $this->cleaning_duration ?? (new \DateInterval('PT0H'));
}
public function setCleaningDuration(?\DateInterval $cleaning_duration): self {
$this->cleaning_duration = $cleaning_duration;
return $this;
}
/**
* @SerializedName("cleaningDuration")
* @Groups({
* "property:read", "property:item:get",
* "booking:read", "booking:item:get"
* })
*/
public function getCleaningDurationMilliseconds(): ?int {
if (!$this->getCleaningDuration())
return null;
return self::durationToMilliseconds($this->getCleaningDuration());
}
public static function durationToMilliseconds(?\DateInterval $di): int {
if ($di == null)
return 0;
$ms = 1000;
return $di->d * (60 * 60 * 24 * $ms) +
$di->h * (60 * 60 * $ms) +
$di->i * (60 * $ms) +
$di->s * $ms;
}
/**
* @Groups({
* "booking:read", "booking:item:get"
* })
* */
public function getNumBedrooms(): ?int {
return $this->getDoubleBedrooms() + $this->getSingleRooms();
}
/**
* @SerializedName("maxGuests")
* @Groups({"booking:read", "booking:item:get"})
*/
public function getGuests(): int {
return $this->guests === null ? $this->getDoubleBedrooms() * 2 + $this->getSingleRooms() : $this->guests;
}
// public function setGuests(int $guests): self
// {
// $this->guests = $guests;
// return $this;
// }
public function getDoubleBedrooms(): int {
return $this->double_bedrooms;
}
public function setDoubleBedrooms(int $double_bedrooms): self {
$this->double_bedrooms = $double_bedrooms;
return $this;
}
public function getExtraBeds(): int {
return $this->getGuests() - ($this->getDoubleBedrooms() * 2 + $this->getSingleRooms());
}
public function getSingleRooms(): ?int {
return $this->single_rooms;
}
public function setSingleRooms(int $single_rooms): self {
$this->single_rooms = $single_rooms;
return $this;
}
/**
* @SerializedName("drivingDuration")
* @Groups({
* "property:read", "property:item:get",
* "booking:read", "booking:item:get"
* })
*/
public function getDrivingDurationMilliseconds(): ?int {
return self::durationToMilliseconds($this->getDrivingDuration());
}
public function getDrivingDuration(): ?\DateInterval {
return $this->driving_duration;
}
public function setDrivingDuration(\DateInterval $driving_duration): self {
$this->driving_duration = $driving_duration;
return $this;
}
public function getCreatedAt(): ?\DateTimeImmutable {
return $this->created_at;
}
public function setCreatedAt(\DateTimeImmutable $created_at): self {
$this->created_at = $created_at;
return $this;
}
/**
* @Groups({
* "property:read", "property:item:get"
* })
**/
public function hasInvoicableBookings(): bool {
return false;
}
/**
* @return Collection|Invoice[]
*/
public function getInvoices(): Collection {
return $this->invoices;
}
public function addInvoice(Invoice $invoice): self {
if (!$this->invoices->contains($invoice)) {
$this->invoices[] = $invoice;
$invoice->setProperty($this);
}
return $this;
}
public function removeInvoice(Invoice $invoice): self {
if ($this->invoices->removeElement($invoice)) {
// set the owning side to null (unless already changed)
if ($invoice->getProperty() === $this) {
$invoice->setProperty(null);
}
}
return $this;
}
/**
* @Groups({
* "booking:read"
* })
**/
public function getBaseFeeCleaning(): ?float {
return $this->base_fee_cleaning;
}
public function setBaseFeeCleaning(float $base_fee_cleaning): self {
$this->base_fee_cleaning = $base_fee_cleaning;
return $this;
}
/**
* @Groups({
* "booking:read"
* })
**/
public function getVatRegistered(): ?bool {
return $this->vat_registered;
}
public function setVatRegistered(bool $vat_registered): self {
$this->vat_registered = $vat_registered;
return $this;
}
/**
* @Groups({
* "booking:read"
* })
*/
public function getHostShare(): ?int {
return $this->host_share;
}
public function setHostShare(int $host_share): self {
$this->host_share = $host_share;
return $this;
}
public function getOwner(): ?PropertyOwner {
return $this->owner;
}
public function setOwner(?PropertyOwner $owner): self {
$this->owner = $owner;
return $this;
}
public function getProjectId(FikenEnv $fikenEnv = FikenEnv::DEBUG): ?int {
return $fikenEnv == FikenEnv::PROD ? $this->projectId : $this->getOwner()->getTestProjectId();
}
public function setProjectId(int $projectId): self {
$this->projectId = $projectId;
return $this;
}
/**
* @Groups({
* "booking:read"
* })
*/
public function isInvoiceReady(): ?bool {
return $this->owner != null && $this->projectId != null;
}
public function getImportAttempt(): ?int {
return $this->importAttempt;
}
public function setImportAttempt(int $importAttempt): self {
$this->importAttempt = $importAttempt;
return $this;
}
/**
* @return Collection<int, MqttDoorLock>
* @SerializedName("doorLocks")
* @Groups({
* "property:read", "property:item:get",
* "booking:read", "booking:item:get"
* })
*/
public function getDoorLocks(): Collection {
return $this->doorLocks;
}
public function addDoorLock(MqttDoorLock $doorLock): self {
if (!$this->doorLocks->contains($doorLock)) {
$this->doorLocks[] = $doorLock;
}
return $this;
}
public function removeDoorLock(MqttDoorLock $doorLock): self {
$this->doorLocks->removeElement($doorLock);
return $this;
}
/**
* @return Collection<int, User>
*/
public function getUsers(): Collection {
return $this->users;
}
public function addUser(User $user): self {
if (!$this->users->contains($user)) {
$this->users[] = $user;
$user->setPropertyUser($this);
}
return $this;
}
public function removeUser(User $user): self {
if ($this->users->removeElement($user)) {
// set the owning side to null (unless already changed)
if ($user->getPropertyUser() === $this) {
$user->setPropertyUser(null);
}
}
return $this;
}
/**
* @return Collection<int, BookingProvider>
*/
public function getBookingProviders(): Collection {
return $this->bookingProviders;
}
public function addBookingProvider(BookingProvider $bookingProvider): self {
if (!$this->bookingProviders->contains($bookingProvider)) {
$this->bookingProviders[] = $bookingProvider;
$bookingProvider->setProperty($this);
}
return $this;
}
public function removeBookingProvider(BookingProvider $bookingProvider): self {
if ($this->bookingProviders->removeElement($bookingProvider)) {
// set the owning side to null (unless already changed)
if ($bookingProvider->getProperty() === $this) {
$bookingProvider->setProperty(null);
}
}
return $this;
}
public function getPropertyAddress(): ?PropertyAddress {
return $this->propertyAddress;
}
public function setPropertyAddress(PropertyAddress $propertyAddress): self {
$this->propertyAddress = $propertyAddress;
return $this;
}
}