feat: api endpoints and xml sync for contingents data

This commit is contained in:
Björn Fromme
2026-04-22 11:23:52 +02:00
parent a1f68ec11d
commit 9b84893d88
50 changed files with 2567 additions and 221 deletions
@@ -5,8 +5,8 @@ declare(strict_types=1);
namespace App\Tests\Service;
use App\BusProNet\Constants;
use App\BusProNet\Model\Service;
use App\BusProNet\Model\Room;
use App\BusProNet\Model\Service;
use App\BusProNet\Model\Travel;
use App\BusProNet\Service\BookingStatusRuleRegistry;
use App\BusProNet\XmlLoader\AgencyLoader;
@@ -8,13 +8,13 @@ use App\BusProNet\Model\Room;
use App\BusProNet\Model\Travel;
use App\Form\Model\BookingCreateContext;
use App\Form\Model\BookingDto;
use App\Form\Model\BookingSummaryDto;
use App\Form\Model\ParticipantCardDataDto;
use App\Form\Model\ParticipantCardPriceDto;
use App\Form\Model\BookingSummaryDto;
use App\Service\BookingCreateContextFactory;
use App\Service\ParticipantCardAssembler;
use App\Service\BookingSummaryAssembler;
use App\Service\BookingPriceCalculator;
use App\Service\BookingSummaryAssembler;
use App\Service\ParticipantCardAssembler;
use App\Service\RoomPricingCalculator;
use PHPUnit\Framework\TestCase;
@@ -9,8 +9,8 @@ use App\BusProNet\Model\Booking;
use App\BusProNet\Model\MutableData;
use App\BusProNet\Model\Travel;
use App\Form\Model\BookingDto;
use App\Form\Model\BookingMutabilityDto;
use App\Form\Model\BookingEditContext;
use App\Form\Model\BookingMutabilityDto;
use App\Form\Model\BookingSummaryDto;
use App\Service\BookingEditContextFactory;
use App\Service\BookingSummaryAssembler;
@@ -13,9 +13,9 @@ use App\Entity\User;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\Repository\BookingEditDraftRepository;
use App\Service\BookingChangeTracker;
use App\Service\BookingEditDraftManager;
use App\Service\BookingEditDraftMerger;
use App\Service\BookingChangeTracker;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
+5 -5
View File
@@ -422,11 +422,11 @@ class BookingEditSubmitterTest extends TestCase
private function createService(
?\App\BusProNet\ApiClient $apiClient = null,
?BookingEditDataLoader $dataLoader = null,
?BookingEditDraftManager $draftService = null,
?TravelDataProvider $travelDataService = null,
?BookingEditSubmitGuard $submitGuard = null,
?BookingSessionManager $bookingSessionService = null,
?BookingEditDataLoader $dataLoader = null,
?BookingEditDraftManager $draftService = null,
?TravelDataProvider $travelDataService = null,
?BookingEditSubmitGuard $submitGuard = null,
?BookingSessionManager $bookingSessionService = null,
): BookingEditSubmitter {
return new BookingEditSubmitter(
$apiClient ?? $this->createMock(\App\BusProNet\ApiClient::class),
@@ -11,8 +11,8 @@ use App\BusProNet\Model\Travel;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\Form\Model\RoomSelectionDto;
use App\Service\BookingPricingAssembler;
use App\Service\BookingPriceMismatchAnalyzer;
use App\Service\BookingPricingAssembler;
use PHPUnit\Framework\TestCase;
class BookingPriceMismatchAnalyzerTest extends TestCase
@@ -4,9 +4,11 @@ declare(strict_types=1);
namespace App\Tests\Service;
use App\BusProNet\DataProvider\CountryDataProvider;
use App\BusProNet\Model\Booking;
use App\BusProNet\Model\Room;
use App\BusProNet\Model\Travel;
use App\BusProNet\XmlLoader\HotelLoader;
use App\Form\Model\BookingDto;
use App\Form\Model\BookingSummaryDto;
use App\Form\Model\ParticipantDto;
@@ -15,8 +17,6 @@ use App\Service\BookingPriceCalculator;
use App\Service\BookingPricingAssembler;
use App\Service\BookingSummaryAssembler;
use App\Service\CmsDataProvider;
use App\BusProNet\DataProvider\CountryDataProvider;
use App\BusProNet\XmlLoader\HotelLoader;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Contracts\Cache\CacheInterface;
+163
View File
@@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
namespace App\Tests\Service;
use App\BusProNet\Model\Travel;
use App\BusProNet\Utility\BookingUrlUtility;
use App\BusProNet\XmlLoader\ContingentLoader;
use App\BusProNet\XmlLoader\TravelLoader;
use App\Service\ContingentDataService;
use PHPUnit\Framework\TestCase;
class ContingentDataServiceTest extends TestCase
{
private ContingentLoader $contingentLoader;
private TravelLoader $travelLoader;
private ContingentDataService $service;
protected function setUp(): void
{
$this->contingentLoader = $this->createMock(ContingentLoader::class);
$this->travelLoader = $this->createMock(TravelLoader::class);
$this->service = new ContingentDataService(
$this->contingentLoader,
$this->travelLoader,
new BookingUrlUtility('https://my.ep-reisen.de'),
);
}
public function testGetAvailableContingentsAggregatesAndAppliesBelKalOverride(): void
{
$travel = new Travel();
$travel->dateFrom = new \DateTimeImmutable('2026-02-01');
$travel->dateTo = new \DateTimeImmutable('2026-02-03');
$this->travelLoader->method('loadById')->with(123, 10)->willReturn($travel);
$this->contingentLoader->method('loadByHotelId')->with(10)->willReturn([
'rows' => [
[
'date' => new \DateTimeImmutable('2026-02-01'),
'isControlRoom' => false,
'available' => 4,
'pax' => 8,
'minNights' => 3,
'status' => 'OK',
],
[
'date' => new \DateTimeImmutable('2026-02-01'),
'isControlRoom' => false,
'available' => 2,
'pax' => 4,
'minNights' => 2,
'status' => 'OK',
],
[
'date' => new \DateTimeImmutable('2026-02-01'),
'isControlRoom' => true,
'available' => 0,
'pax' => 0,
'minNights' => null,
'status' => 'BLOCKED',
],
],
]);
$result = $this->service->getAvailableContingents(10, 123);
$this->assertArrayHasKey('2026-02-01', $result);
$summary = $result['2026-02-01'];
$this->assertSame('2026-02-01', $summary->date);
$this->assertSame(2, $summary->minNights);
$this->assertSame(12, $summary->capacity);
$this->assertSame(0, $summary->total); // BelKal override
}
public function testGetAvailableRoomsRejectsRangeOutsideTravelPeriod(): void
{
$travel = new Travel();
$travel->dateFrom = new \DateTimeImmutable('2026-02-10');
$travel->dateTo = new \DateTimeImmutable('2026-02-20');
$this->travelLoader->method('loadById')->with(123, 10)->willReturn($travel);
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Requested range must be within the travel date range');
$this->service->getAvailableRooms('2026-02-01', '2026-02-05', 10, 123);
}
public function testGetAvailableRoomsCalculatesPriceAndAppliesBelKalStatusOverride(): void
{
$travel = new Travel();
$travel->dateFrom = new \DateTimeImmutable('2026-02-01');
$travel->dateTo = new \DateTimeImmutable('2026-02-10');
$this->travelLoader->method('loadById')->with(123, 10)->willReturn($travel);
$this->contingentLoader->method('loadByHotelId')->with(10)->willReturn([
'rows' => [
[
'date' => new \DateTimeImmutable('2026-02-03'),
'isControlRoom' => true,
'status' => 'ON_REQUEST',
],
[
'date' => new \DateTimeImmutable('2026-02-03'),
'isControlRoom' => false,
'roomCode' => 'DZ',
'roomLabel' => 'Double',
'pax' => 4,
'available' => 2,
'status' => 'OK',
'minPrice' => 100.0,
'minNights' => 2,
'additionalNightMinPrice' => 30.0,
'additionalNightMinNights' => 1,
],
],
]);
$result = $this->service->getAvailableRooms('2026-02-03', '2026-02-06', 10, 123);
$this->assertCount(1, $result);
$room = $result[0];
$this->assertSame('2026-02-03', $room->date);
$this->assertSame('ON_REQUEST', $room->status);
$this->assertSame(0, $room->available); // overridden
$this->assertSame(130.0, $room->priceForSelection); // 100 + (3-2)*30
$this->assertSame('https://my.ep-reisen.de/bookings/create?date_id=123&hotel_id=10', $room->bookingUrl);
}
public function testGetCalendarEventsUsesWorstStatusAndExcludesControlRoomFromSums(): void
{
$this->contingentLoader->method('loadByHotelId')->with(10)->willReturn([
'rows' => [
[
'date' => new \DateTimeImmutable('2026-02-03'),
'isControlRoom' => false,
'status' => 'OK',
'pax' => 6,
'available' => 2,
],
[
'date' => new \DateTimeImmutable('2026-02-03'),
'isControlRoom' => true,
'status' => 'BLOCKED',
'pax' => 0,
'available' => 0,
],
],
]);
$events = $this->service->getCalendarEvents(10, '2026-02-01', '2026-02-10');
$this->assertCount(1, $events);
$event = $events[0];
$this->assertSame('2026-02-03', $event->date);
$this->assertSame('BLOCKED', $event->status);
$this->assertSame(6, $event->pax);
$this->assertSame(2, $event->available);
}
}