feat: offer contact validation flow

This commit is contained in:
2026-09-16 10:40:01 +02:00
parent 5d7ffd7d58
commit 12fe4d8e15
15 changed files with 1219 additions and 554 deletions
+62 -10
View File
@@ -14,6 +14,7 @@ use App\Entity\Groups\BoardService;
use App\Enum\Groups\AccommodationBookingOrigin;
use App\Enum\Groups\AccommodationBookingStatus;
use App\Form\Model\AccommodationBookingDto;
use App\Model\AccommodationBookingContext;
use App\Model\AccommodationBookingQueryParams;
use App\Model\CmsHotelData;
use App\Model\InquiryStatus;
@@ -23,6 +24,7 @@ use App\Repository\Groups\AdditionalServiceRepository;
use App\Repository\Groups\BoardServiceRepository;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class AccommodationBookingService
{
@@ -41,6 +43,7 @@ class AccommodationBookingService
private readonly AccommodationBookingLinkSigner $linkSigner,
private readonly AccommodationBookingBreakdownCalculator $breakdownCalculator,
private readonly AccommodationBookingPdfGenerator $pdfGenerator,
private readonly ValidatorInterface $validator,
private readonly string $accommodationEmail,
) {
}
@@ -115,6 +118,19 @@ class AccommodationBookingService
return $this->cmsDataProvider->getHotelDetails($accommodation->getEffectiveCmsCode());
}
/**
* What the customer-facing offer pages show around a stored booking: the hotel's CMS
* content and the price breakdown computed from the booking's frozen snapshot.
*/
public function createOfferContext(AccommodationBooking $booking, Accommodation $accommodation): AccommodationBookingContext
{
return new AccommodationBookingContext(
accommodation: $accommodation,
hotelCmsData: $this->loadHotelCmsData($accommodation),
priceBreakdown: $this->breakdownCalculator->compute($booking),
);
}
/**
* @return AccommodationPrice[]
*/
@@ -262,7 +278,6 @@ class AccommodationBookingService
): AccommodationBooking {
$booking = new AccommodationBooking();
$booking->setAccommodation($accommodation);
$booking->setGroupName($dto->groupName);
$booking->setDateFrom($dto->dateFrom);
$booking->setDateTo($dto->dateTo);
$booking->setPaxCount($dto->paxCount);
@@ -305,15 +320,7 @@ class AccommodationBookingService
);
}
// Personal data
$booking->setSalutation($dto->salutation);
$booking->setFirstName($dto->firstName);
$booking->setLastName($dto->lastName);
$booking->setEmail($dto->email);
$booking->setPhone($dto->phone);
$booking->setStreet($dto->street);
$booking->setZip($dto->zip);
$booking->setCity($dto->city);
$this->copyContactData($dto, $booking);
$booking->setRemarks($dto->remarks);
$this->refreshPriceSnapshot($booking);
@@ -627,6 +634,51 @@ class AccommodationBookingService
$this->sendOfferAcceptedNotificationEmail($booking);
}
/**
* Offers are put together by the office, which only has to supply name and email, so
* the rest of the contact data is often missing. A customer may accept an offer once it
* meets the constraints a self-service booking meets in step 3.
*/
public function hasCompleteContactData(AccommodationBooking $booking): bool
{
return 0 === $this->validator->validate($this->contactDataFromBooking($booking), null, ['contact'])->count();
}
public function contactDataFromBooking(AccommodationBooking $booking): AccommodationBookingDto
{
$dto = new AccommodationBookingDto();
$dto->groupName = $booking->getGroupName();
$dto->salutation = $booking->getSalutation();
$dto->firstName = $booking->getFirstName();
$dto->lastName = $booking->getLastName();
$dto->email = $booking->getEmail();
$dto->phone = $booking->getPhone();
$dto->street = $booking->getStreet();
$dto->zip = $booking->getZip();
$dto->city = $booking->getCity();
return $dto;
}
public function applyContactData(AccommodationBooking $booking, AccommodationBookingDto $dto): void
{
$this->copyContactData($dto, $booking);
$this->entityManager->flush();
}
private function copyContactData(AccommodationBookingDto $dto, AccommodationBooking $booking): void
{
$booking->setGroupName($dto->groupName);
$booking->setSalutation($dto->salutation);
$booking->setFirstName($dto->firstName);
$booking->setLastName($dto->lastName);
$booking->setEmail($dto->email);
$booking->setPhone($dto->phone);
$booking->setStreet($dto->street);
$booking->setZip($dto->zip);
$booking->setCity($dto->city);
}
/**
* Explicit office action after the requested services and capacities have been validated:
* this is the moment the booking becomes binding for the customer, and the only place the