feat: refactored accommodation booking status model
This commit is contained in:
@@ -11,8 +11,8 @@ use App\Entity\Groups\AccommodationBooking;
|
||||
use App\Entity\Groups\AccommodationPrice;
|
||||
use App\Entity\Groups\AdditionalService;
|
||||
use App\Entity\Groups\BoardService;
|
||||
use App\Enum\Groups\AccommodationBookingOrigin;
|
||||
use App\Enum\Groups\AccommodationBookingStatus;
|
||||
use App\Enum\Groups\AccommodationBookingType;
|
||||
use App\Form\Model\AccommodationBookingDto;
|
||||
use App\Model\AccommodationBookingQueryParams;
|
||||
use App\Model\CmsHotelData;
|
||||
@@ -268,12 +268,12 @@ class AccommodationBookingService
|
||||
$booking->setPaxCount($dto->paxCount);
|
||||
$booking->setMinorsCount($dto->minorsCount);
|
||||
$booking->setChildrenCount($dto->childrenCount);
|
||||
// An inquiry arrives live — the office has nothing to prepare first, so it goes
|
||||
// straight to Offen. A direct booking has been committed to by the customer but
|
||||
// still awaits the office's validation, hence Eingegangen rather than Bestätigt.
|
||||
// An inquiry still needs an offer the office has to prepare, so it waits at Angefragt
|
||||
// until that offer goes out. A direct booking has been committed to by the customer
|
||||
// but still awaits the office's validation, hence Eingegangen rather than Bestätigt.
|
||||
$isInquiry = $dto->forceInquiry || $this->computeInquiryStatus($dto, $prices)->isInquiry;
|
||||
$booking->setType($isInquiry ? AccommodationBookingType::Inquiry : AccommodationBookingType::Booking);
|
||||
$booking->setStatus($isInquiry ? AccommodationBookingStatus::Open : AccommodationBookingStatus::Received);
|
||||
$booking->setOrigin($isInquiry ? AccommodationBookingOrigin::Offer : AccommodationBookingOrigin::Direct);
|
||||
$booking->setStatus($isInquiry ? AccommodationBookingStatus::Requested : AccommodationBookingStatus::Received);
|
||||
if (!$isInquiry) {
|
||||
$booking->setAcceptedAt(new \DateTimeImmutable());
|
||||
}
|
||||
@@ -342,7 +342,7 @@ class AccommodationBookingService
|
||||
// A direct booking gets no customer mail here: it only reaches the customer once the
|
||||
// office has validated it and confirmed via confirmBooking(). Acknowledging an
|
||||
// inquiry is a different matter — it promises nothing, so it still goes out.
|
||||
if ($booking->isInquiry()) {
|
||||
if ($booking->isFromOffer()) {
|
||||
$this->sendCustomerConfirmationEmail($booking);
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ class AccommodationBookingService
|
||||
$this->accommodationEmail,
|
||||
sprintf(
|
||||
'Neue Unterkunfts%s: %s',
|
||||
$booking->isInquiry() ? 'anfrage' : 'buchung',
|
||||
$booking->isDirectBooking() ? 'buchung' : 'anfrage',
|
||||
$booking->getAccommodation()?->getName(),
|
||||
),
|
||||
'email/accommodation_booking.html.twig',
|
||||
@@ -457,7 +457,7 @@ class AccommodationBookingService
|
||||
*/
|
||||
public function issueAccessLinkForDirectBooking(AccommodationBooking $booking): void
|
||||
{
|
||||
if (!$booking->isBooking()) {
|
||||
if (!$booking->isDirectBooking()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -465,13 +465,14 @@ class AccommodationBookingService
|
||||
}
|
||||
|
||||
/**
|
||||
* Always sends a confirmation email to the customer, whether or not an access link
|
||||
* exists yet — the template renders differently depending on accessLink being present,
|
||||
* so the subject has to follow the same distinction.
|
||||
* Serves both customer-facing messages of the pre-booking phase: the acknowledgement
|
||||
* that an inquiry arrived, and the offer itself. Offen is the status that means the
|
||||
* offer is out, so it is what decides the subject — the template makes the same
|
||||
* distinction via accessLink, which is always issued as the offer goes out.
|
||||
*/
|
||||
public function sendCustomerConfirmationEmail(AccommodationBooking $booking): void
|
||||
{
|
||||
$subject = null !== $booking->getAccessLinkIssuedAt()
|
||||
$subject = $booking->isOpen()
|
||||
? 'Dein Angebot ist bereit'
|
||||
: 'Deine Anfrage ist bei uns eingegangen';
|
||||
|
||||
@@ -496,9 +497,45 @@ class AccommodationBookingService
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts an open offer and notifies the office. The type stays Anfrage —
|
||||
* only the status moves to Eingegangen, so an offer-originated booking remains
|
||||
* distinguishable from a direct one.
|
||||
* Publishes the prepared offer: the single action that puts an offer in front of the
|
||||
* customer, and the only transition into Offen. Issues the access link the email links
|
||||
* to, so the two can never drift apart the way they could when sending was a side
|
||||
* effect of editing.
|
||||
* Idempotent — a no-op (including no email) for anything that is not awaiting an offer.
|
||||
*/
|
||||
public function sendOffer(AccommodationBooking $booking): void
|
||||
{
|
||||
if (!$booking->isDraft() && !$booking->isRequested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->issueAccessLink($booking);
|
||||
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->sendCustomerConfirmationEmail($booking);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a booking that is not going to happen. Deliberately silent: the office tells
|
||||
* the customer itself, so this only records the outcome.
|
||||
* Idempotent — a no-op for a booking that is already confirmed or discarded, since a
|
||||
* released confirmation is binding and must not be withdrawn behind the customer's back.
|
||||
*/
|
||||
public function discardBooking(AccommodationBooking $booking): void
|
||||
{
|
||||
if ($booking->isConfirmed() || $booking->isDiscarded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$booking->setStatus(AccommodationBookingStatus::Discarded);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts an offer that is out with the customer and notifies the office. The origin
|
||||
* stays untouched, so the booking remains recognisable as offer-originated for the
|
||||
* rest of its life; only the status moves on to Eingegangen.
|
||||
* Idempotent — a no-op (including no emails) if the offer is not open any more.
|
||||
*
|
||||
* The customer deliberately gets no mail here: nothing is confirmed until the office
|
||||
@@ -509,7 +546,9 @@ class AccommodationBookingService
|
||||
*/
|
||||
public function acceptBooking(AccommodationBooking $booking, ?string $remarks = null): void
|
||||
{
|
||||
if (!$booking->isInquiry() || !$booking->isOpen()) {
|
||||
// Offen means exactly one thing — an offer is out and awaiting this acceptance —
|
||||
// so the status alone is the whole guard.
|
||||
if (!$booking->isOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user