fix: enforce submit-time immutability, scope booking cache per user
This commit is contained in:
@@ -25,28 +25,6 @@ use Psr\Log\LoggerInterface;
|
||||
*/
|
||||
class BookingEditDraftService
|
||||
{
|
||||
private const array ADDITIONAL_SERVICE_KEYS = [
|
||||
'skiPass',
|
||||
'courses',
|
||||
'board',
|
||||
'rentals',
|
||||
'rentalInsurance',
|
||||
'additionalServices',
|
||||
];
|
||||
|
||||
private const array TRANSPORTATION_KEYS = [
|
||||
'transportationOutbound',
|
||||
'transportationInbound',
|
||||
'parking',
|
||||
];
|
||||
|
||||
private const array PICKUP_KEYS = [
|
||||
'pickup',
|
||||
'dropOff',
|
||||
];
|
||||
|
||||
private bool $draftHadImmutableSkips = false;
|
||||
|
||||
public function __construct(
|
||||
private readonly BookingEditDraftRepository $draftRepository,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
@@ -134,14 +112,6 @@ class BookingEditDraftService
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the last draft application skipped fields due to immutability.
|
||||
*/
|
||||
public function hadImmutableSkips(): bool
|
||||
{
|
||||
return $this->draftHadImmutableSkips;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies draft data to a fresh BookingDto loaded from the API.
|
||||
*
|
||||
@@ -158,8 +128,6 @@ class BookingEditDraftService
|
||||
*/
|
||||
public function applyDraftToDto(BookingEditDraft $draft, BookingDto $dto, Travel $travel): bool
|
||||
{
|
||||
$this->draftHadImmutableSkips = false;
|
||||
|
||||
try {
|
||||
$formData = $draft->getFormData();
|
||||
|
||||
@@ -228,10 +196,6 @@ class BookingEditDraftService
|
||||
$participant = $bookingDto->participants[$participantIndex];
|
||||
$canApplyPersonalDataDraft = $this->canApplyPersonalDataDraft($bookingDto, $participantIndex, $participant);
|
||||
|
||||
if (false === $canApplyPersonalDataDraft && $this->hasBlockedPersonalOrAddressChanges($participant, $data)) {
|
||||
$this->draftHadImmutableSkips = true;
|
||||
}
|
||||
|
||||
// Personal data
|
||||
if (true === $canApplyPersonalDataDraft && isset($data['personalData']) && true === is_array($data['personalData'])) {
|
||||
$this->applyPersonalData($participant, $data['personalData']);
|
||||
@@ -259,7 +223,6 @@ class BookingEditDraftService
|
||||
|
||||
// Services
|
||||
if (true === isset($data['services']) && true === is_array($data['services'])) {
|
||||
$this->markImmutableServiceSkips($data['services'], $participant, $travel);
|
||||
$this->applyServiceSelections($participant, $data['services'], $travel);
|
||||
}
|
||||
|
||||
@@ -290,182 +253,6 @@ class BookingEditDraftService
|
||||
return $participant->mutable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether draft payload contains personal or address data sections.
|
||||
*/
|
||||
private function hasBlockedPersonalOrAddressChanges(ParticipantDto $participant, array $data): bool
|
||||
{
|
||||
$personalData = $data['personalData'] ?? null;
|
||||
$addressData = $data['address'] ?? null;
|
||||
|
||||
if (true === is_array($personalData)) {
|
||||
if ($this->hasDifferences($personalData, $this->normalizePersonalData($participant), [
|
||||
'firstName',
|
||||
'lastName',
|
||||
'dateOfBirth',
|
||||
'email',
|
||||
'mobile',
|
||||
'gender',
|
||||
'nationality',
|
||||
])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (true === is_array($addressData)) {
|
||||
if ($this->hasDifferences($addressData, $this->normalizeAddressData($participant), [
|
||||
'street',
|
||||
'postCode',
|
||||
'city',
|
||||
'country',
|
||||
])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks draft as partially skipped when immutable service categories had draft changes.
|
||||
*/
|
||||
private function markImmutableServiceSkips(array $servicesData, ParticipantDto $participant, Travel $travel): void
|
||||
{
|
||||
$currentServices = $this->normalizeServiceState($participant);
|
||||
|
||||
$this->markImmutableSkipForCategory(
|
||||
false === $travel->additionalServicesMutable,
|
||||
$servicesData,
|
||||
$currentServices,
|
||||
self::ADDITIONAL_SERVICE_KEYS
|
||||
);
|
||||
$this->markImmutableSkipForCategory(
|
||||
false === $travel->transportationServicesMutable,
|
||||
$servicesData,
|
||||
$currentServices,
|
||||
self::TRANSPORTATION_KEYS
|
||||
);
|
||||
$this->markImmutableSkipForCategory(
|
||||
false === $travel->pickupsMutable,
|
||||
$servicesData,
|
||||
$currentServices,
|
||||
self::PICKUP_KEYS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks immutable skip flag when category is locked and draft contains related keys.
|
||||
*/
|
||||
private function markImmutableSkipForCategory(bool $isImmutable, array $servicesData, array $currentServices, array $categoryKeys): void
|
||||
{
|
||||
if ($isImmutable && $this->hasDifferences($servicesData, $currentServices, $categoryKeys)) {
|
||||
$this->draftHadImmutableSkips = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes participant personal data to draft payload shape.
|
||||
*/
|
||||
private function normalizePersonalData(ParticipantDto $participant): array
|
||||
{
|
||||
return [
|
||||
'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,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes participant address data to draft payload shape.
|
||||
*/
|
||||
private function normalizeAddressData(ParticipantDto $participant): array
|
||||
{
|
||||
return [
|
||||
'street' => $participant->address?->street,
|
||||
'postCode' => $participant->address?->postCode,
|
||||
'city' => $participant->address?->city,
|
||||
'country' => $participant->address?->country,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes participant service state to draft payload shape.
|
||||
*/
|
||||
private function normalizeServiceState(ParticipantDto $participant): array
|
||||
{
|
||||
return [
|
||||
'skiPass' => $participant->skiPass?->id,
|
||||
'courses' => $this->normalizeServiceIds($participant->courses),
|
||||
'board' => $this->normalizeServiceIds($participant->board),
|
||||
'rentals' => $this->normalizeServiceIds($participant->rentals),
|
||||
'rentalInsurance' => $participant->rentalInsurance?->id,
|
||||
'additionalServices' => $this->normalizeServiceIds($participant->additionalServices),
|
||||
'transportationOutbound' => $participant->transportationOutbound?->id,
|
||||
'transportationInbound' => $participant->transportationInbound?->id,
|
||||
'pickup' => $participant->pickup?->id,
|
||||
'dropOff' => $participant->dropOff?->id,
|
||||
'parking' => (bool) $participant->parking,
|
||||
'insurance' => $participant->insurance?->id,
|
||||
'bulkInsuranceBooking' => (bool) $participant->bulkInsuranceBooking,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sorted unique IDs for service arrays.
|
||||
*/
|
||||
private function normalizeServiceIds(array $services): array
|
||||
{
|
||||
$ids = array_map(static fn ($service) => $service->id, $services);
|
||||
$ids = array_values(array_unique($ids));
|
||||
sort($ids);
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of the given keys differ between draft and current values.
|
||||
*/
|
||||
private function hasDifferences(array $draftData, array $currentData, array $keys): bool
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
if (false === array_key_exists($key, $draftData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$draftValue = $this->normalizeComparableValue($draftData[$key]);
|
||||
$currentValue = $this->normalizeComparableValue($currentData[$key] ?? null);
|
||||
|
||||
if ($draftValue !== $currentValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes scalar/list values for strict comparisons.
|
||||
*/
|
||||
private function normalizeComparableValue(mixed $value): mixed
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$normalized = array_map(fn ($item) => $this->normalizeComparableValue($item), array_values($value));
|
||||
sort($normalized);
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
if ('' === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies personal data fields to participant.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user