feat: refactor to cards
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Form\Model\BookingDto;
|
||||
|
||||
/**
|
||||
* Generates fingerprints of booking state for change detection in edit mode.
|
||||
*
|
||||
* Creates SHA-256 hashes of all mutable booking data to detect unsaved changes.
|
||||
* Used by EditController to determine if user modifications need to be saved.
|
||||
*/
|
||||
class BookingFingerprintService
|
||||
{
|
||||
/**
|
||||
* Generates a fingerprint (hash) of all mutable booking data.
|
||||
*
|
||||
* The fingerprint includes payment details and all participant data including
|
||||
* personal information, addresses, body dimensions, room assignments, and service selections.
|
||||
*/
|
||||
public function generateFingerprint(BookingDto $bookingDto, bool $logData = false): string
|
||||
{
|
||||
$data = [
|
||||
'paymentMethod' => $bookingDto->paymentMethod,
|
||||
'bankAccount' => [
|
||||
'iban' => $bookingDto->bankAccount?->iban,
|
||||
'accountHolder' => $bookingDto->bankAccount?->accountHolder,
|
||||
],
|
||||
'participants' => [],
|
||||
];
|
||||
|
||||
foreach ($bookingDto->participants as $index => $participant) {
|
||||
$data['participants'][$index] = [
|
||||
'personalData' => [
|
||||
'firstName' => $participant->firstName,
|
||||
'lastName' => $participant->lastName,
|
||||
'dateOfBirth' => $participant->dateOfBirth?->format('Y-m-d'),
|
||||
'email' => $participant->email,
|
||||
'mobile' => $participant->mobile,
|
||||
'gender' => $participant->gender,
|
||||
'nationality' => $participant->nationality,
|
||||
],
|
||||
'address' => [
|
||||
'street' => $participant->address?->street,
|
||||
'postCode' => $participant->address?->postCode,
|
||||
'city' => $participant->address?->city,
|
||||
'country' => $participant->address?->country,
|
||||
],
|
||||
'bodyDimensions' => [
|
||||
'height' => $participant->height,
|
||||
'weight' => $participant->weight,
|
||||
'shoeSize' => $participant->shoeSize,
|
||||
],
|
||||
'roomAssignment' => [
|
||||
'assignedRoomId' => $participant->assignedRoomId,
|
||||
'remarksRoom' => $participant->remarksRoom,
|
||||
],
|
||||
'licensePlate' => $participant->licensePlate,
|
||||
'services' => [
|
||||
'skiPass' => $participant->skiPass?->id,
|
||||
'courses' => $this->normalizeServiceArray($participant->courses),
|
||||
'board' => $this->normalizeServiceArray($participant->board),
|
||||
'rentals' => $this->normalizeServiceArray($participant->rentals),
|
||||
'rentalInsurance' => $participant->rentalInsurance?->id,
|
||||
'additionalServices' => $this->normalizeServiceArray($participant->additionalServices),
|
||||
'transportationOutbound' => $participant->transportationOutbound?->id,
|
||||
'transportationInbound' => $participant->transportationInbound?->id,
|
||||
'pickup' => $participant->pickup?->id,
|
||||
'parking' => $participant->parking,
|
||||
'insurance' => $participant->insurance?->id,
|
||||
'bulkInsuranceBooking' => $participant->bulkInsuranceBooking,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$fingerprint = hash('sha256', serialize($data));
|
||||
|
||||
if ($logData) {
|
||||
error_log(sprintf('[Fingerprint] Generated fingerprint: %s', $fingerprint));
|
||||
error_log(sprintf('[Fingerprint] Serialized data: %s', serialize($data)));
|
||||
}
|
||||
|
||||
return $fingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a service array to ensure consistent fingerprinting.
|
||||
*
|
||||
* Extracts service IDs, sorts them, and returns a simple indexed array.
|
||||
* This ensures that associative arrays, indexed arrays, and different orders
|
||||
* all produce the same fingerprint as long as the same services are present.
|
||||
*
|
||||
* @param array $services Array of Service objects
|
||||
*
|
||||
* @return array Sorted array of service IDs
|
||||
*/
|
||||
private function normalizeServiceArray(array $services): array
|
||||
{
|
||||
$ids = array_map(fn ($s) => $s->id, $services);
|
||||
sort($ids);
|
||||
|
||||
return array_values($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the booking has unsaved changes in edit mode.
|
||||
*
|
||||
* Compares the current state fingerprint with the original fingerprint
|
||||
* that was set when the booking was loaded from the API.
|
||||
*/
|
||||
public function isDirty(BookingDto $bookingDto): bool
|
||||
{
|
||||
if (BookingDto::MODE_EDIT !== $bookingDto->getMode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (null === $bookingDto->originalFingerprint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$currentFingerprint = $this->generateFingerprint($bookingDto);
|
||||
$isDirty = $bookingDto->originalFingerprint !== $currentFingerprint;
|
||||
|
||||
// Debug logging to identify what changed
|
||||
if ($isDirty) {
|
||||
error_log(sprintf('[Fingerprint] DIRTY DETECTED! Original: %s, Current: %s', $bookingDto->originalFingerprint, $currentFingerprint));
|
||||
$this->logFingerprintDiff($bookingDto);
|
||||
}
|
||||
|
||||
return $isDirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs detailed fingerprint data for debugging dirty state issues.
|
||||
*/
|
||||
private function logFingerprintDiff(BookingDto $bookingDto): void
|
||||
{
|
||||
foreach ($bookingDto->participants as $index => $participant) {
|
||||
$participantData = [
|
||||
'firstName' => $participant->firstName,
|
||||
'lastName' => $participant->lastName,
|
||||
'dateOfBirth' => $participant->dateOfBirth?->format('Y-m-d'),
|
||||
'email' => $participant->email,
|
||||
'mobile' => $participant->mobile,
|
||||
'gender' => $participant->gender,
|
||||
'nationality' => $participant->nationality,
|
||||
'address' => [
|
||||
'street' => $participant->address?->street,
|
||||
'postCode' => $participant->address?->postCode,
|
||||
'city' => $participant->address?->city,
|
||||
'country' => $participant->address?->country,
|
||||
],
|
||||
'services' => [
|
||||
'skiPass' => $participant->skiPass?->id,
|
||||
'courses' => $this->normalizeServiceArray($participant->courses),
|
||||
'board' => $this->normalizeServiceArray($participant->board),
|
||||
'rentals' => $this->normalizeServiceArray($participant->rentals),
|
||||
'rentalInsurance' => $participant->rentalInsurance?->id,
|
||||
'additionalServices' => $this->normalizeServiceArray($participant->additionalServices),
|
||||
'transportationOutbound' => $participant->transportationOutbound?->id,
|
||||
'transportationInbound' => $participant->transportationInbound?->id,
|
||||
'pickup' => $participant->pickup?->id,
|
||||
'parking' => $participant->parking,
|
||||
],
|
||||
];
|
||||
|
||||
error_log(sprintf('[Fingerprint] Participant %d data: %s', $index, json_encode($participantData)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,12 @@ class BookingPriceCalculatorService
|
||||
{
|
||||
$roomPricing = [];
|
||||
|
||||
// In edit mode, use room data from the booking entity
|
||||
if (BookingDto::MODE_EDIT === $bookingDto->getMode() && null !== $bookingDto->booking) {
|
||||
return $this->calculateRoomPricingFromBooking($bookingDto);
|
||||
}
|
||||
|
||||
// In create mode, use room selections from the form
|
||||
$selectedRooms = $bookingDto->getSelectedRooms();
|
||||
if (true === empty($selectedRooms)) {
|
||||
return $roomPricing;
|
||||
@@ -86,6 +92,61 @@ class BookingPriceCalculatorService
|
||||
return $roomPricing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates room pricing from booking entity data (edit mode).
|
||||
*
|
||||
* In edit mode, room prices come from the booking entity's individualPrice arrays.
|
||||
* Each participant has their room price stored in the room's individualPrice array.
|
||||
*
|
||||
* @param BookingDto $bookingDto The booking data with booking entity
|
||||
*
|
||||
* @return array Array of room pricing data with labels, quantities, and totals
|
||||
*/
|
||||
private function calculateRoomPricingFromBooking(BookingDto $bookingDto): array
|
||||
{
|
||||
$roomPricing = [];
|
||||
$roomGroups = [];
|
||||
|
||||
// Group participants by room and sum their individual prices
|
||||
foreach ($bookingDto->booking->rooms as $room) {
|
||||
if (false === isset($roomGroups[$room->id])) {
|
||||
$roomGroups[$room->id] = [
|
||||
'room' => $room,
|
||||
'participantCount' => 0,
|
||||
'totalPrice' => 0.0,
|
||||
];
|
||||
}
|
||||
|
||||
// Sum individual prices for all participants in this room
|
||||
foreach ($room->mapping as $participantIndex) {
|
||||
$individualPrice = $room->individualPrice[$participantIndex] ?? 0.0;
|
||||
$roomGroups[$room->id]['totalPrice'] += $individualPrice;
|
||||
++$roomGroups[$room->id]['participantCount'];
|
||||
}
|
||||
}
|
||||
|
||||
// Build pricing array
|
||||
foreach ($roomGroups as $roomId => $data) {
|
||||
$room = $data['room'];
|
||||
$participantCount = $data['participantCount'];
|
||||
$totalPrice = $data['totalPrice'];
|
||||
|
||||
// Calculate average unit price (price per person)
|
||||
$unitPrice = $participantCount > 0 ? $totalPrice / $participantCount : 0.0;
|
||||
|
||||
$roomPricing[] = [
|
||||
'roomId' => $room->id,
|
||||
'label' => $room->label,
|
||||
'quantity' => $room->totalCount,
|
||||
'participantCount' => $participantCount,
|
||||
'unitPrice' => $unitPrice,
|
||||
'totalPrice' => $totalPrice,
|
||||
];
|
||||
}
|
||||
|
||||
return $roomPricing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates pricing for all selected services across all participants, grouped by subtype.
|
||||
*
|
||||
@@ -516,9 +577,9 @@ class BookingPriceCalculatorService
|
||||
/**
|
||||
* Calculates the total service cost for a single participant.
|
||||
*
|
||||
* @param ParticipantDto $participant The participant to calculate services for
|
||||
* @param bool $includeInsurance Whether to include insurance pricing (default: true)
|
||||
* @param BookingDto|null $bookingDto Optional booking DTO for bulk insurance resolution
|
||||
* @param ParticipantDto $participant The participant to calculate services for
|
||||
* @param bool $includeInsurance Whether to include insurance pricing (default: true)
|
||||
* @param BookingDto|null $bookingDto Optional booking DTO for bulk insurance resolution
|
||||
* @param bool $onlyInsuranceCalculationServices Only include services with includeInInsuranceCalculation=true (default: false)
|
||||
*
|
||||
* @return float The total service cost for this participant
|
||||
|
||||
@@ -72,7 +72,7 @@ class ParticipantCardDataService
|
||||
$firstName = $participant->firstName ?? '';
|
||||
$lastName = $participant->lastName ?? '';
|
||||
|
||||
$name = trim($firstName . ' ' . $lastName);
|
||||
$name = trim($firstName.' '.$lastName);
|
||||
|
||||
if ('' === $name) {
|
||||
return sprintf('Teilnehmer %d', $index + 1);
|
||||
@@ -98,7 +98,7 @@ class ParticipantCardDataService
|
||||
return 'Unbekanntes Zimmer';
|
||||
}
|
||||
|
||||
return $room->name;
|
||||
return $room->label;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,6 +110,6 @@ class ParticipantCardDataService
|
||||
|
||||
$price = $prices[$index] ?? 0.0;
|
||||
|
||||
return number_format($price, 2, ',', '.') . ' €';
|
||||
return number_format($price, 2, ',', '.').' €';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user