feat: inquiry bookings

This commit is contained in:
Björn Fromme
2026-03-16 11:59:10 +01:00
parent 2d9cb2c0b9
commit 023ecdebc9
11 changed files with 360 additions and 4 deletions
@@ -671,7 +671,7 @@ class BookingDataProcessor
$payload = [
'buchungsart' => $bookingType,
'status' => 'F',
'status' => $bookingDto->bookingStatus,
'idreise' => $bookingDto->travel->id,
'idpartner' => $bookingDto->travel->hotelId,
'idagentur' => $bookingDto->agencyId,
+7
View File
@@ -46,6 +46,13 @@ class Travel
#[Groups(['api:list', 'api:single'])]
public ?string $type = null;
/**
* Travel booking status from BusProNet API.
* Possible values: 'Frei', 'Anfrage', 'Buchungsstop'
*/
#[Groups(['api:single'])]
public ?string $status = null;
#[Groups(['api:list', 'api:single'])]
public ?float $priceFrom = null;
+1
View File
@@ -54,6 +54,7 @@ class TravelParser extends AbstractParser
$travel->dateTo = $this->stringToDate($node->attr('bis'));
$travel->code = $node->attr('code');
$travel->type = $node->attr('reiseart');
$travel->status = $this->getStringOrNullValue($node->filterXPath('//status_hin'));
$travel->priceFrom = $this->stringToFloat($this->getStringOrNullValue($node->filterXPath('//abpreis')));
$travel->selectionGroups = $this->getSelectionGroups($node->filterXPath('//selektiongruppe'));
$travel->additionalServices = $this
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Controller\Booking;
use App\BusProNet\XmlLoader\AgencyLoader;
use App\Exception\BookingNotPossibleException;
use App\Exception\HotelNotFoundException;
use App\Exception\HotelNotInTravelException;
use App\Exception\NoRoomsAvailableException;
@@ -64,6 +65,8 @@ class CreateInitController extends AbstractController
throw $this->createNotFoundException(sprintf('Hotel ID %d is not available for travel ID %d', $hotelId, $dateId));
} catch (NoRoomsAvailableException $e) {
throw $this->createNotFoundException('No rooms available for this travel.');
} catch (BookingNotPossibleException $e) {
throw $this->createNotFoundException('Booking is not possible for this travel (Buchungsstop).');
}
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Exception;
use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Exception thrown when attempting to create a booking for a travel with Buchungsstop status.
*/
class BookingNotPossibleException extends HttpException
{
public function __construct(int $dateId, int $hotelId, ?\Throwable $previous = null)
{
$message = sprintf(
'Für diese Reise ist aktuell keine Buchung möglich (Travel ID: %d, Hotel ID: %d). Status: Buchungsstop',
$dateId,
$hotelId
);
parent::__construct(400, $message, $previous);
}
}
+6
View File
@@ -47,6 +47,12 @@ class BookingDto
public ?int $agencyId = null;
/**
* Booking status code for API submission.
* Values: 'F' (Final/Frei), 'A' (Anfrage/Inquiry)
*/
public string $bookingStatus = 'F';
/**
* Reference to booking entity (only populated in edit mode).
*/
+27 -2
View File
@@ -4,6 +4,7 @@ namespace App\Service;
use App\BusProNet\Model\Room;
use App\BusProNet\Model\Travel;
use App\Exception\BookingNotPossibleException;
use App\Exception\BookingSessionNotFoundException;
use App\Exception\NoRoomsAvailableException;
use App\Form\Model\BookingDto;
@@ -166,6 +167,11 @@ class BookingService
* This method initializes a new BookingDto with empty room selections
* and saves it to the session. It's designed to be called from the clean
* booking entry point without requiring UID parameters.
*
* Handles three booking status types:
* - 'Frei': Regular booking with availability checks
* - 'Anfrage': Inquiry booking, allows booking even with 0 availability
* - 'Buchungsstop': Booking stopped, no bookings allowed
*/
public function startFreshBooking(Request $request, int $dateId, int $hotelId, ?int $agencyId = null): BookingDto
{
@@ -174,13 +180,31 @@ class BookingService
throw new NotFoundHttpException(sprintf('Travel data not found for date ID %d and hotel ID %d', $dateId, $hotelId));
}
// Handle Buchungsstop - no bookings allowed at all
if ('Buchungsstop' === $travelData->status) {
throw new BookingNotPossibleException($dateId, $hotelId);
}
// Determine booking status based on travel status
$isInquiryBooking = 'Anfrage' === $travelData->status;
$bookingStatus = $isInquiryBooking ? 'A' : 'F';
// Get available rooms
$availableRooms = $travelData->getAvailableRooms();
// Prevent booking flow entry when no rooms are available
if (empty($availableRooms)) {
// For regular bookings (Frei), prevent entry when no rooms are available
// For inquiry bookings (Anfrage), allow even with 0 availability
if (false === $isInquiryBooking && empty($availableRooms)) {
throw new NoRoomsAvailableException($dateId, $hotelId);
}
// For inquiry bookings with 0 availability, get all rooms ignoring availability count
if ($isInquiryBooking && empty($availableRooms)) {
$availableRooms = array_filter($travelData->rooms, function (Room $room) {
return \App\BusProNet\Constants::STATUS_AVAILABLE === $room->status;
});
}
// Create room selections with zero quantities (user will set these in step 1)
$roomSelections = array_map(
fn (Room $room) => $this->createRoomSelection($room, []),
@@ -191,6 +215,7 @@ class BookingService
$bookingCreateDto->roomSelections = $roomSelections;
$bookingCreateDto->currentStep = 1;
$bookingCreateDto->agencyId = $agencyId;
$bookingCreateDto->bookingStatus = $bookingStatus;
$this->saveBookingCreateDto($request, $bookingCreateDto);