feat: force booking status 'O' for selection id 1473

addresses #869d79q0r
This commit is contained in:
Björn Fromme
2026-06-11 15:53:56 +02:00
parent bcf75ce3c3
commit d207bc74cb
9 changed files with 318 additions and 5 deletions
+2
View File
@@ -211,12 +211,14 @@ services:
- '@App\Form\Service\ParticipantPromoVoucherFieldHandler'
# Booking Status Rules
App\BusProNet\Service\StatusRule\Selection1473StatusRule: ~
App\BusProNet\Service\StatusRule\ChaperonServiceStatusRule: ~
# Booking Status Rule Registry
App\BusProNet\Service\BookingStatusRuleRegistry:
arguments:
$rules:
- '@App\BusProNet\Service\StatusRule\Selection1473StatusRule'
- '@App\BusProNet\Service\StatusRule\ChaperonServiceStatusRule'
App\Service\CmsDataProvider:
+1 -1
View File
@@ -57,7 +57,7 @@ final class Constants
// Booking status codes from buchungstatusmoeglich attribute
public const BOOKING_STATUS_FREE = 'F';
public const BOOKING_STATUS_INQUIRY = 'A';
public const BOOKING_STATUS_OPEN = 'O'; // Optionsbuchung
public const BOOKING_STATUS_OPTION = 'O'; // Optionsbuchung
// Payment methods
public const PAYMENT_METHOD_TRANSFER = 'transfer';
+22
View File
@@ -218,6 +218,28 @@ class Travel
return $services;
}
/**
* Checks whether any CRM selection group contains the given selection ID.
*
* This scans all parsed groups and their selections, regardless of group.
*/
public function hasSelectionId(int $selectionId): bool
{
foreach ($this->selectionGroups as $selectionGroup) {
if (null === $selectionGroup->selections) {
continue;
}
foreach ($selectionGroup->selections as $selection) {
if ($selectionId === $selection->id) {
return true;
}
}
}
return false;
}
/**
* Retrieves rooms filtered by their IDs.
*
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Service\StatusRule;
use App\BusProNet\Service\Contract\BookingStatusRuleInterface;
use App\Form\Model\BookingDto;
/**
* Status rule for travels containing selection 1473.
*
* Forces Option status when the travel contains the CRM selection
* "NICHT-automatisch bestätigen".
*/
class Selection1473StatusRule implements BookingStatusRuleInterface
{
private const STATUS = 'O';
private const PRIORITY = 200;
private const SELECTION_ID = 1473;
public function evaluate(BookingDto $bookingDto): bool
{
return $bookingDto->travel->hasSelectionId(self::SELECTION_ID);
}
public function getStatus(): string
{
return self::STATUS;
}
public function getPriority(): int
{
return self::PRIORITY;
}
public function getDescription(): string
{
return 'Assigns Option status when selection 1473 is present';
}
}
+5 -3
View File
@@ -41,6 +41,7 @@ class BookingConfigurator
*
* Handles three booking status types:
* - 'Frei': Regular booking with availability checks
* - 'Option': Create-flow override from specific rules
* - 'Anfrage': Inquiry booking, allows booking even with 0 availability
*/
public function startFreshBooking(Request $request, int $dateId, int $hotelId, ?int $agencyId = null): BookingDto
@@ -83,6 +84,7 @@ class BookingConfigurator
? $this->agencyLoader->loadById($agencyId)?->code
: null;
$bookingCreateDto->bookingStatus = $bookingStatus;
$this->applyCreateBookingStatusRules($bookingCreateDto);
$this->bookingSessionService->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE);
@@ -587,13 +589,13 @@ class BookingConfigurator
return $this->defaultBookingStatus;
}
// Fall back to allowed statuses in order of preference: F → O → A
// Fall back to allowed statuses in order of preference: F → O(option) → A
if ($travelData->isBookingStatusAllowed(Constants::BOOKING_STATUS_FREE)) {
return Constants::BOOKING_STATUS_FREE;
}
if ($travelData->isBookingStatusAllowed(Constants::BOOKING_STATUS_OPEN)) {
return Constants::BOOKING_STATUS_OPEN;
if ($travelData->isBookingStatusAllowed(Constants::BOOKING_STATUS_OPTION)) {
return Constants::BOOKING_STATUS_OPTION;
}
if ($travelData->isBookingStatusAllowed(Constants::BOOKING_STATUS_INQUIRY)) {
+42
View File
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Tests\BusProNet\Model;
use App\BusProNet\Model\CrmSelection;
use App\BusProNet\Model\CrmSelectionGroup;
use App\BusProNet\Model\Service;
use App\BusProNet\Model\Travel;
use PHPUnit\Framework\TestCase;
@@ -109,4 +111,44 @@ class TravelTest extends TestCase
$this->assertSame('BUS', $result[0]->subType);
$this->assertSame('PKW', $result[1]->subType);
}
public function testHasSelectionIdReturnsTrueWhenSelectionExists(): void
{
$travel = new Travel();
$travel->selectionGroups = [
10 => $this->createSelectionGroup(10, [1473, 2001]),
11 => $this->createSelectionGroup(11, [3001]),
];
$this->assertTrue($travel->hasSelectionId(1473));
}
public function testHasSelectionIdReturnsFalseWhenSelectionDoesNotExist(): void
{
$travel = new Travel();
$travel->selectionGroups = [
10 => $this->createSelectionGroup(10, [2001]),
11 => $this->createSelectionGroup(11, [3001]),
];
$this->assertFalse($travel->hasSelectionId(1473));
}
/**
* @param list<int> $selectionIds
*/
private function createSelectionGroup(int $groupId, array $selectionIds): CrmSelectionGroup
{
$group = new CrmSelectionGroup();
$group->id = $groupId;
$group->selections = [];
foreach ($selectionIds as $selectionId) {
$selection = new CrmSelection();
$selection->id = $selectionId;
$group->selections[] = $selection;
}
return $group;
}
}
@@ -7,6 +7,7 @@ namespace App\Tests\BusProNet\Service;
use App\BusProNet\Model\Travel;
use App\BusProNet\Service\BookingStatusRuleRegistry;
use App\BusProNet\Service\Contract\BookingStatusRuleInterface;
use App\BusProNet\Service\StatusRule\Selection1473StatusRule;
use App\Form\Model\BookingDto;
use PHPUnit\Framework\TestCase;
@@ -99,6 +100,22 @@ class BookingStatusRuleRegistryTest extends TestCase
$this->assertSame('L', $registry->evaluateStatus($bookingDto));
}
public function testSelection1473RuleWinsOverLowerPriorityRule(): void
{
$lowerPriorityRule = $this->createMock(BookingStatusRuleInterface::class);
$lowerPriorityRule->method('evaluate')->willReturn(true);
$lowerPriorityRule->method('getStatus')->willReturn('X');
$lowerPriorityRule->method('getPriority')->willReturn(100);
$registry = new BookingStatusRuleRegistry([
$lowerPriorityRule,
new Selection1473StatusRule(),
]);
$bookingDto = new BookingDto($this->createMockTravelWithSelection1473(), 1);
$this->assertSame('O', $registry->evaluateStatus($bookingDto));
}
private function createMockTravel(): Travel
{
$travel = new Travel();
@@ -109,4 +126,32 @@ class BookingStatusRuleRegistryTest extends TestCase
return $travel;
}
private function createMockTravelWithSelection1473(): Travel
{
$travel = $this->createMockTravel();
$travel->selectionGroups = [
1 => $this->createSelectionGroup(1, [1473]),
];
return $travel;
}
/**
* @param list<int> $selectionIds
*/
private function createSelectionGroup(int $groupId, array $selectionIds): \App\BusProNet\Model\CrmSelectionGroup
{
$group = new \App\BusProNet\Model\CrmSelectionGroup();
$group->id = $groupId;
$group->selections = [];
foreach ($selectionIds as $selectionId) {
$selection = new \App\BusProNet\Model\CrmSelection();
$selection->id = $selectionId;
$group->selections[] = $selection;
}
return $group;
}
}
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace App\Tests\BusProNet\Service\StatusRule;
use App\BusProNet\Model\CrmSelection;
use App\BusProNet\Model\CrmSelectionGroup;
use App\BusProNet\Model\Travel;
use App\BusProNet\Service\StatusRule\Selection1473StatusRule;
use App\Form\Model\BookingDto;
use PHPUnit\Framework\TestCase;
class Selection1473StatusRuleTest extends TestCase
{
private Selection1473StatusRule $rule;
protected function setUp(): void
{
$this->rule = new Selection1473StatusRule();
}
public function testEvaluatesTrueWhenSelection1473Exists(): void
{
$bookingDto = new BookingDto($this->createTravelWithSelectionIds([1473]), 1);
$this->assertTrue($this->rule->evaluate($bookingDto));
}
public function testEvaluatesFalseWhenSelection1473DoesNotExist(): void
{
$bookingDto = new BookingDto($this->createTravelWithSelectionIds([2001]), 1);
$this->assertFalse($this->rule->evaluate($bookingDto));
}
public function testReturnsStatusO(): void
{
$this->assertSame('O', $this->rule->getStatus());
}
public function testReturnsPriority200(): void
{
$this->assertSame(200, $this->rule->getPriority());
}
public function testReturnsDescription(): void
{
$this->assertSame('Assigns Option status when selection 1473 is present', $this->rule->getDescription());
}
/**
* @param list<int> $selectionIds
*/
private function createTravelWithSelectionIds(array $selectionIds): Travel
{
$travel = new Travel();
$travel->id = 1;
$travel->hotelId = 1;
$travel->dateFrom = new \DateTimeImmutable('2030-01-01');
$travel->dateTo = new \DateTimeImmutable('2030-01-08');
$travel->selectionGroups = [
1 => $this->createSelectionGroup(1, $selectionIds),
];
return $travel;
}
/**
* @param list<int> $selectionIds
*/
private function createSelectionGroup(int $groupId, array $selectionIds): CrmSelectionGroup
{
$group = new CrmSelectionGroup();
$group->id = $groupId;
$group->selections = [];
foreach ($selectionIds as $selectionId) {
$selection = new CrmSelection();
$selection->id = $selectionId;
$group->selections[] = $selection;
}
return $group;
}
}
@@ -6,11 +6,14 @@ namespace App\Tests\Service;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\BusProNet\Model\CrmSelection;
use App\BusProNet\Model\CrmSelectionGroup;
use App\BusProNet\Constants;
use App\BusProNet\Model\Room;
use App\BusProNet\Model\Service;
use App\BusProNet\Model\Travel;
use App\BusProNet\Service\BookingStatusRuleRegistry;
use App\BusProNet\Service\StatusRule\Selection1473StatusRule;
use App\BusProNet\XmlLoader\AgencyLoader;
use App\Exception\NoRoomsAvailableException;
use App\Service\BookingConfigurator;
@@ -174,6 +177,57 @@ class BookingConfiguratorStatusTest extends TestCase
$this->assertSame('A', $bookingDto->bookingStatus);
}
public function testStartFreshBookingWithSelection1473UsesOptionStatus(): void
{
$bookingService = new BookingConfigurator(
$this->createMock(BookingSessionManager::class),
$this->travelDataService,
$this->createMock(ParticipantEligibilityChecker::class),
new BookingStatusRuleRegistry([
new Selection1473StatusRule(),
]),
$this->createMock(AgencyLoader::class),
'F'
);
$travel = $this->createTravelWithRooms(
[
['status' => Constants::STATUS_AVAILABLE, 'available' => 5],
],
[$this->createSelectionGroupWithSelectionId(1473)]
);
$this->travelDataService
->method('getTravelData')
->willReturn($travel);
$request = $this->createRequestWithSession();
$bookingDto = $bookingService->startFreshBooking($request, 123, 456);
$this->assertSame('O', $bookingDto->bookingStatus);
}
public function testStartFreshBookingWithInquiryRoomsAndSelection1473KeepsInquiryStatus(): void
{
$travel = $this->createTravelWithRooms(
[
['status' => Constants::STATUS_ON_REQUEST, 'available' => 5],
],
[$this->createSelectionGroupWithSelectionId(1473)]
);
$this->travelDataService
->method('getTravelData')
->willReturn($travel);
$request = $this->createRequestWithSession();
$bookingDto = $this->bookingService->startFreshBooking($request, 123, 456);
$this->assertSame('A', $bookingDto->bookingStatus);
}
public function testApplyCreateBookingStatusRulesSetsOptionFromRegistry(): void
{
$bookingStatusRuleRegistry = $this->createMock(BookingStatusRuleRegistry::class);
@@ -236,7 +290,7 @@ class BookingConfiguratorStatusTest extends TestCase
/**
* @param array<array{status: string, available: int}> $roomsConfig
*/
private function createTravelWithRooms(array $roomsConfig): Travel
private function createTravelWithRooms(array $roomsConfig, array $selectionGroups = []): Travel
{
$travel = new Travel();
$travel->id = 123;
@@ -244,6 +298,7 @@ class BookingConfiguratorStatusTest extends TestCase
$travel->dateFrom = new \DateTimeImmutable('2030-01-01');
$travel->dateTo = new \DateTimeImmutable('2030-01-06');
$travel->allowedBookingStatus = []; // Empty means no API restrictions
$travel->selectionGroups = $selectionGroups;
$rooms = [];
foreach ($roomsConfig as $index => $config) {
@@ -265,6 +320,24 @@ class BookingConfiguratorStatusTest extends TestCase
return $travel;
}
/**
* @param list<int> $selectionIds
*/
private function createSelectionGroupWithSelectionId(int $groupId, array $selectionIds = [1473]): CrmSelectionGroup
{
$group = new CrmSelectionGroup();
$group->id = $groupId;
$group->selections = [];
foreach ($selectionIds as $selectionId) {
$selection = new CrmSelection();
$selection->id = $selectionId;
$group->selections[] = $selection;
}
return $group;
}
private function createRequestWithSession(): Request
{
$session = new Session(new MockArraySessionStorage());