<?php
namespace App\Entity;
use App\Entity\CustomExtra;
use App\Entity\History\Reservation\ReservationHistory;
use App\Entity\Payment\AbstractPayment;
use App\Entity\Payment\CarteCadeauPayment;
use App\Entity\Payment\EmptyPayment;
use App\Repository\ReservationRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=ReservationRepository::class)
*/
class Reservation extends AbstractReservation
{
/**
* @ORM\Column(type="string", length=255)
*/
private $bains;
/**
* @ORM\Column(type="boolean")
*/
private $confirmed;
/**
* @ORM\Column(type="string", length=255)
*/
private $token;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
private $deleted;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $comment;
/**
* @ORM\OneToMany(targetEntity=ExtraReservation::class, mappedBy="reservation", orphanRemoval=true, cascade={"persist", "remove"})
*/
protected $extraReservations;
/**
* @ORM\OneToMany(targetEntity=CustomExtra::class, mappedBy="reservation", orphanRemoval=true, cascade={"persist", "remove"})
*/
private $customExtras;
/**
* @ORM\OneToMany(targetEntity=Participant::class, mappedBy="reservation", orphanRemoval=true, cascade={"persist", "remove"})
*/
protected $participants;
/**
* @ORM\OneToMany(targetEntity=Token::class, mappedBy="reservation", cascade={"persist", "remove"})
*/
private $tokens;
/**
* @ORM\Column(type="float", nullable=true)
*/
private $resteAPayer;
/**
* @ORM\OneToMany(targetEntity=AbstractPayment::class, mappedBy="reservation", cascade={"remove"})
*/
private $abstractPayments;
/**
* @ORM\ManyToOne(targetEntity=Contact::class, inversedBy="reservations", cascade={"persist"})
*/
private $contact;
/**
* @ORM\OneToMany(targetEntity=ReservationHistory::class, mappedBy="reservation", cascade={"remove"})
*/
private $reservationHistories;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
private $isSatisfied;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
private $isClientMissing;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $clientMissingReason;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $cancelReason;
/**
* @ORM\OneToMany(targetEntity=Code::class, mappedBy="createdFromReservation", cascade={"remove"})
*/
private $createdCodes;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->deleted = false;
$this->confirmed = false;
$this->bains = '[]'; // Initialize with empty JSON array
$this->participants = new ArrayCollection();
$this->extraReservations = new ArrayCollection();
$this->customExtras = new ArrayCollection();
$this->tokens = new ArrayCollection();
$this->abstractPayments = new ArrayCollection();
$this->reservationHistories = new ArrayCollection();
$this->createdCodes = new ArrayCollection();
}
public function __clone()
{
$participants = new ArrayCollection();
foreach($this->getParticipants() as $participant) {
$newParticipant = clone $participant;
$participants->add($newParticipant);
$this->removeParticipant($participant);
}
$this->participants = $participants;
$extraReservations = new ArrayCollection();
foreach($this->getExtraReservations() as $extraReservation) {
$newExtraReservation = clone $extraReservation;
$extraReservations->add($newExtraReservation);
$this->removeExtraReservation($extraReservation);
}
$this->extraReservations = $extraReservations;
$customExtras = new ArrayCollection();
foreach($this->getCustomExtras() as $customExtra) {
$newCustomExtra = clone $customExtra;
$newCustomExtra->setReservation($this);
$customExtras->add($newCustomExtra);
}
$this->customExtras = $customExtras;
}
public function getBains(): ?string
{
return $this->bains;
}
public function setBains(string $bains): self
{
$this->bains = $bains;
return $this;
}
public function getConfirmed(): ?bool
{
return $this->confirmed;
}
public function setConfirmed(bool $confirmed): self
{
$this->confirmed = $confirmed;
return $this;
}
public function getToken(): ?string
{
return $this->token;
}
public function setToken(string $token): self
{
$this->token = $token;
return $this;
}
public function getDeleted(): ?bool
{
return $this->deleted;
}
public function setDeleted(?bool $deleted): self
{
$this->deleted = $deleted;
return $this;
}
public function getComment(): ?string
{
return $this->comment;
}
public function setComment(?string $comment): self
{
$this->comment = $comment;
return $this;
}
/**
* @return Collection<int, ExtraReservation>
*/
public function getExtraReservations(): Collection
{
return $this->extraReservations;
}
/**
* @return Collection<int, ExtraReservation>
*/
public function getActiveExtraReservations(): Collection
{
return $this->extraReservations->filter(function(ExtraReservation $extraReservation) {
return !$extraReservation->isEmpty();
});
}
public function addExtraReservation(ExtraReservation $extraReservation): self
{
if (!$this->extraReservations->contains($extraReservation)) {
$this->extraReservations[] = $extraReservation;
$extraReservation->setReservation($this);
}
return $this;
}
public function removeExtraReservation(ExtraReservation $extraReservation): self
{
if ($this->extraReservations->removeElement($extraReservation)) {
// set the owning side to null (unless already changed)
if ($extraReservation->getReservation() === $this) {
$extraReservation->setReservation(null);
}
}
return $this;
}
/**
* @return Collection<int, CustomExtra>
*/
public function getCustomExtras(): Collection
{
return $this->customExtras;
}
public function addCustomExtra(CustomExtra $customExtra): self
{
if (!$this->customExtras->contains($customExtra)) {
$this->customExtras[] = $customExtra;
$customExtra->setReservation($this);
}
return $this;
}
public function removeCustomExtra(CustomExtra $customExtra): self
{
if ($this->customExtras->removeElement($customExtra)) {
if ($customExtra->getReservation() === $this) {
$customExtra->setReservation(null);
}
}
return $this;
}
/**
* @return Collection<int, Participant>
*/
public function getParticipants(): Collection
{
return $this->participants;
}
public function addParticipant(Participant $participant): self
{
if (!$this->participants->contains($participant)) {
$this->participants[] = $participant;
$participant->setReservation($this);
}
return $this;
}
public function removeParticipant(Participant $participant): self
{
if ($this->participants->removeElement($participant)) {
// set the owning side to null (unless already changed)
if ($participant->getReservation() === $this) {
$participant->setReservation(null);
}
}
return $this;
}
/**
* @return Collection<int, Token>
*/
public function getTokens(): Collection
{
return $this->tokens;
}
public function addToken(Token $token): self
{
if (!$this->tokens->contains($token)) {
$this->tokens[] = $token;
$token->setReservation($this);
}
return $this;
}
public function removeToken(Token $token): self
{
if ($this->tokens->removeElement($token)) {
// set the owning side to null (unless already changed)
if ($token->getReservation() === $this) {
$token->setReservation(null);
}
}
return $this;
}
public function getResteAPayer(): ?float
{
return $this->resteAPayer ?? 0;
}
public function setResteAPayer(?float $resteAPayer): self
{
$this->resteAPayer = $resteAPayer;
return $this;
}
/**
* @return Collection<int, AbstractPayment>
*/
public function getAbstractPayments(): Collection
{
return $this->abstractPayments;
}
public function isPaidWithCarteCadeau(): bool
{
foreach ($this->abstractPayments as $payment) {
if ($payment instanceof CarteCadeauPayment) {
return true;
}
}
return false;
}
public function getUsedCarteCadeaux(): Collection
{
$carteCadeaux = new ArrayCollection();
foreach ($this->abstractPayments as $payment) {
if ($payment instanceof CarteCadeauPayment) {
$carteCadeaux->add($payment->getCartecadeau());
}
}
return $carteCadeaux;
}
public function addAbstractPayment(AbstractPayment $abstractPayment): self
{
if (!$this->abstractPayments->contains($abstractPayment)) {
$this->abstractPayments[] = $abstractPayment;
$abstractPayment->setReservation($this);
}
return $this;
}
public function removeAbstractPayment(AbstractPayment $abstractPayment): self
{
if ($this->abstractPayments->removeElement($abstractPayment)) {
// set the owning side to null (unless already changed)
if ($abstractPayment->getReservation() === $this) {
$abstractPayment->setReservation(null);
}
}
return $this;
}
public function getContact(): ?Contact
{
return $this->contact;
}
public function setContact(?Contact $contact): self
{
$this->contact = $contact;
return $this;
}
public function getAllHistories(): array
{
$histories = new ArrayCollection();
foreach($this->getReservationHistories() as $history) {
$histories->add($history);
}
foreach($this->getAbstractPayments() as $payment) {
if (!$payment instanceof EmptyPayment) {
$histories->add($payment);
}
// foreach($payment->getPaymentHistories() as $history) {
// $histories->add($history->getPayment());
// }
}
// sort by createdAt
$histories = $histories->toArray();
usort($histories, function($a, $b) {
return $b->getCreatedAt() <=> $a->getCreatedAt();
});
return $histories;
}
/**
* Get only payment histories from all histories
* @return array Payment histories sorted by creation date (newest first)
*/
public function getPaymentHistories(): array
{
$paymentHistories = new ArrayCollection();
foreach($this->getAbstractPayments() as $payment) {
if (!$payment instanceof EmptyPayment) {
$paymentHistories->add($payment);
}
}
// sort by createdAt
$histories = $paymentHistories->toArray();
usort($histories, function($a, $b) {
return $b->getCreatedAt() <=> $a->getCreatedAt();
});
return $histories;
}
/**
* @return Collection<int, ReservationHistory>
*/
public function getReservationHistories(): Collection
{
return $this->reservationHistories;
}
public function addReservationHistory(ReservationHistory $reservationHistory): self
{
if (!$this->reservationHistories->contains($reservationHistory)) {
$this->reservationHistories[] = $reservationHistory;
$reservationHistory->setReservation($this);
}
return $this;
}
public function removeReservationHistory(ReservationHistory $reservationHistory): self
{
$this->reservationHistories->removeElement($reservationHistory);
return $this;
}
public function getTotalPeople(): int
{
return ( $this->getSolo() ?? 0 )
+ ( ($this->getDuo() ?? 0) * 2);
}
public function getTotalBaths(): int
{
return ( $this->getSolo() ?? 0 )
+ ( $this->getDuo() ?? 0) ;
}
public function getArrivalDate(): ?\DateTimeInterface
{
return $this->getDate()->modify('-15 minutes');
}
public function getDepartureDate(): ?\DateTimeInterface
{
return $this->getOffer() == "60" ?
$this->getDate()->modify('+80 minutes') :
$this->getDate()->modify('+110 minutes')
;
}
public function getIsSatisfied(): ?bool
{
return $this->isSatisfied;
}
public function setIsSatisfied(?bool $isSatisfied): self
{
$this->isSatisfied = $isSatisfied;
return $this;
}
public function getIsClientMissing(): ?bool
{
return $this->isClientMissing;
}
public function setIsClientMissing(?bool $isClientMissing): self
{
$this->isClientMissing = $isClientMissing;
return $this;
}
public function getDateFormated(): string
{
// French day and month names
$days = [
'Sunday' => 'Dimanche',
'Monday' => 'Lundi',
'Tuesday' => 'Mardi',
'Wednesday' => 'Mercredi',
'Thursday' => 'Jeudi',
'Friday' => 'Vendredi',
'Saturday' => 'Samedi'
];
$months = [
'January' => 'janvier',
'February' => 'février',
'March' => 'mars',
'April' => 'avril',
'May' => 'mai',
'June' => 'juin',
'July' => 'juillet',
'August' => 'août',
'September' => 'septembre',
'October' => 'octobre',
'November' => 'novembre',
'December' => 'décembre'
];
$date = $this->getDate();
// Format components
$dayName = $days[$date->format('l')]; // Full day name
$day = $date->format('d'); // Day of the month
$monthName = $months[$date->format('F')]; // Full month name
$year = $date->format('Y'); // Year
$time = $date->format('H\h') . str_pad($date->format('i'), 2, '0', STR_PAD_LEFT);
// Combine the formatted string
$formattedDate = "$dayName $day $monthName $year à $time";
return $formattedDate;
}
public function getIsPast() {
return $this->getEndDate() < new \DateTime();
}
public function getEndDate(): \DateTimeInterface
{
$offerDuration = $this->getOffer() == "60" ? 60 : 90; // Durée en minutes
return (clone $this->getDate())->modify("+$offerDuration minutes");
}
public function getClientMissingReason(): ?string
{
return $this->clientMissingReason;
}
public function setClientMissingReason(?string $clientMissingReason): self
{
$this->clientMissingReason = $clientMissingReason;
return $this;
}
public function getCancelReason(): ?string
{
return $this->cancelReason;
}
public function setCancelReason(?string $cancelReason): self
{
$this->cancelReason = $cancelReason;
return $this;
}
/**
* @return Collection<int, Code>
*/
public function getCreatedCodes(): Collection
{
return $this->createdCodes;
}
public function addCreatedCode(Code $createdCode): self
{
if (!$this->createdCodes->contains($createdCode)) {
$this->createdCodes[] = $createdCode;
$createdCode->setCreatedFromReservation($this);
}
return $this;
}
public function removeCreatedCode(Code $createdCode): self
{
if ($this->createdCodes->removeElement($createdCode)) {
// set the owning side to null (unless already changed)
if ($createdCode->getCreatedFromReservation() === $this) {
$createdCode->setCreatedFromReservation(null);
}
}
return $this;
}
/**
* Count the number of times a client (non-admin) has edited this reservation
* @return int The number of client edits
*/
public function getClientEditCount(): int
{
$count = 0;
foreach($this->getReservationHistories() as $history) {
if ($history instanceof \App\Entity\History\Reservation\UpdateReservationHistory
&& $history->getModifier() !== "admin@taakabeerspa.com") {
$count++;
}
}
return $count;
}
/**
* Check if a client can edit this reservation (limit to 3 edits)
* @return bool Whether the client can edit the reservation
*/
public function canClientEdit(): bool
{
return $this->getClientEditCount() < 3;
}
/**
* Get the current status of the reservation based on business rules
* @return string The status constant from ReservationStatus
*/
public function getStatus(): string
{
if ($this->getDeleted()) {
return ReservationStatus::ANNULE;
}
if ($this->getConfirmed() && $this->getIsClientMissing()) {
return ReservationStatus::ABSENT;
}
if ($this->getConfirmed()) {
return ReservationStatus::CONFIRME;
}
return ReservationStatus::NON_CONFIRME;
}
/**
* Get the human-readable label for the current status
* @return string The French label for the status
*/
public function getStatusLabel(): string
{
return ReservationStatus::getLabel($this->getStatus());
}
/**
* Get the CSS class for the current status
* @return string The CSS class name for styling
*/
public function getStatusCssClass(): string
{
return ReservationStatus::getCssStatusClass($this->getStatus());
}
/**
* String representation of the reservation for debugging purposes
* @return string
*/
public function __toString(): string
{
return sprintf(
'Reservation(id: %s, token: %s, date: %s, confirmed: %s)',
$this->getId() ?? 'new',
$this->getToken() ?? 'none',
$this->getDate() ? $this->getDate()->format('Y-m-d H:i:s') : 'none',
$this->getConfirmed() ? 'true' : 'false'
);
}
}