feat: automatic saving of drafts when editing bookings

This commit is contained in:
Björn Fromme
2026-03-16 12:01:09 +01:00
parent 721016a00e
commit 0b5c7af4be
7 changed files with 759 additions and 52 deletions
+76 -42
View File
@@ -5,12 +5,14 @@ declare(strict_types=1);
namespace App\Service;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
/**
* 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.
* Also provides data extraction for draft persistence.
*/
class BookingFingerprintService
{
@@ -21,6 +23,22 @@ class BookingFingerprintService
* personal information, addresses, body dimensions, room assignments, and service selections.
*/
public function generateFingerprint(BookingDto $bookingDto, bool $logData = false): string
{
return hash('sha256', serialize($this->extractUserData($bookingDto)));
}
/**
* Extracts all user-editable data from a BookingDto as a serializable array.
*
* This method captures all data that represents user input: personal information,
* addresses, body dimensions, service selections, voucher codes, and payment details.
* The resulting array can be used for fingerprint generation or draft persistence.
*
* @param BookingDto $bookingDto The booking DTO to extract data from
*
* @return array Serializable array of all user-editable data
*/
public function extractUserData(BookingDto $bookingDto): array
{
$data = [
'paymentMethod' => $bookingDto->paymentMethod,
@@ -32,50 +50,66 @@ class BookingFingerprintService
];
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,
],
];
$data['participants'][$index] = $this->extractParticipantData($participant);
}
return hash('sha256', serialize($data));
return $data;
}
/**
* Extracts user-editable data from a single participant.
*
* @param ParticipantDto $participant The participant to extract data from
*
* @return array Serializable array of participant data
*/
private function extractParticipantData(ParticipantDto $participant): array
{
return [
'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,
],
'vouchers' => [
'purchaseVoucherCode' => $participant->purchaseVoucherCode,
'promoVoucherCode' => $participant->promoVoucherCode,
],
];
}
/**