feat: split summary DTOs into typed sub-objects
This commit is contained in:
@@ -136,7 +136,7 @@ class Step4Controller extends AbstractController
|
||||
// Success: Store booking data in flash for conversion tracking
|
||||
$bookingCreateContext = $this->createContextFactory->createWithParticipantPrices($bookingCreateDto);
|
||||
$this->addFlash('booking_number', $bookingResponse->bookingNumber);
|
||||
$this->addFlash('booking_total', $bookingCreateContext->summaryData->payableAmount);
|
||||
$this->addFlash('booking_total', $bookingCreateContext->summaryData->vouchers->payableAmount);
|
||||
$this->addFlash('booking_travel_name', $bookingCreateDto->travel->label);
|
||||
$this->clearTravelDataCache($bookingCreateDto);
|
||||
$this->bookingSessionService->clearBookingDto($request, BookingDto::MODE_CREATE);
|
||||
|
||||
@@ -4,22 +4,19 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
use App\BusProNet\Model\Room;
|
||||
|
||||
/**
|
||||
* Bundles the data needed to render the booking create flow.
|
||||
*/
|
||||
class BookingCreateContext
|
||||
{
|
||||
/**
|
||||
* @param array{by_pax: array<int, Room>, by_room: array<int, Room>} $groupedRooms
|
||||
* @param array<int, ParticipantCardDataDto>|null $cardsData
|
||||
* @param array<int, float>|null $participantPrices
|
||||
* @param array<int, ParticipantCardDataDto>|null $cardsData
|
||||
* @param array<int, float>|null $participantPrices
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly BookingDto $bookingDto,
|
||||
public readonly BookingSummaryDto $summaryData,
|
||||
public readonly array $groupedRooms,
|
||||
public readonly RoomGroupsDto $groupedRooms,
|
||||
public readonly ?array $cardsData = null,
|
||||
public readonly bool $isSubmitted = false,
|
||||
public readonly ?array $participantPrices = null,
|
||||
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
use App\BusProNet\Model\BaseData;
|
||||
use App\BusProNet\Model\Booking;
|
||||
|
||||
/**
|
||||
@@ -18,7 +17,7 @@ class BookingEditContext
|
||||
public function __construct(
|
||||
public readonly BookingDto $bookingDto,
|
||||
public readonly ?Booking $bookingData,
|
||||
public readonly ?BaseData $mutableData,
|
||||
public readonly ?BookingMutabilityDto $mutableData,
|
||||
public readonly BookingSummaryDto $summaryData,
|
||||
public readonly ?array $cardsData = null,
|
||||
public readonly bool $isDirty = false,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
use App\BusProNet\Model\BaseData;
|
||||
use App\BusProNet\Model\MutableData;
|
||||
|
||||
/**
|
||||
* Typed wrapper for edit-mode mutability data.
|
||||
*/
|
||||
final class BookingMutabilityDto
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ?MutableData $participantCount,
|
||||
public readonly ?MutableData $participantData,
|
||||
public readonly ?MutableData $transportation,
|
||||
public readonly ?MutableData $pickup,
|
||||
public readonly ?MutableData $accommodation,
|
||||
public readonly ?MutableData $additionalServices,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromBaseData(?BaseData $baseData): ?self
|
||||
{
|
||||
if (null === $baseData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new self(
|
||||
participantCount: self::getItem($baseData, MutableData::CATEGORY_PARTICIPANT_COUNT),
|
||||
participantData: self::getItem($baseData, MutableData::CATEGORY_PARTICIPANT_DATA),
|
||||
transportation: self::getItem($baseData, MutableData::CATEGORY_TRANSPORTATION),
|
||||
pickup: self::getItem($baseData, MutableData::CATEGORY_PICKUP),
|
||||
accommodation: self::getItem($baseData, MutableData::CATEGORY_ACCOMMODATION),
|
||||
additionalServices: self::getItem($baseData, MutableData::CATEGORY_ADDITIONAL_SERVICES),
|
||||
);
|
||||
}
|
||||
|
||||
private static function getItem(BaseData $baseData, string $key): ?MutableData
|
||||
{
|
||||
$item = $baseData->getItemByKey($key);
|
||||
|
||||
return $item instanceof MutableData ? $item : null;
|
||||
}
|
||||
}
|
||||
@@ -6,30 +6,17 @@ namespace App\Form\Model;
|
||||
|
||||
/**
|
||||
* DTO containing all booking summary data for sidebar display.
|
||||
*
|
||||
* Provides pricing breakdowns, room assignments, participant counts,
|
||||
* and CMS product information in a single typed object.
|
||||
*/
|
||||
class BookingSummaryDto
|
||||
{
|
||||
/**
|
||||
* @param array<int, RoomSelectionDto> $selectedRooms Selected room DTOs from booking
|
||||
* @param int $participantCount Total participant count from room capacity
|
||||
* @param float $totalPrice Total price before voucher deductions
|
||||
* @param float $payableAmount Amount after voucher deductions
|
||||
* @param array<array{room: mixed, count: int}> $groupedSelectedRooms Rooms grouped by participant assignments
|
||||
* @param array<int, int> $assignmentCounts Room ID to participant count mapping
|
||||
* @param array $pricingData Detailed pricing breakdown
|
||||
* @param array|null $cmsData CMS product data (images, etc.)
|
||||
* @param array<int, RoomSelectionDto> $selectedRooms Selected room DTOs from booking
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $selectedRooms,
|
||||
public readonly int $participantCount,
|
||||
public readonly float $totalPrice,
|
||||
public readonly float $payableAmount,
|
||||
public readonly array $groupedSelectedRooms,
|
||||
public readonly array $assignmentCounts,
|
||||
public readonly array $pricingData,
|
||||
public readonly BookingSummaryPricingDto $pricing,
|
||||
public readonly BookingSummaryVoucherDto $vouchers,
|
||||
public readonly ?array $cmsData,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
/**
|
||||
* Typed pricing section for the booking summary.
|
||||
*/
|
||||
final class BookingSummaryPricingDto
|
||||
{
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $rooms
|
||||
* @param array<int, array<string, mixed>> $services
|
||||
* @param array<int, array<string, mixed>>|null $surcharges
|
||||
* @param array<int, int> $assignmentCounts
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $rooms,
|
||||
public readonly array $services,
|
||||
public readonly ?array $surcharges,
|
||||
public readonly array $assignmentCounts,
|
||||
public readonly float $grandTotal,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
/**
|
||||
* Typed voucher section for the booking summary.
|
||||
*/
|
||||
final class BookingSummaryVoucherDto
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ?AcceptedVouchersDto $acceptedVouchers,
|
||||
public readonly float $payableAmount,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
use App\BusProNet\Model\Room;
|
||||
|
||||
/**
|
||||
* Typed room grouping used by the booking create flow.
|
||||
*/
|
||||
final class RoomGroupsDto
|
||||
{
|
||||
/**
|
||||
* @param array<int, Room> $byPax
|
||||
* @param array<int, Room> $byRoom
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $byPax,
|
||||
public readonly array $byRoom,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ namespace App\Service;
|
||||
|
||||
use App\Form\Model\BookingCreateContext;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\BookingSummaryDto;
|
||||
use App\Form\Model\RoomGroupsDto;
|
||||
|
||||
/**
|
||||
* Prepares the shared view model for booking create.
|
||||
@@ -60,15 +62,13 @@ class BookingCreateContextFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{summaryData: \App\Form\Model\BookingSummaryDto, groupedRooms: array{by_pax: array<int, \App\BusProNet\Model\Room>, by_room: array<int, \App\BusProNet\Model\Room>}}
|
||||
* @return array{summaryData: BookingSummaryDto, groupedRooms: RoomGroupsDto}
|
||||
*/
|
||||
private function buildBaseContext(BookingDto $bookingDto, string $pricingMode): array
|
||||
{
|
||||
return [
|
||||
'summaryData' => $this->summaryDataService->getSummaryData($bookingDto, $pricingMode),
|
||||
'groupedRooms' => $this->roomSelectionService->groupRoomsBySelectionType(
|
||||
$bookingDto->travel->getAvailableRooms()
|
||||
),
|
||||
'groupedRooms' => $this->roomSelectionService->groupRoomsBySelectionType($bookingDto->travel->getAvailableRooms()),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\BusProNet\Model\Booking;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\BookingEditContext;
|
||||
use App\Form\Model\BookingSummaryDto;
|
||||
use App\Form\Model\BookingMutabilityDto;
|
||||
|
||||
/**
|
||||
* Prepares the shared booking edit flow context.
|
||||
@@ -37,7 +38,9 @@ class BookingEditContextFactory
|
||||
return new BookingEditContext(
|
||||
bookingDto: $bookingDto,
|
||||
bookingData: $bookingData,
|
||||
mutableData: $bookingData ? $this->travelDataService->getMutabilityData($bookingData->dateId) : null,
|
||||
mutableData: BookingMutabilityDto::fromBaseData(
|
||||
$bookingData ? $this->travelDataService->getMutabilityData($bookingData->dateId) : null
|
||||
),
|
||||
summaryData: $this->createSummaryData($bookingDto),
|
||||
);
|
||||
}
|
||||
@@ -52,7 +55,7 @@ class BookingEditContextFactory
|
||||
return new BookingEditContext(
|
||||
bookingDto: $bookingDto,
|
||||
bookingData: $bookingData,
|
||||
mutableData: $this->travelDataService->getMutabilityData($bookingData->dateId),
|
||||
mutableData: BookingMutabilityDto::fromBaseData($this->travelDataService->getMutabilityData($bookingData->dateId)),
|
||||
summaryData: $this->createSummaryData($bookingDto),
|
||||
cardsData: $this->participantCardDataService->getAllCardsDataWithValidation($bookingDto),
|
||||
isDirty: $isDirty,
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Service;
|
||||
|
||||
use App\BusProNet\Model\Room;
|
||||
use App\Form\Model\RoomGroupsDto;
|
||||
|
||||
/**
|
||||
* Groups room data for booking steps.
|
||||
@@ -13,10 +14,8 @@ class BookingRoomSelectionService
|
||||
{
|
||||
/**
|
||||
* @param array<int, Room> $rooms
|
||||
*
|
||||
* @return array{by_pax: array<int, Room>, by_room: array<int, Room>}
|
||||
*/
|
||||
public function groupRoomsBySelectionType(array $rooms): array
|
||||
public function groupRoomsBySelectionType(array $rooms): RoomGroupsDto
|
||||
{
|
||||
$groups = [
|
||||
Room::SELECTION_TYPE_BY_PAX => [],
|
||||
@@ -35,6 +34,9 @@ class BookingRoomSelectionService
|
||||
uasort($groups[Room::SELECTION_TYPE_BY_PAX], static fn (Room $a, Room $b): int => $a->maxPax <=> $b->maxPax);
|
||||
uasort($groups[Room::SELECTION_TYPE_BY_ROOM], static fn (Room $a, Room $b): int => $a->maxPax <=> $b->maxPax);
|
||||
|
||||
return $groups;
|
||||
return new RoomGroupsDto(
|
||||
byPax: $groups[Room::SELECTION_TYPE_BY_PAX],
|
||||
byRoom: $groups[Room::SELECTION_TYPE_BY_ROOM],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@ namespace App\Service;
|
||||
use App\BusProNet\DataProvider\CountryDataProvider;
|
||||
use App\BusProNet\Model\Hotel;
|
||||
use App\BusProNet\XmlLoader\HotelLoader;
|
||||
use App\Form\Model\AcceptedVouchersDto;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\BookingSummaryDto;
|
||||
use App\Form\Model\BookingSummaryPricingDto;
|
||||
use App\Form\Model\BookingSummaryVoucherDto;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
@@ -53,18 +56,6 @@ class BookingSummaryDataService
|
||||
}
|
||||
}
|
||||
|
||||
// Group selected rooms with counts (for display)
|
||||
$groupedSelectedRooms = [];
|
||||
foreach ($roomCounts as $roomId => $count) {
|
||||
$room = $bookingDto->travel->getRoomById($roomId);
|
||||
if (null !== $room) {
|
||||
$groupedSelectedRooms[] = [
|
||||
'room' => $room,
|
||||
'count' => $count,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Get detailed pricing breakdown
|
||||
$pricingData = $this->priceCalculator->getPricingBreakdown($bookingDto, $roomPricingMode);
|
||||
|
||||
@@ -74,17 +65,29 @@ class BookingSummaryDataService
|
||||
// Calculate participant count from room capacity (source of truth)
|
||||
$participantCount = $this->calculateParticipantCountFromRooms($bookingDto);
|
||||
|
||||
$acceptedVouchers = $bookingDto->getAcceptedVouchers($participantPrices);
|
||||
|
||||
// Calculate payable amount after voucher deductions
|
||||
$payableAmount = $this->calculatePayableAmount($bookingDto, $pricingData['grandTotal'], $participantPrices);
|
||||
$payableAmount = $this->calculatePayableAmount($pricingData['grandTotal'], $acceptedVouchers);
|
||||
|
||||
$pricing = new BookingSummaryPricingDto(
|
||||
rooms: $pricingData['rooms'],
|
||||
services: $pricingData['services'],
|
||||
surcharges: $pricingData['surcharges'] ?? null,
|
||||
assignmentCounts: $roomCounts,
|
||||
grandTotal: $pricingData['grandTotal'],
|
||||
);
|
||||
|
||||
$vouchers = new BookingSummaryVoucherDto(
|
||||
acceptedVouchers: $acceptedVouchers,
|
||||
payableAmount: $payableAmount,
|
||||
);
|
||||
|
||||
return new BookingSummaryDto(
|
||||
selectedRooms: $selectedRooms,
|
||||
participantCount: $participantCount,
|
||||
totalPrice: $pricingData['grandTotal'],
|
||||
payableAmount: $payableAmount,
|
||||
groupedSelectedRooms: $groupedSelectedRooms,
|
||||
assignmentCounts: $roomCounts,
|
||||
pricingData: $pricingData,
|
||||
pricing: $pricing,
|
||||
vouchers: $vouchers,
|
||||
cmsData: $cmsData,
|
||||
);
|
||||
}
|
||||
@@ -92,12 +95,10 @@ class BookingSummaryDataService
|
||||
/**
|
||||
* Calculates the payable amount after voucher deductions.
|
||||
*
|
||||
* @param array<int, float> $participantPrices Prices per participant for percentage voucher calculation
|
||||
* @param AcceptedVouchersDto|null $acceptedVouchers Accepted vouchers for discount calculation
|
||||
*/
|
||||
private function calculatePayableAmount(BookingDto $bookingDto, float $grandTotal, array $participantPrices): float
|
||||
private function calculatePayableAmount(float $grandTotal, ?AcceptedVouchersDto $acceptedVouchers): float
|
||||
{
|
||||
$acceptedVouchers = $bookingDto->getAcceptedVouchers($participantPrices);
|
||||
|
||||
if (null === $acceptedVouchers) {
|
||||
return $grandTotal;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user