From ca7d0b69651c590567ade24dcc7bb7a521ce8f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Wed, 18 Mar 2026 15:24:52 +0100 Subject: [PATCH] feat: extended price mismatch diagnostics in logs --- .../Booking/Create/Step3Controller.php | 10 ++ ...BookingPriceMismatchDiagnosticsService.php | 156 ++++++++++++++++++ ...ingPriceMismatchDiagnosticsServiceTest.php | 78 +++++++++ 3 files changed, 244 insertions(+) create mode 100644 src/Service/BookingPriceMismatchDiagnosticsService.php create mode 100644 tests/Service/BookingPriceMismatchDiagnosticsServiceTest.php diff --git a/src/Controller/Booking/Create/Step3Controller.php b/src/Controller/Booking/Create/Step3Controller.php index 5f929cc..ff5092f 100644 --- a/src/Controller/Booking/Create/Step3Controller.php +++ b/src/Controller/Booking/Create/Step3Controller.php @@ -14,6 +14,7 @@ use App\Form\BookingCreateStep3Type; use App\Form\Model\BookingDto; use App\Htmx\HxTrait; use App\Service\BookingPriceCalculatorService; +use App\Service\BookingPriceMismatchDiagnosticsService; use App\Service\BookingService; use App\Service\BookingSummaryDataService; use Psr\Log\LoggerInterface; @@ -36,6 +37,7 @@ class Step3Controller extends AbstractController private readonly BookingService $bookingService, private readonly BookingSummaryDataService $summaryDataService, private readonly BookingPriceCalculatorService $priceCalculator, + private readonly BookingPriceMismatchDiagnosticsService $priceMismatchDiagnostics, private readonly ApiClient $apiClient, private readonly LoggerInterface $logger, ) { @@ -126,6 +128,9 @@ class Step3Controller extends AbstractController $expectedTotal = round($calculatedSubtotal - $promoGoodwillDiscount - $apiAppliedDiscount, 2); if ((int) round($apiTotal * 100) !== (int) round($expectedTotal * 100)) { + $diagnostics = $this->priceMismatchDiagnostics->buildDiagnostics($bookingCreateDto, $inquiryResponse); + $deltaBreakdown = $diagnostics['deltaBreakdown'] ?? []; + return $this->handleApiError( 'Price mismatch detected - payload incomplete', [ @@ -135,6 +140,11 @@ class Step3Controller extends AbstractController 'apiAppliedDiscount' => $apiAppliedDiscount, 'expectedTotal' => $expectedTotal, 'difference' => abs($apiTotal - $expectedTotal), + 'deltaTotal' => $deltaBreakdown['deltaTotal'] ?? null, + 'deltaRoom' => $deltaBreakdown['deltaRoom'] ?? null, + 'deltaService' => $deltaBreakdown['deltaService'] ?? null, + 'deltaInsurance' => $deltaBreakdown['deltaInsurance'] ?? null, + 'diagnostics' => $diagnostics, ], 'Preisabweichung festgestellt. Bitte wende dich an den Kundenservice.', $bookingCreateDto, diff --git a/src/Service/BookingPriceMismatchDiagnosticsService.php b/src/Service/BookingPriceMismatchDiagnosticsService.php new file mode 100644 index 0000000..2819480 --- /dev/null +++ b/src/Service/BookingPriceMismatchDiagnosticsService.php @@ -0,0 +1,156 @@ + + */ + public function buildDiagnostics(BookingDto $bookingCreateDto, BookingResponse $response): array + { + $pricingBreakdown = $this->priceCalculator->getPricingBreakdown($bookingCreateDto); + + $roomLines = $pricingBreakdown['rooms'] ?? []; + $serviceGroups = $pricingBreakdown['services'] ?? []; + + $roomTotal = round(array_sum(array_column($roomLines, 'totalPrice')), 2); + $serviceTotal = round(array_sum(array_column($serviceGroups, 'groupTotal')), 2); + $insuranceTotal = round($this->extractInsuranceTotal($serviceGroups), 2); + + $selectedRooms = []; + foreach ($bookingCreateDto->getSelectedRooms() as $roomSelection) { + $selectedRooms[] = [ + 'roomId' => $roomSelection->id, + 'quantity' => $roomSelection->quantity, + 'unitPrice' => $roomSelection->price, + 'capacity' => $roomSelection->capacity, + ]; + } + + $participantInsurance = []; + foreach ($bookingCreateDto->getParticipants() as $index => $participant) { + $participantInsurance[] = [ + 'participantIndex' => $index, + 'bulkInsuranceBooking' => $participant->bulkInsuranceBooking, + 'insuranceId' => $participant->insurance?->id, + 'insurancePrice' => $participant->insurance?->price, + 'assignedRoomId' => $participant->assignedRoomId, + ]; + } + + $apiPriceItems = array_map(static function (PriceItem $item): array { + return [ + 'position' => $item->position, + 'type' => $item->type, + 'subType' => $item->subType, + 'id' => $item->id, + 'quantity' => $item->quantity, + 'totalPrice' => round($item->totalPrice, 2), + ]; + }, $response->priceItems); + + $apiRoomTotal = round($this->sumApiRoomTotal($response->priceItems), 2); + $apiInsuranceTotal = round($this->sumApiInsuranceTotal($response->priceItems), 2); + $apiTotal = round($response->totalPrice ?? 0.0, 2); + $apiServiceTotal = round($apiTotal - $apiRoomTotal, 2); + + $deltaTotal = round(($pricingBreakdown['grandTotal'] ?? 0.0) - $apiTotal, 2); + $deltaRoom = round($roomTotal - $apiRoomTotal, 2); + $deltaService = round($serviceTotal - $apiServiceTotal, 2); + $deltaInsurance = round($insuranceTotal - $apiInsuranceTotal, 2); + + return [ + 'localBreakdown' => [ + 'grandTotal' => round($pricingBreakdown['grandTotal'] ?? 0.0, 2), + 'roomTotal' => $roomTotal, + 'serviceTotal' => $serviceTotal, + 'insuranceTotal' => $insuranceTotal, + ], + 'apiBreakdown' => [ + 'total' => $apiTotal, + 'roomTotal' => $apiRoomTotal, + 'serviceTotal' => $apiServiceTotal, + 'insuranceTotal' => $apiInsuranceTotal, + ], + 'deltaBreakdown' => [ + 'deltaTotal' => $deltaTotal, + 'deltaRoom' => $deltaRoom, + 'deltaService' => $deltaService, + 'deltaInsurance' => $deltaInsurance, + ], + 'roomLines' => $roomLines, + 'serviceGroups' => $serviceGroups, + 'selectedRooms' => $selectedRooms, + 'roomAssignmentCounts' => $bookingCreateDto->getRoomAssignmentCounts(), + 'participantInsurance' => $participantInsurance, + 'apiPriceItems' => $apiPriceItems, + ]; + } + + /** + * @param array> $serviceGroups + */ + private function extractInsuranceTotal(array $serviceGroups): float + { + $total = 0.0; + + foreach ($serviceGroups as $group) { + foreach (($group['services'] ?? []) as $service) { + $subType = $service['subType'] ?? null; + if (null === $subType || false === in_array($subType, Constants::TOKEN_INSURANCES, true)) { + continue; + } + + $total += (float) ($service['totalPrice'] ?? 0.0); + } + } + + return $total; + } + + /** + * @param array $priceItems + */ + private function sumApiRoomTotal(array $priceItems): float + { + $total = 0.0; + foreach ($priceItems as $item) { + if ('UNT' === $item->type) { + $total += $item->totalPrice; + } + } + + return $total; + } + + /** + * @param array $priceItems + */ + private function sumApiInsuranceTotal(array $priceItems): float + { + $total = 0.0; + foreach ($priceItems as $item) { + if (null !== $item->subType && true === in_array($item->subType, Constants::TOKEN_INSURANCES, true)) { + $total += $item->totalPrice; + } + } + + return $total; + } +} diff --git a/tests/Service/BookingPriceMismatchDiagnosticsServiceTest.php b/tests/Service/BookingPriceMismatchDiagnosticsServiceTest.php new file mode 100644 index 0000000..5c76c9b --- /dev/null +++ b/tests/Service/BookingPriceMismatchDiagnosticsServiceTest.php @@ -0,0 +1,78 @@ +createMock(BookingPriceCalculatorService::class); + $priceCalculator->method('getPricingBreakdown')->willReturn([ + 'rooms' => [ + ['roomId' => 74, 'totalPrice' => 444.0], + ], + 'services' => [ + [ + 'groupName' => 'Reiseversicherungen', + 'groupTotal' => 69.5, + 'services' => [ + ['subType' => 'RRV', 'totalPrice' => 115.0], + ], + ], + ], + 'grandTotal' => 513.5, + ]); + + $service = new BookingPriceMismatchDiagnosticsService($priceCalculator); + + $bookingDto = new BookingDto(new Travel(), 197136); + $roomSelection = new RoomSelectionDto(); + $roomSelection->id = 74; + $roomSelection->quantity = 1; + $roomSelection->price = 444.0; + $roomSelection->capacity = 1; + $bookingDto->roomSelections = [$roomSelection]; + + $participant = new ParticipantDto(); + $participant->index = 0; + $participant->assignedRoomId = 74; + $participant->bulkInsuranceBooking = false; + $insurance = new Insurance(); + $insurance->id = '178926'; + $insurance->price = 115.0; + $participant->insurance = $insurance; + $bookingDto->participants = [$participant]; + + $response = new BookingResponse( + status: 'möglich', + priceItems: [ + new PriceItem(1, 'UNT', 'ZIM', 'Room', null, null, 1, '1', 329.0, 329.0, null), + new PriceItem(2, 'SON', 'SPA', 'Skipass', null, null, 1, '1', 49.9, 49.9, '186099'), + new PriceItem(3, 'SON', 'SON', 'Tax', null, null, 1, '1', 9.6, 9.6, '186098'), + new PriceItem(4, 'SON', 'SON', 'Card', null, null, 1, '1', 5.0, 5.0, '186097'), + new PriceItem(5, 'BEF', 'BUS', 'Bus', null, null, 1, '1', 5.0, 5.0, '186092'), + ], + totalPrice: 398.5, + ); + + $diagnostics = $service->buildDiagnostics($bookingDto, $response); + + $this->assertSame(115.0, $diagnostics['deltaBreakdown']['deltaTotal']); + $this->assertSame(115.0, $diagnostics['deltaBreakdown']['deltaRoom']); + $this->assertSame(0.0, $diagnostics['deltaBreakdown']['deltaService']); + $this->assertSame(115.0, $diagnostics['deltaBreakdown']['deltaInsurance']); + } +}