feat: extended price mismatch diagnostics in logs
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\BusProNet\Constants;
|
||||
use App\BusProNet\Model\BookingResponse;
|
||||
use App\BusProNet\Model\PriceItem;
|
||||
use App\Form\Model\BookingDto;
|
||||
|
||||
/**
|
||||
* Builds diagnostic payloads for booking price mismatches.
|
||||
*/
|
||||
class BookingPriceMismatchDiagnosticsService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BookingPriceCalculatorService $priceCalculator,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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<int, array<string, mixed>> $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<int, PriceItem> $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<int, PriceItem> $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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\BusProNet\Model\BookingResponse;
|
||||
use App\BusProNet\Model\Insurance;
|
||||
use App\BusProNet\Model\PriceItem;
|
||||
use App\BusProNet\Model\Travel;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use App\Form\Model\RoomSelectionDto;
|
||||
use App\Service\BookingPriceCalculatorService;
|
||||
use App\Service\BookingPriceMismatchDiagnosticsService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class BookingPriceMismatchDiagnosticsServiceTest extends TestCase
|
||||
{
|
||||
public function testBuildDiagnosticsProvidesDeltaBreakdown(): void
|
||||
{
|
||||
$priceCalculator = $this->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']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user