feat: accommodation booking type and status as dedicated properties
This commit is contained in:
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.4 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 9.2 KiB |
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260806120000 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Split the accommodation booking status into a status and a type';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE accommodation_booking ADD type VARCHAR(20) DEFAULT NULL');
|
||||||
|
|
||||||
|
// An old 'booking' row with an accepted_at came from an accepted offer, so it is an
|
||||||
|
// Anfrage; without one it was placed directly. Rows in 'draft'/'discarded' carry no
|
||||||
|
// trace of their kind any more — they are mapped to 'inquiry', which is where the
|
||||||
|
// office path produces them.
|
||||||
|
$this->addSql("UPDATE accommodation_booking SET type = CASE
|
||||||
|
WHEN status = 'booking' AND accepted_at IS NULL THEN 'booking'
|
||||||
|
ELSE 'inquiry' END");
|
||||||
|
|
||||||
|
$this->addSql("UPDATE accommodation_booking SET status = CASE
|
||||||
|
WHEN status = 'inquiry' THEN 'open'
|
||||||
|
WHEN status = 'booking' THEN 'accepted'
|
||||||
|
ELSE status END");
|
||||||
|
|
||||||
|
$this->addSql('ALTER TABLE accommodation_booking CHANGE type type VARCHAR(20) NOT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql("UPDATE accommodation_booking SET status = CASE
|
||||||
|
WHEN status IN ('draft', 'discarded') THEN status
|
||||||
|
WHEN type = 'inquiry' AND status = 'open' THEN 'inquiry'
|
||||||
|
ELSE 'booking' END");
|
||||||
|
|
||||||
|
$this->addSql('ALTER TABLE accommodation_booking DROP type');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -127,9 +127,9 @@ class EditController extends AbstractController
|
|||||||
$this->entityManager->persist($booking);
|
$this->entityManager->persist($booking);
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
// Entering Anfrage or Buchung is what the customer needs to hear about: the offer
|
// Entering Offen or Bestätigt is what the customer needs to hear about: the offer
|
||||||
// becomes viewable, or the booking is confirmed. Entwurf and Absage stay silent.
|
// becomes viewable, or the booking is confirmed. Entwurf and Absage stay silent.
|
||||||
$notifiableStatuses = [AccommodationBookingStatus::Inquiry, AccommodationBookingStatus::Booking];
|
$notifiableStatuses = [AccommodationBookingStatus::Open, AccommodationBookingStatus::Accepted];
|
||||||
if ($previousStatus !== $booking->getStatus() && in_array($booking->getStatus(), $notifiableStatuses, true)) {
|
if ($previousStatus !== $booking->getStatus() && in_array($booking->getStatus(), $notifiableStatuses, true)) {
|
||||||
$this->bookingService->issueAccessLink($booking);
|
$this->bookingService->issueAccessLink($booking);
|
||||||
$this->bookingService->sendCustomerConfirmationEmail($booking);
|
$this->bookingService->sendCustomerConfirmationEmail($booking);
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class OfferController extends AbstractController
|
|||||||
return $this->redirectToOfferPage($request, $uuid);
|
return $this->redirectToOfferPage($request, $uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$booking->isInquiry()) {
|
if (!$booking->isInquiry() || !$booking->isOpen()) {
|
||||||
return $this->redirectToOfferPage($request, $uuid);
|
return $this->redirectToOfferPage($request, $uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ class Step4Controller extends AbstractAccommodationController
|
|||||||
{
|
{
|
||||||
$prices = $this->bookingService->loadPrices($dto, $accommodation);
|
$prices = $this->bookingService->loadPrices($dto, $accommodation);
|
||||||
$booking = $this->bookingService->finalizeBooking($dto, $accommodation, $prices, $services);
|
$booking = $this->bookingService->finalizeBooking($dto, $accommodation, $prices, $services);
|
||||||
$this->addFlash('groups_booking_result', $booking->getStatus()->value);
|
$this->addFlash('groups_booking_result', $booking->getType()->value);
|
||||||
$this->sessionManager->clear($request);
|
$this->sessionManager->clear($request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use App\Entity\TimestampableEntity;
|
|||||||
use App\Entity\TimestampableEntityInterface;
|
use App\Entity\TimestampableEntityInterface;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Enum\Groups\AccommodationBookingStatus;
|
use App\Enum\Groups\AccommodationBookingStatus;
|
||||||
|
use App\Enum\Groups\AccommodationBookingType;
|
||||||
use App\Enum\Groups\AdditionalServiceType;
|
use App\Enum\Groups\AdditionalServiceType;
|
||||||
use App\Repository\Groups\AccommodationBookingRepository;
|
use App\Repository\Groups\AccommodationBookingRepository;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
@@ -79,6 +80,14 @@ class AccommodationBooking implements BlameableEntityInterface, TimestampableEnt
|
|||||||
#[ORM\Column(length: 20, enumType: AccommodationBookingStatus::class)]
|
#[ORM\Column(length: 20, enumType: AccommodationBookingStatus::class)]
|
||||||
private AccommodationBookingStatus $status = AccommodationBookingStatus::Draft;
|
private AccommodationBookingStatus $status = AccommodationBookingStatus::Draft;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What kind of record this is — an offer the customer still has to accept, or a
|
||||||
|
* directly placed, binding booking. Independent of the status: an accepted offer
|
||||||
|
* keeps its Inquiry type, which is how offer-originated bookings stay recognisable.
|
||||||
|
*/
|
||||||
|
#[ORM\Column(length: 20, enumType: AccommodationBookingType::class)]
|
||||||
|
private AccommodationBookingType $type = AccommodationBookingType::Inquiry;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
|
||||||
private ?\DateTimeImmutable $accessLinkIssuedAt = null;
|
private ?\DateTimeImmutable $accessLinkIssuedAt = null;
|
||||||
|
|
||||||
@@ -353,14 +362,14 @@ class AccommodationBooking implements BlameableEntityInterface, TimestampableEnt
|
|||||||
return AccommodationBookingStatus::Draft === $this->status;
|
return AccommodationBookingStatus::Draft === $this->status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isInquiry(): bool
|
public function isOpen(): bool
|
||||||
{
|
{
|
||||||
return AccommodationBookingStatus::Inquiry === $this->status;
|
return AccommodationBookingStatus::Open === $this->status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isBooking(): bool
|
public function isAccepted(): bool
|
||||||
{
|
{
|
||||||
return AccommodationBookingStatus::Booking === $this->status;
|
return AccommodationBookingStatus::Accepted === $this->status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isDiscarded(): bool
|
public function isDiscarded(): bool
|
||||||
@@ -368,6 +377,28 @@ class AccommodationBooking implements BlameableEntityInterface, TimestampableEnt
|
|||||||
return AccommodationBookingStatus::Discarded === $this->status;
|
return AccommodationBookingStatus::Discarded === $this->status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getType(): AccommodationBookingType
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setType(AccommodationBookingType $type): self
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isInquiry(): bool
|
||||||
|
{
|
||||||
|
return AccommodationBookingType::Inquiry === $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isBooking(): bool
|
||||||
|
{
|
||||||
|
return AccommodationBookingType::Booking === $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
public function getAccessLinkIssuedAt(): ?\DateTimeImmutable
|
public function getAccessLinkIssuedAt(): ?\DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->accessLinkIssuedAt;
|
return $this->accessLinkIssuedAt;
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ namespace App\Enum\Groups;
|
|||||||
enum AccommodationBookingStatus: string
|
enum AccommodationBookingStatus: string
|
||||||
{
|
{
|
||||||
case Draft = 'draft';
|
case Draft = 'draft';
|
||||||
case Inquiry = 'inquiry';
|
case Open = 'open';
|
||||||
case Booking = 'booking';
|
case Accepted = 'accepted';
|
||||||
case Discarded = 'discarded';
|
case Discarded = 'discarded';
|
||||||
|
|
||||||
public function label(): string
|
public function label(): string
|
||||||
{
|
{
|
||||||
return match ($this) {
|
return match ($this) {
|
||||||
self::Draft => 'Entwurf',
|
self::Draft => 'Entwurf',
|
||||||
self::Inquiry => 'Anfrage',
|
self::Open => 'Offen',
|
||||||
self::Booking => 'Buchung',
|
self::Accepted => 'Bestätigt',
|
||||||
self::Discarded => 'Absage',
|
self::Discarded => 'Absage',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enum\Groups;
|
||||||
|
|
||||||
|
enum AccommodationBookingType: string
|
||||||
|
{
|
||||||
|
case Inquiry = 'inquiry';
|
||||||
|
case Booking = 'booking';
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Inquiry => 'Anfrage',
|
||||||
|
self::Booking => 'Buchung',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,8 +53,8 @@ class AccommodationBookingCreateType extends AbstractType
|
|||||||
'class' => AccommodationBookingStatus::class,
|
'class' => AccommodationBookingStatus::class,
|
||||||
'choices' => [
|
'choices' => [
|
||||||
AccommodationBookingStatus::Draft,
|
AccommodationBookingStatus::Draft,
|
||||||
AccommodationBookingStatus::Inquiry,
|
AccommodationBookingStatus::Open,
|
||||||
AccommodationBookingStatus::Booking,
|
AccommodationBookingStatus::Accepted,
|
||||||
],
|
],
|
||||||
'choice_label' => fn (AccommodationBookingStatus $status) => $status->label(),
|
'choice_label' => fn (AccommodationBookingStatus $status) => $status->label(),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ final readonly class AccommodationBookingApiResponse
|
|||||||
#[Groups(['api:single'])]
|
#[Groups(['api:single'])]
|
||||||
public string $status;
|
public string $status;
|
||||||
|
|
||||||
|
#[Groups(['api:single'])]
|
||||||
|
public string $type;
|
||||||
|
|
||||||
#[Groups(['api:single'])]
|
#[Groups(['api:single'])]
|
||||||
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
||||||
public ?\DateTimeImmutable $dateFrom;
|
public ?\DateTimeImmutable $dateFrom;
|
||||||
@@ -89,6 +92,7 @@ final readonly class AccommodationBookingApiResponse
|
|||||||
{
|
{
|
||||||
$this->uuid = $booking->getUuid();
|
$this->uuid = $booking->getUuid();
|
||||||
$this->status = $booking->getStatus()->value;
|
$this->status = $booking->getStatus()->value;
|
||||||
|
$this->type = $booking->getType()->value;
|
||||||
$this->dateFrom = $booking->getDateFrom();
|
$this->dateFrom = $booking->getDateFrom();
|
||||||
$this->dateTo = $booking->getDateTo();
|
$this->dateTo = $booking->getDateTo();
|
||||||
$this->nights = $booking->getNights();
|
$this->nights = $booking->getNights();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use App\Entity\Groups\AccommodationPrice;
|
|||||||
use App\Entity\Groups\AdditionalService;
|
use App\Entity\Groups\AdditionalService;
|
||||||
use App\Entity\Groups\BoardService;
|
use App\Entity\Groups\BoardService;
|
||||||
use App\Enum\Groups\AccommodationBookingStatus;
|
use App\Enum\Groups\AccommodationBookingStatus;
|
||||||
|
use App\Enum\Groups\AccommodationBookingType;
|
||||||
use App\Form\Model\AccommodationBookingDto;
|
use App\Form\Model\AccommodationBookingDto;
|
||||||
use App\Model\AccommodationBookingQueryParams;
|
use App\Model\AccommodationBookingQueryParams;
|
||||||
use App\Model\CmsHotelData;
|
use App\Model\CmsHotelData;
|
||||||
@@ -265,11 +266,11 @@ class AccommodationBookingService
|
|||||||
$booking->setPaxCount($dto->paxCount);
|
$booking->setPaxCount($dto->paxCount);
|
||||||
$booking->setMinorsCount($dto->minorsCount);
|
$booking->setMinorsCount($dto->minorsCount);
|
||||||
$booking->setChildrenCount($dto->childrenCount);
|
$booking->setChildrenCount($dto->childrenCount);
|
||||||
$booking->setStatus(
|
// An inquiry arrives live — the office has nothing to prepare first, so it goes
|
||||||
$dto->forceInquiry || $this->computeInquiryStatus($dto, $prices)->isInquiry
|
// straight to Offen. A direct booking is binding on arrival, hence Bestätigt.
|
||||||
? AccommodationBookingStatus::Inquiry
|
$isInquiry = $dto->forceInquiry || $this->computeInquiryStatus($dto, $prices)->isInquiry;
|
||||||
: AccommodationBookingStatus::Booking
|
$booking->setType($isInquiry ? AccommodationBookingType::Inquiry : AccommodationBookingType::Booking);
|
||||||
);
|
$booking->setStatus($isInquiry ? AccommodationBookingStatus::Open : AccommodationBookingStatus::Accepted);
|
||||||
|
|
||||||
// Freeze board service as scalar fields (no FK)
|
// Freeze board service as scalar fields (no FK)
|
||||||
if (null !== $dto->selectedBoardServiceId) {
|
if (null !== $dto->selectedBoardServiceId) {
|
||||||
@@ -432,9 +433,9 @@ class AccommodationBookingService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Issues the access link only for a confirmed booking. Inquiries from the public flow
|
* Issues the access link only for a direct booking. Inquiries from the public flow
|
||||||
* deliberately get no link — the office prepares the offer first and issues the link
|
* deliberately get no link — the office prepares the offer first and issues the link
|
||||||
* when it switches the booking to Anfrage.
|
* when it publishes the offer.
|
||||||
*/
|
*/
|
||||||
public function issueAccessLinkForDirectBooking(AccommodationBooking $booking): void
|
public function issueAccessLinkForDirectBooking(AccommodationBooking $booking): void
|
||||||
{
|
{
|
||||||
@@ -452,7 +453,7 @@ class AccommodationBookingService
|
|||||||
*/
|
*/
|
||||||
public function sendCustomerConfirmationEmail(AccommodationBooking $booking): void
|
public function sendCustomerConfirmationEmail(AccommodationBooking $booking): void
|
||||||
{
|
{
|
||||||
if (!$booking->isInquiry()) {
|
if ($booking->isAccepted()) {
|
||||||
$subject = 'Deine Buchung ist bestätigt';
|
$subject = 'Deine Buchung ist bestätigt';
|
||||||
} elseif (null !== $booking->getAccessLinkIssuedAt()) {
|
} elseif (null !== $booking->getAccessLinkIssuedAt()) {
|
||||||
$subject = 'Dein Angebot ist bereit';
|
$subject = 'Dein Angebot ist bereit';
|
||||||
@@ -481,16 +482,18 @@ class AccommodationBookingService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transitions a booking from inquiry to booking and notifies office and customer.
|
* Accepts an open offer and notifies office and customer. The type stays Anfrage —
|
||||||
* Idempotent — a no-op (including no emails) if the booking is already accepted.
|
* only the status moves to Bestätigt, so an offer-originated booking remains
|
||||||
|
* distinguishable from a direct one.
|
||||||
|
* Idempotent — a no-op (including no emails) if the offer is not open any more.
|
||||||
*/
|
*/
|
||||||
public function acceptBooking(AccommodationBooking $booking): void
|
public function acceptBooking(AccommodationBooking $booking): void
|
||||||
{
|
{
|
||||||
if (!$booking->isInquiry()) {
|
if (!$booking->isInquiry() || !$booking->isOpen()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$booking->setStatus(AccommodationBookingStatus::Booking);
|
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||||
$booking->setAcceptedAt(new \DateTimeImmutable());
|
$booking->setAcceptedAt(new \DateTimeImmutable());
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ booking.status.label }}
|
{{ booking.type.label }} · {{ booking.status.label }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% set discounts = [] %}
|
{% set discounts = [] %}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<twig:page:heading>
|
<twig:page:heading>
|
||||||
{{ booking.status.label }}
|
{{ booking.type.label }} · {{ booking.status.label }}
|
||||||
{{ booking.groupName }}
|
{{ booking.groupName }}
|
||||||
</twig:page:heading>
|
</twig:page:heading>
|
||||||
|
|
||||||
@@ -53,6 +53,8 @@
|
|||||||
<dt class="font-medium">davon Kinder (4–{{ booking.accommodation.maxAdolescentAge }} Jahre)</dt>
|
<dt class="font-medium">davon Kinder (4–{{ booking.accommodation.maxAdolescentAge }} Jahre)</dt>
|
||||||
<dd>{{ booking.childrenCount }}</dd>
|
<dd>{{ booking.childrenCount }}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<dt class="font-medium">Art</dt>
|
||||||
|
<dd>{{ booking.type.label }}</dd>
|
||||||
<dt class="font-medium">Status</dt>
|
<dt class="font-medium">Status</dt>
|
||||||
<dd>{{ booking.status.label }}</dd>
|
<dd>{{ booking.status.label }}</dd>
|
||||||
{% if booking.acceptedAt is not null %}
|
{% if booking.acceptedAt is not null %}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% if accessLink %}
|
{% if accessLink %}
|
||||||
<h1>{{ booking.inquiry ? 'Dein Angebot ist bereit' : 'Deine Buchung ist bestätigt' }}</h1>
|
<h1>{{ booking.accepted ? 'Deine Buchung ist bestätigt' : 'Dein Angebot ist bereit' }}</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Hallo {{ booking.firstName }},
|
Hallo {{ booking.firstName }},
|
||||||
{% if booking.inquiry %}
|
{% if not booking.accepted %}
|
||||||
vielen Dank für deine Anfrage! Wir haben sie geprüft — dein Angebot wartet auf dich.
|
vielen Dank für deine Anfrage! Wir haben sie geprüft — dein Angebot wartet auf dich.
|
||||||
{% else %}
|
{% else %}
|
||||||
vielen Dank! Deine Buchung ist bei uns eingegangen und bestätigt.
|
vielen Dank! Deine Buchung ist bei uns eingegangen und bestätigt.
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ accessLink }}" class="button">
|
<a href="{{ accessLink }}" class="button">
|
||||||
{{ booking.inquiry ? 'Angebot ansehen & bestätigen' : 'Buchung ansehen' }}
|
{{ booking.accepted ? 'Buchung ansehen' : 'Angebot ansehen & bestätigen' }}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
{% import _self as macros %}
|
{% import _self as macros %}
|
||||||
|
|
||||||
{% block title %}{{ booking.inquiry ? 'Dein Angebot' : 'Deine Buchung' }}{% endblock %}
|
{% block title %}{{ booking.accepted ? 'Deine Buchung' : 'Dein Angebot' }}{% endblock %}
|
||||||
|
|
||||||
{% block background %}bg-outer bg-outer--summer{% endblock %}
|
{% block background %}bg-outer bg-outer--summer{% endblock %}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
<hgroup>
|
<hgroup>
|
||||||
<h1 class="text-2xl lg:text-4xl font-bold mb-2">
|
<h1 class="text-2xl lg:text-4xl font-bold mb-2">
|
||||||
{{ booking.inquiry ? 'Dein Angebot' : 'Deine Buchung' }}
|
{{ booking.accepted ? 'Deine Buchung' : 'Dein Angebot' }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-2xl">
|
<p class="text-2xl">
|
||||||
{{ ctx.accommodation.name }}
|
{{ ctx.accommodation.name }}
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
{% if booking.boardServiceLabel or booking.additionalServices | length > 0 %}
|
{% if booking.boardServiceLabel or booking.additionalServices | length > 0 %}
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-xl font-bold mb-2">
|
<h2 class="text-xl font-bold mb-2">
|
||||||
{{ booking.inquiry ? 'Angefragte' : 'Gebuchte' }} Leistungen
|
{{ booking.accepted ? 'Gebuchte' : 'Angefragte' }} Leistungen
|
||||||
</h2>
|
</h2>
|
||||||
<ul class="list-disc pl-4">
|
<ul class="list-disc pl-4">
|
||||||
{% if booking.boardServiceLabel %}
|
{% if booking.boardServiceLabel %}
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if booking.inquiry %}
|
{% if booking.inquiry and booking.open %}
|
||||||
<div>
|
<div>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="button button--primary w-full"
|
class="button button--primary w-full"
|
||||||
|
|||||||
@@ -25,32 +25,32 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
*/
|
*/
|
||||||
class EditControllerTest extends TestCase
|
class EditControllerTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testTransitionToInquiryIssuesAccessLinkAndSendsCustomerEmail(): void
|
public function testTransitionToOpenIssuesAccessLinkAndSendsCustomerEmail(): void
|
||||||
{
|
{
|
||||||
$bookingService = $this->assertNotified(AccommodationBookingStatus::Inquiry);
|
$bookingService = $this->assertNotified(AccommodationBookingStatus::Open);
|
||||||
|
|
||||||
$this->submitStatusChange(AccommodationBookingStatus::Draft, AccommodationBookingStatus::Inquiry, $bookingService);
|
$this->submitStatusChange(AccommodationBookingStatus::Draft, AccommodationBookingStatus::Open, $bookingService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTransitionToBookingIssuesAccessLinkAndSendsCustomerEmail(): void
|
public function testTransitionToAcceptedIssuesAccessLinkAndSendsCustomerEmail(): void
|
||||||
{
|
{
|
||||||
$bookingService = $this->assertNotified(AccommodationBookingStatus::Booking);
|
$bookingService = $this->assertNotified(AccommodationBookingStatus::Accepted);
|
||||||
|
|
||||||
$this->submitStatusChange(AccommodationBookingStatus::Draft, AccommodationBookingStatus::Booking, $bookingService);
|
$this->submitStatusChange(AccommodationBookingStatus::Draft, AccommodationBookingStatus::Accepted, $bookingService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTransitionToDiscardedSendsNothing(): void
|
public function testTransitionToDiscardedSendsNothing(): void
|
||||||
{
|
{
|
||||||
$bookingService = $this->assertNotNotified();
|
$bookingService = $this->assertNotNotified();
|
||||||
|
|
||||||
$this->submitStatusChange(AccommodationBookingStatus::Inquiry, AccommodationBookingStatus::Discarded, $bookingService);
|
$this->submitStatusChange(AccommodationBookingStatus::Open, AccommodationBookingStatus::Discarded, $bookingService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSavingWithoutStatusChangeSendsNothing(): void
|
public function testSavingWithoutStatusChangeSendsNothing(): void
|
||||||
{
|
{
|
||||||
$bookingService = $this->assertNotNotified();
|
$bookingService = $this->assertNotNotified();
|
||||||
|
|
||||||
$this->submitStatusChange(AccommodationBookingStatus::Booking, AccommodationBookingStatus::Booking, $bookingService);
|
$this->submitStatusChange(AccommodationBookingStatus::Accepted, AccommodationBookingStatus::Accepted, $bookingService);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function assertNotified(AccommodationBookingStatus $expected): AccommodationBookingService
|
private function assertNotified(AccommodationBookingStatus $expected): AccommodationBookingService
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use App\Controller\Api\AccommodationBookingController;
|
|||||||
use App\Entity\Groups\Accommodation;
|
use App\Entity\Groups\Accommodation;
|
||||||
use App\Entity\Groups\AccommodationBooking;
|
use App\Entity\Groups\AccommodationBooking;
|
||||||
use App\Enum\Groups\AccommodationBookingStatus;
|
use App\Enum\Groups\AccommodationBookingStatus;
|
||||||
|
use App\Enum\Groups\AccommodationBookingType;
|
||||||
use App\Repository\Groups\AccommodationBookingRepository;
|
use App\Repository\Groups\AccommodationBookingRepository;
|
||||||
use App\Service\AccommodationBookingBreakdownCalculator;
|
use App\Service\AccommodationBookingBreakdownCalculator;
|
||||||
use App\Service\AccommodationBookingService;
|
use App\Service\AccommodationBookingService;
|
||||||
@@ -55,7 +56,8 @@ class AccommodationBookingControllerTest extends TestCase
|
|||||||
$booking->setAccommodationDiscount(10);
|
$booking->setAccommodationDiscount(10);
|
||||||
$booking->setBoardServiceDiscount(20);
|
$booking->setBoardServiceDiscount(20);
|
||||||
$booking->setAdditionalServicesDiscount(30);
|
$booking->setAdditionalServicesDiscount(30);
|
||||||
$booking->setStatus(AccommodationBookingStatus::Booking);
|
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||||
|
$booking->setType(AccommodationBookingType::Booking);
|
||||||
$booking->setAcceptedAt(new \DateTimeImmutable('2026-07-15T10:00:00+00:00'));
|
$booking->setAcceptedAt(new \DateTimeImmutable('2026-07-15T10:00:00+00:00'));
|
||||||
|
|
||||||
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
||||||
@@ -85,7 +87,8 @@ class AccommodationBookingControllerTest extends TestCase
|
|||||||
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
|
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
|
||||||
self::assertSame([
|
self::assertSame([
|
||||||
'uuid' => $booking->getUuid(),
|
'uuid' => $booking->getUuid(),
|
||||||
'status' => 'booking',
|
'status' => 'accepted',
|
||||||
|
'type' => 'booking',
|
||||||
'dateFrom' => '2026-07-20',
|
'dateFrom' => '2026-07-20',
|
||||||
'dateTo' => '2026-07-25',
|
'dateTo' => '2026-07-25',
|
||||||
'nights' => 5,
|
'nights' => 5,
|
||||||
@@ -135,7 +138,7 @@ class AccommodationBookingControllerTest extends TestCase
|
|||||||
self::assertArrayNotHasKey('currency', $payload['accommodation']);
|
self::assertArrayNotHasKey('currency', $payload['accommodation']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSingleReturnsInquiryStatusWhenBookingIsInquiry(): void
|
public function testSingleReturnsOpenStatusAndInquiryTypeForAnOffer(): void
|
||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setDateFrom(new \DateTimeImmutable('2026-08-01'));
|
$booking->setDateFrom(new \DateTimeImmutable('2026-08-01'));
|
||||||
@@ -144,7 +147,8 @@ class AccommodationBookingControllerTest extends TestCase
|
|||||||
$booking->setFirstName('Tom');
|
$booking->setFirstName('Tom');
|
||||||
$booking->setLastName('Beispiel');
|
$booking->setLastName('Beispiel');
|
||||||
$booking->setEmail('[email protected]');
|
$booking->setEmail('[email protected]');
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
|
$booking->setType(AccommodationBookingType::Inquiry);
|
||||||
|
|
||||||
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
||||||
$bookingRepository
|
$bookingRepository
|
||||||
@@ -160,7 +164,8 @@ class AccommodationBookingControllerTest extends TestCase
|
|||||||
$response = $controller->single($booking->getUuid());
|
$response = $controller->single($booking->getUuid());
|
||||||
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
self::assertSame('inquiry', $payload['status']);
|
self::assertSame('open', $payload['status']);
|
||||||
|
self::assertSame('inquiry', $payload['type']);
|
||||||
self::assertNull($payload['priceBreakdown']);
|
self::assertNull($payload['priceBreakdown']);
|
||||||
self::assertNull($payload['acceptedAt']);
|
self::assertNull($payload['acceptedAt']);
|
||||||
}
|
}
|
||||||
@@ -186,7 +191,7 @@ class AccommodationBookingControllerTest extends TestCase
|
|||||||
self::assertSame(['message' => 'Not found'], $payload);
|
self::assertSame(['message' => 'Not found'], $payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAcceptTransitionsInquiryToBookingAndSetsAcceptedAt(): void
|
public function testAcceptAcceptsOpenOfferAndSetsAcceptedAt(): void
|
||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setDateFrom(new \DateTimeImmutable('2026-08-01'));
|
$booking->setDateFrom(new \DateTimeImmutable('2026-08-01'));
|
||||||
@@ -195,7 +200,7 @@ class AccommodationBookingControllerTest extends TestCase
|
|||||||
$booking->setFirstName('Tom');
|
$booking->setFirstName('Tom');
|
||||||
$booking->setLastName('Beispiel');
|
$booking->setLastName('Beispiel');
|
||||||
$booking->setEmail('[email protected]');
|
$booking->setEmail('[email protected]');
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
|
|
||||||
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
||||||
$bookingRepository
|
$bookingRepository
|
||||||
@@ -213,7 +218,7 @@ class AccommodationBookingControllerTest extends TestCase
|
|||||||
->method('acceptBooking')
|
->method('acceptBooking')
|
||||||
->with($booking)
|
->with($booking)
|
||||||
->willReturnCallback(static function (AccommodationBooking $b): void {
|
->willReturnCallback(static function (AccommodationBooking $b): void {
|
||||||
$b->setStatus(AccommodationBookingStatus::Booking);
|
$b->setStatus(AccommodationBookingStatus::Accepted);
|
||||||
$b->setAcceptedAt(new \DateTimeImmutable());
|
$b->setAcceptedAt(new \DateTimeImmutable());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -223,8 +228,9 @@ class AccommodationBookingControllerTest extends TestCase
|
|||||||
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
|
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
|
||||||
self::assertSame('booking', $payload['status']);
|
self::assertSame('accepted', $payload['status']);
|
||||||
self::assertSame(AccommodationBookingStatus::Booking, $booking->getStatus());
|
self::assertSame('inquiry', $payload['type']);
|
||||||
|
self::assertSame(AccommodationBookingStatus::Accepted, $booking->getStatus());
|
||||||
self::assertNotNull($booking->getAcceptedAt());
|
self::assertNotNull($booking->getAcceptedAt());
|
||||||
self::assertSame($booking->getAcceptedAt()->format(\DATE_ATOM), $payload['acceptedAt']);
|
self::assertSame($booking->getAcceptedAt()->format(\DATE_ATOM), $payload['acceptedAt']);
|
||||||
}
|
}
|
||||||
@@ -240,7 +246,7 @@ class AccommodationBookingControllerTest extends TestCase
|
|||||||
$booking->setFirstName('Tom');
|
$booking->setFirstName('Tom');
|
||||||
$booking->setLastName('Beispiel');
|
$booking->setLastName('Beispiel');
|
||||||
$booking->setEmail('[email protected]');
|
$booking->setEmail('[email protected]');
|
||||||
$booking->setStatus(AccommodationBookingStatus::Booking);
|
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||||
$booking->setAcceptedAt($acceptedAt);
|
$booking->setAcceptedAt($acceptedAt);
|
||||||
|
|
||||||
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
||||||
@@ -261,7 +267,7 @@ class AccommodationBookingControllerTest extends TestCase
|
|||||||
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
$payload = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
|
self::assertSame(Response::HTTP_OK, $response->getStatusCode());
|
||||||
self::assertSame('booking', $payload['status']);
|
self::assertSame('accepted', $payload['status']);
|
||||||
self::assertSame($acceptedAt, $booking->getAcceptedAt());
|
self::assertSame($acceptedAt, $booking->getAcceptedAt());
|
||||||
self::assertSame($acceptedAt->format(\DATE_ATOM), $payload['acceptedAt']);
|
self::assertSame($acceptedAt->format(\DATE_ATOM), $payload['acceptedAt']);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class OfferControllerTest extends TestCase
|
|||||||
public function testAccessAuthorizesSessionAndRedirectsToView(): void
|
public function testAccessAuthorizesSessionAndRedirectsToView(): void
|
||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
|
|
||||||
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
||||||
$bookingRepository->method('findOneBy')->with(['uuid' => $booking->getUuid()])->willReturn($booking);
|
$bookingRepository->method('findOneBy')->with(['uuid' => $booking->getUuid()])->willReturn($booking);
|
||||||
@@ -151,7 +151,7 @@ class OfferControllerTest extends TestCase
|
|||||||
public function testViewRendersOfferWhenSessionAuthorized(): void
|
public function testViewRendersOfferWhenSessionAuthorized(): void
|
||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
$booking->setAccommodation(new Accommodation());
|
$booking->setAccommodation(new Accommodation());
|
||||||
|
|
||||||
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
||||||
@@ -207,7 +207,7 @@ class OfferControllerTest extends TestCase
|
|||||||
public function testConfirmGetRendersModalWithFreshForm(): void
|
public function testConfirmGetRendersModalWithFreshForm(): void
|
||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
|
|
||||||
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
||||||
$bookingRepository->method('findOneBy')->with(['uuid' => $booking->getUuid()])->willReturn($booking);
|
$bookingRepository->method('findOneBy')->with(['uuid' => $booking->getUuid()])->willReturn($booking);
|
||||||
@@ -238,7 +238,7 @@ class OfferControllerTest extends TestCase
|
|||||||
public function testConfirmPostValidAcceptsBookingAndRedirects(): void
|
public function testConfirmPostValidAcceptsBookingAndRedirects(): void
|
||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
|
|
||||||
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
||||||
$bookingRepository->method('findOneBy')->willReturn($booking);
|
$bookingRepository->method('findOneBy')->willReturn($booking);
|
||||||
@@ -273,7 +273,7 @@ class OfferControllerTest extends TestCase
|
|||||||
public function testConfirmPostValidViaHtmxReturnsHxRedirect(): void
|
public function testConfirmPostValidViaHtmxReturnsHxRedirect(): void
|
||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
|
|
||||||
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
||||||
$bookingRepository->method('findOneBy')->willReturn($booking);
|
$bookingRepository->method('findOneBy')->willReturn($booking);
|
||||||
@@ -308,7 +308,7 @@ class OfferControllerTest extends TestCase
|
|||||||
public function testConfirmPostInvalidReRendersModalWithoutAccepting(): void
|
public function testConfirmPostInvalidReRendersModalWithoutAccepting(): void
|
||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
|
|
||||||
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
||||||
$bookingRepository->method('findOneBy')->willReturn($booking);
|
$bookingRepository->method('findOneBy')->willReturn($booking);
|
||||||
@@ -342,7 +342,7 @@ class OfferControllerTest extends TestCase
|
|||||||
public function testConfirmRedirectsWhenAlreadyAccepted(): void
|
public function testConfirmRedirectsWhenAlreadyAccepted(): void
|
||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Booking);
|
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||||
|
|
||||||
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
$bookingRepository = $this->createMock(AccommodationBookingRepository::class);
|
||||||
$bookingRepository->method('findOneBy')->willReturn($booking);
|
$bookingRepository->method('findOneBy')->willReturn($booking);
|
||||||
@@ -462,7 +462,7 @@ final class TestableOfferController extends OfferController
|
|||||||
$this->renderedParameters = $parameters;
|
$this->renderedParameters = $parameters;
|
||||||
|
|
||||||
if ('groups/booking/offer.html.twig' === $view) {
|
if ('groups/booking/offer.html.twig' === $view) {
|
||||||
$content = $parameters['booking']->isInquiry() ? '<html>offer</html>' : '<html>confirmed</html>';
|
$content = $parameters['booking']->isAccepted() ? '<html>confirmed</html>' : '<html>offer</html>';
|
||||||
} else {
|
} else {
|
||||||
$content = '<html>unavailable</html>';
|
$content = '<html>unavailable</html>';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace App\Tests\Controller\Groups;
|
|||||||
use App\Controller\Groups\Step4Controller;
|
use App\Controller\Groups\Step4Controller;
|
||||||
use App\Entity\Groups\Accommodation;
|
use App\Entity\Groups\Accommodation;
|
||||||
use App\Entity\Groups\AccommodationBooking;
|
use App\Entity\Groups\AccommodationBooking;
|
||||||
use App\Enum\Groups\AccommodationBookingStatus;
|
use App\Enum\Groups\AccommodationBookingType;
|
||||||
use App\Form\Model\AccommodationBookingDto;
|
use App\Form\Model\AccommodationBookingDto;
|
||||||
use App\Service\AccommodationBookingService;
|
use App\Service\AccommodationBookingService;
|
||||||
use App\Service\AccommodationSessionManager;
|
use App\Service\AccommodationSessionManager;
|
||||||
@@ -102,7 +102,7 @@ class Step4ControllerTest extends TestCase
|
|||||||
->setCalendarCode('HOTEL')
|
->setCalendarCode('HOTEL')
|
||||||
->setMaxAdolescentAge(17);
|
->setMaxAdolescentAge(17);
|
||||||
|
|
||||||
$booking = (new AccommodationBooking())->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking = (new AccommodationBooking())->setType(AccommodationBookingType::Inquiry);
|
||||||
|
|
||||||
$bookingService = $this->createMock(AccommodationBookingService::class);
|
$bookingService = $this->createMock(AccommodationBookingService::class);
|
||||||
$bookingService->method('loadAccommodation')->with(1)->willReturn($accommodation);
|
$bookingService->method('loadAccommodation')->with(1)->willReturn($accommodation);
|
||||||
@@ -275,7 +275,7 @@ class Step4ControllerTest extends TestCase
|
|||||||
->setCalendarCode('HOTEL')
|
->setCalendarCode('HOTEL')
|
||||||
->setMaxAdolescentAge(17);
|
->setMaxAdolescentAge(17);
|
||||||
|
|
||||||
$booking = (new AccommodationBooking())->setStatus(AccommodationBookingStatus::Booking);
|
$booking = (new AccommodationBooking())->setType(AccommodationBookingType::Booking);
|
||||||
|
|
||||||
$bookingService = $this->createMock(AccommodationBookingService::class);
|
$bookingService = $this->createMock(AccommodationBookingService::class);
|
||||||
$bookingService->method('loadAccommodation')->with(1)->willReturn($accommodation);
|
$bookingService->method('loadAccommodation')->with(1)->willReturn($accommodation);
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ class AccommodationBookingTypeTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public static function customerFacingStatuses(): iterable
|
public static function customerFacingStatuses(): iterable
|
||||||
{
|
{
|
||||||
yield 'inquiry' => [AccommodationBookingStatus::Inquiry];
|
yield 'open' => [AccommodationBookingStatus::Open];
|
||||||
yield 'booking' => [AccommodationBookingStatus::Booking];
|
yield 'accepted' => [AccommodationBookingStatus::Accepted];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,7 +67,7 @@ class AccommodationBookingTypeTest extends TestCase
|
|||||||
public function testPromotedBookingRequiresContactData(): void
|
public function testPromotedBookingRequiresContactData(): void
|
||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
|
|
||||||
$violations = $this->validate($booking, ['Default', 'edit']);
|
$violations = $this->validate($booking, ['Default', 'edit']);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use App\Entity\Groups\Accommodation;
|
|||||||
use App\Entity\Groups\AccommodationBooking;
|
use App\Entity\Groups\AccommodationBooking;
|
||||||
use App\Entity\Groups\AccommodationPrice;
|
use App\Entity\Groups\AccommodationPrice;
|
||||||
use App\Enum\Groups\AccommodationBookingStatus;
|
use App\Enum\Groups\AccommodationBookingStatus;
|
||||||
|
use App\Enum\Groups\AccommodationBookingType;
|
||||||
use App\Model\AccommodationBookingQueryParams;
|
use App\Model\AccommodationBookingQueryParams;
|
||||||
use App\Repository\Groups\AccommodationPriceRepository;
|
use App\Repository\Groups\AccommodationPriceRepository;
|
||||||
use App\Repository\Groups\AccommodationRepository;
|
use App\Repository\Groups\AccommodationRepository;
|
||||||
@@ -66,7 +67,7 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
$service = $this->createServiceWithAccommodation(entityManager: $entityManager);
|
$service = $this->createServiceWithAccommodation(entityManager: $entityManager);
|
||||||
|
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Booking);
|
$booking->setType(AccommodationBookingType::Booking);
|
||||||
|
|
||||||
$service->issueAccessLinkForDirectBooking($booking);
|
$service->issueAccessLinkForDirectBooking($booking);
|
||||||
|
|
||||||
@@ -81,7 +82,7 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
$service = $this->createServiceWithAccommodation(entityManager: $entityManager);
|
$service = $this->createServiceWithAccommodation(entityManager: $entityManager);
|
||||||
|
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setType(AccommodationBookingType::Inquiry);
|
||||||
|
|
||||||
$service->issueAccessLinkForDirectBooking($booking);
|
$service->issueAccessLinkForDirectBooking($booking);
|
||||||
|
|
||||||
@@ -96,7 +97,7 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
$service = $this->createServiceWithAccommodation(entityManager: $entityManager);
|
$service = $this->createServiceWithAccommodation(entityManager: $entityManager);
|
||||||
|
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Booking);
|
$booking->setType(AccommodationBookingType::Booking);
|
||||||
$issuedAt = new \DateTimeImmutable('2026-01-01');
|
$issuedAt = new \DateTimeImmutable('2026-01-01');
|
||||||
$booking->setAccessLinkIssuedAt($issuedAt);
|
$booking->setAccessLinkIssuedAt($issuedAt);
|
||||||
|
|
||||||
@@ -113,7 +114,7 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
$service = $this->createServiceWithAccommodation(entityManager: $entityManager);
|
$service = $this->createServiceWithAccommodation(entityManager: $entityManager);
|
||||||
|
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
|
|
||||||
$service->issueAccessLink($booking);
|
$service->issueAccessLink($booking);
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
$service = $this->createServiceWithAccommodation(entityManager: $entityManager);
|
$service = $this->createServiceWithAccommodation(entityManager: $entityManager);
|
||||||
|
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
$issuedAt = new \DateTimeImmutable('2026-01-01');
|
$issuedAt = new \DateTimeImmutable('2026-01-01');
|
||||||
$booking->setAccessLinkIssuedAt($issuedAt);
|
$booking->setAccessLinkIssuedAt($issuedAt);
|
||||||
|
|
||||||
@@ -141,7 +142,7 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setEmail('[email protected]');
|
$booking->setEmail('[email protected]');
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
$booking->setAccessLinkIssuedAt(new \DateTimeImmutable());
|
$booking->setAccessLinkIssuedAt(new \DateTimeImmutable());
|
||||||
|
|
||||||
$linkSigner = $this->createMock(AccommodationBookingLinkSigner::class);
|
$linkSigner = $this->createMock(AccommodationBookingLinkSigner::class);
|
||||||
@@ -168,7 +169,7 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setEmail('[email protected]');
|
$booking->setEmail('[email protected]');
|
||||||
$booking->setStatus(AccommodationBookingStatus::Booking);
|
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||||
$booking->setAccessLinkIssuedAt(new \DateTimeImmutable());
|
$booking->setAccessLinkIssuedAt(new \DateTimeImmutable());
|
||||||
|
|
||||||
$linkSigner = $this->createMock(AccommodationBookingLinkSigner::class);
|
$linkSigner = $this->createMock(AccommodationBookingLinkSigner::class);
|
||||||
@@ -197,7 +198,7 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setEmail('[email protected]');
|
$booking->setEmail('[email protected]');
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
|
|
||||||
$breakdownCalculator = $this->createMock(AccommodationBookingBreakdownCalculator::class);
|
$breakdownCalculator = $this->createMock(AccommodationBookingBreakdownCalculator::class);
|
||||||
$breakdownCalculator->method('compute')->willReturn(null);
|
$breakdownCalculator->method('compute')->willReturn(null);
|
||||||
@@ -220,7 +221,7 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setEmail('[email protected]');
|
$booking->setEmail('[email protected]');
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
|
|
||||||
$breakdownCalculator = $this->createMock(AccommodationBookingBreakdownCalculator::class);
|
$breakdownCalculator = $this->createMock(AccommodationBookingBreakdownCalculator::class);
|
||||||
$breakdownCalculator->method('compute')->willReturn(null);
|
$breakdownCalculator->method('compute')->willReturn(null);
|
||||||
@@ -240,7 +241,7 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setEmail('[email protected]');
|
$booking->setEmail('[email protected]');
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
$previousIssuedAt = new \DateTimeImmutable('2026-01-01');
|
$previousIssuedAt = new \DateTimeImmutable('2026-01-01');
|
||||||
$booking->setAccessLinkIssuedAt($previousIssuedAt);
|
$booking->setAccessLinkIssuedAt($previousIssuedAt);
|
||||||
|
|
||||||
@@ -264,13 +265,13 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
self::assertNotSame($previousIssuedAt, $booking->getAccessLinkIssuedAt());
|
self::assertNotSame($previousIssuedAt, $booking->getAccessLinkIssuedAt());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAcceptBookingTransitionsInquiryToBookingAndSendsNotifications(): void
|
public function testAcceptBookingAcceptsOpenInquiryAndSendsNotifications(): void
|
||||||
{
|
{
|
||||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||||
$entityManager->expects(self::once())->method('flush');
|
$entityManager->expects(self::once())->method('flush');
|
||||||
|
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Inquiry);
|
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||||
$booking->setEmail('[email protected]');
|
$booking->setEmail('[email protected]');
|
||||||
|
|
||||||
$mailer = $this->createMock(Mailer::class);
|
$mailer = $this->createMock(Mailer::class);
|
||||||
@@ -288,7 +289,8 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
|
|
||||||
$service->acceptBooking($booking);
|
$service->acceptBooking($booking);
|
||||||
|
|
||||||
self::assertSame(AccommodationBookingStatus::Booking, $booking->getStatus());
|
self::assertSame(AccommodationBookingStatus::Accepted, $booking->getStatus());
|
||||||
|
self::assertSame(AccommodationBookingType::Inquiry, $booking->getType(), 'an accepted offer stays an Anfrage');
|
||||||
self::assertNotNull($booking->getAcceptedAt());
|
self::assertNotNull($booking->getAcceptedAt());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,17 +305,37 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
$service = $this->createServiceWithAccommodation(entityManager: $entityManager, mailer: $mailer);
|
$service = $this->createServiceWithAccommodation(entityManager: $entityManager, mailer: $mailer);
|
||||||
|
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Booking);
|
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||||
|
|
||||||
$service->acceptBooking($booking);
|
$service->acceptBooking($booking);
|
||||||
|
|
||||||
self::assertNull($booking->getAcceptedAt());
|
self::assertNull($booking->getAcceptedAt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testAcceptBookingNoOpsForOfferThatIsNotOpenYet(): void
|
||||||
|
{
|
||||||
|
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||||
|
$entityManager->expects(self::never())->method('flush');
|
||||||
|
|
||||||
|
$mailer = $this->createMock(Mailer::class);
|
||||||
|
$mailer->expects(self::never())->method('createAndSendEmail');
|
||||||
|
|
||||||
|
$service = $this->createServiceWithAccommodation(entityManager: $entityManager, mailer: $mailer);
|
||||||
|
|
||||||
|
$booking = new AccommodationBooking();
|
||||||
|
$booking->setType(AccommodationBookingType::Inquiry);
|
||||||
|
$booking->setStatus(AccommodationBookingStatus::Draft);
|
||||||
|
|
||||||
|
$service->acceptBooking($booking);
|
||||||
|
|
||||||
|
self::assertSame(AccommodationBookingStatus::Draft, $booking->getStatus());
|
||||||
|
self::assertNull($booking->getAcceptedAt());
|
||||||
|
}
|
||||||
|
|
||||||
public function testSendOfferAcceptedNotificationEmailLogsAndSwallowsMailerFailures(): void
|
public function testSendOfferAcceptedNotificationEmailLogsAndSwallowsMailerFailures(): void
|
||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setStatus(AccommodationBookingStatus::Booking);
|
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||||
|
|
||||||
$mailer = $this->createMock(Mailer::class);
|
$mailer = $this->createMock(Mailer::class);
|
||||||
$mailer->method('createAndSendEmail')->willThrowException(new \RuntimeException('SMTP down'));
|
$mailer->method('createAndSendEmail')->willThrowException(new \RuntimeException('SMTP down'));
|
||||||
@@ -330,7 +352,7 @@ class AccommodationBookingServiceTest extends TestCase
|
|||||||
{
|
{
|
||||||
$booking = new AccommodationBooking();
|
$booking = new AccommodationBooking();
|
||||||
$booking->setEmail('[email protected]');
|
$booking->setEmail('[email protected]');
|
||||||
$booking->setStatus(AccommodationBookingStatus::Booking);
|
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||||
|
|
||||||
$mailer = $this->createMock(Mailer::class);
|
$mailer = $this->createMock(Mailer::class);
|
||||||
$mailer->method('createAndSendEmail')->willThrowException(new \RuntimeException('SMTP down'));
|
$mailer->method('createAndSendEmail')->willThrowException(new \RuntimeException('SMTP down'));
|
||||||
|
|||||||
Reference in New Issue
Block a user