From 98dd685679749f65fdea7b10c2f1b2df51a68cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Sun, 12 Apr 2026 14:40:05 +0200 Subject: [PATCH] feat: extract participant draft field applier --- .../BookingEditDraftParticipantApplier.php | 154 +++++++++ src/Service/BookingEditDraftService.php | 316 ++++-------------- .../BookingEditDraftServiceMutabilityTest.php | 2 + 3 files changed, 227 insertions(+), 245 deletions(-) create mode 100644 src/Service/BookingEditDraftParticipantApplier.php diff --git a/src/Service/BookingEditDraftParticipantApplier.php b/src/Service/BookingEditDraftParticipantApplier.php new file mode 100644 index 0000000..1acfba9 --- /dev/null +++ b/src/Service/BookingEditDraftParticipantApplier.php @@ -0,0 +1,154 @@ +canApplyPersonalDataDraft($bookingDto, $participantIndex, $participant); + + // Personal data + if (true === $canApplyPersonalDataDraft && isset($data['personalData']) && true === is_array($data['personalData'])) { + $this->applyPersonalData($participant, $data['personalData']); + } + + // Address + if (true === $canApplyPersonalDataDraft && isset($data['address']) && true === is_array($data['address'])) { + $this->applyAddressData($participant, $data['address']); + } + + // Body dimensions + if (true === isset($data['bodyDimensions']) && true === is_array($data['bodyDimensions'])) { + $this->applyBodyDimensions($participant, $data['bodyDimensions']); + } + + // Room assignment + if (true === isset($data['roomAssignment']) && true === is_array($data['roomAssignment'])) { + $this->applyRoomAssignment($participant, $data['roomAssignment']); + } + + // License plate + if (true === array_key_exists('licensePlate', $data)) { + $participant->licensePlate = $data['licensePlate']; + } + + // Vouchers + if (true === isset($data['vouchers']) && true === is_array($data['vouchers'])) { + $this->applyVoucherCodes($participant, $data['vouchers']); + } + } + + private function canApplyPersonalDataDraft(BookingDto $bookingDto, int $participantIndex, ParticipantDto $participant): bool + { + if (true === $bookingDto->isInternalAgencyBooking()) { + return true; + } + + if (0 === $participantIndex) { + return false; + } + + return $participant->mutable; + } + + private function applyPersonalData(ParticipantDto $participant, array $data): void + { + if (true === array_key_exists('firstName', $data)) { + $participant->firstName = $data['firstName']; + } + if (true === array_key_exists('lastName', $data)) { + $participant->lastName = $data['lastName']; + } + if (true === array_key_exists('dateOfBirth', $data) && null !== $data['dateOfBirth']) { + $participant->dateOfBirth = new \DateTimeImmutable($data['dateOfBirth']); + } + if (true === array_key_exists('email', $data)) { + $participant->email = $data['email']; + } + if (true === array_key_exists('mobile', $data)) { + $participant->mobile = $data['mobile']; + } + if (true === array_key_exists('gender', $data)) { + $participant->gender = $data['gender']; + } + if (true === array_key_exists('nationality', $data) && '' !== $data['nationality'] && null !== $data['nationality']) { + $participant->nationality = $data['nationality']; + } + } + + private function applyAddressData(ParticipantDto $participant, array $data): void + { + $hasAddressData = null !== ($data['street'] ?? null) + || null !== ($data['postCode'] ?? null) + || null !== ($data['city'] ?? null) + || null !== ($data['country'] ?? null); + + if (false === $hasAddressData) { + return; + } + + if (null === $participant->address) { + $participant->address = new \App\BusProNet\Model\Address(); + } + + if (true === array_key_exists('street', $data)) { + $participant->address->street = $data['street']; + } + if (true === array_key_exists('postCode', $data)) { + $participant->address->postCode = $data['postCode']; + } + if (true === array_key_exists('city', $data)) { + $participant->address->city = $data['city']; + } + if (true === array_key_exists('country', $data)) { + $participant->address->country = $data['country']; + } + } + + private function applyBodyDimensions(ParticipantDto $participant, array $data): void + { + if (true === array_key_exists('height', $data)) { + $participant->height = $data['height']; + } + if (true === array_key_exists('weight', $data)) { + $participant->weight = $data['weight']; + } + if (true === array_key_exists('shoeSize', $data)) { + $participant->shoeSize = $data['shoeSize']; + } + } + + private function applyRoomAssignment(ParticipantDto $participant, array $data): void + { + if (true === array_key_exists('assignedRoomId', $data)) { + $participant->assignedRoomId = $data['assignedRoomId']; + } + if (true === array_key_exists('remarksRoom', $data)) { + $participant->remarksRoom = $data['remarksRoom']; + } + } + + private function applyVoucherCodes(ParticipantDto $participant, array $data): void + { + if (true === array_key_exists('purchaseVoucherCode', $data)) { + $participant->purchaseVoucherCode = $data['purchaseVoucherCode']; + } + if (true === array_key_exists('promoVoucherCode', $data)) { + $participant->promoVoucherCode = $data['promoVoucherCode']; + } + } + +} diff --git a/src/Service/BookingEditDraftService.php b/src/Service/BookingEditDraftService.php index 6acac1e..68b64a6 100644 --- a/src/Service/BookingEditDraftService.php +++ b/src/Service/BookingEditDraftService.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Service; -use App\BusProNet\Model\Address; use App\BusProNet\Model\Travel; use App\Entity\BookingEditDraft; use App\Entity\User; @@ -29,6 +28,7 @@ class BookingEditDraftService private readonly BookingEditDraftRepository $draftRepository, private readonly EntityManagerInterface $entityManager, private readonly BookingFingerprintService $fingerprintService, + private readonly BookingEditDraftParticipantApplier $participantApplier, private readonly LoggerInterface $logger, ) { } @@ -148,7 +148,16 @@ class BookingEditDraftService continue; } - $this->applyParticipantData($dto, $index, $participantData, $travel); + $this->participantApplier->apply( + $dto, + $index, + $dto->participants[$index], + $participantData, + ); + + if (isset($participantData['services']) && true === is_array($participantData['services'])) { + $this->applyServiceSelections($dto->participants[$index], $participantData['services'], $travel); + } } } @@ -188,160 +197,6 @@ class BookingEditDraftService $dto->bankAccount->accountHolder = $accountHolder; } - /** - * Applies participant data from draft to a ParticipantDto. - */ - private function applyParticipantData(BookingDto $bookingDto, int $participantIndex, array $data, Travel $travel): void - { - $participant = $bookingDto->participants[$participantIndex]; - $canApplyPersonalDataDraft = $this->canApplyPersonalDataDraft($bookingDto, $participantIndex, $participant); - - // Personal data - if (true === $canApplyPersonalDataDraft && isset($data['personalData']) && true === is_array($data['personalData'])) { - $this->applyPersonalData($participant, $data['personalData']); - } - - // Address - if (true === $canApplyPersonalDataDraft && isset($data['address']) && true === is_array($data['address'])) { - $this->applyAddressData($participant, $data['address']); - } - - // Body dimensions - if (true === isset($data['bodyDimensions']) && true === is_array($data['bodyDimensions'])) { - $this->applyBodyDimensions($participant, $data['bodyDimensions']); - } - - // Room assignment - if (true === isset($data['roomAssignment']) && true === is_array($data['roomAssignment'])) { - $this->applyRoomAssignment($participant, $data['roomAssignment']); - } - - // License plate - if (true === array_key_exists('licensePlate', $data)) { - $participant->licensePlate = $data['licensePlate']; - } - - // Services - if (true === isset($data['services']) && true === is_array($data['services'])) { - $this->applyServiceSelections($participant, $data['services'], $travel); - } - - // Vouchers - if (true === isset($data['vouchers']) && true === is_array($data['vouchers'])) { - $this->applyVoucherCodes($participant, $data['vouchers']); - } - } - - /** - * Determines whether personal/address draft data may be applied. - * - * Edit rules mirror UI mutability behavior: - * - Internal agency bookings may always update participant personal data - * - Non-internal agency: first participant is always read-only in edit flow - * - Other participants require participant-level mutability from BPN - */ - private function canApplyPersonalDataDraft(BookingDto $bookingDto, int $participantIndex, ParticipantDto $participant): bool - { - if (true === $bookingDto->isInternalAgencyBooking()) { - return true; - } - - if (0 === $participantIndex) { - return false; - } - - return $participant->mutable; - } - - /** - * Applies personal data fields to participant. - */ - private function applyPersonalData(ParticipantDto $participant, array $data): void - { - if (true === array_key_exists('firstName', $data)) { - $participant->firstName = $data['firstName']; - } - if (true === array_key_exists('lastName', $data)) { - $participant->lastName = $data['lastName']; - } - if (true === array_key_exists('dateOfBirth', $data) && null !== $data['dateOfBirth']) { - $participant->dateOfBirth = new \DateTimeImmutable($data['dateOfBirth']); - } - if (true === array_key_exists('email', $data)) { - $participant->email = $data['email']; - } - if (true === array_key_exists('mobile', $data)) { - $participant->mobile = $data['mobile']; - } - if (true === array_key_exists('gender', $data)) { - $participant->gender = $data['gender']; - } - if (true === array_key_exists('nationality', $data) && '' !== $data['nationality'] && null !== $data['nationality']) { - $participant->nationality = $data['nationality']; - } - } - - /** - * Applies address data to participant. - */ - private function applyAddressData(ParticipantDto $participant, array $data): void - { - $hasAddressData = null !== ($data['street'] ?? null) - || null !== ($data['postCode'] ?? null) - || null !== ($data['city'] ?? null) - || null !== ($data['country'] ?? null); - - if (false === $hasAddressData) { - return; - } - - if (null === $participant->address) { - $participant->address = new Address(); - } - - if (true === array_key_exists('street', $data)) { - $participant->address->street = $data['street']; - } - if (true === array_key_exists('postCode', $data)) { - $participant->address->postCode = $data['postCode']; - } - if (true === array_key_exists('city', $data)) { - $participant->address->city = $data['city']; - } - if (true === array_key_exists('country', $data)) { - $participant->address->country = $data['country']; - } - } - - /** - * Applies body dimension data to participant. - */ - private function applyBodyDimensions(ParticipantDto $participant, array $data): void - { - if (true === array_key_exists('height', $data)) { - $participant->height = $data['height']; - } - if (true === array_key_exists('weight', $data)) { - $participant->weight = $data['weight']; - } - if (true === array_key_exists('shoeSize', $data)) { - $participant->shoeSize = $data['shoeSize']; - } - } - - /** - * Applies room assignment data to participant. - */ - private function applyRoomAssignment(ParticipantDto $participant, array $data): void - { - if (true === array_key_exists('assignedRoomId', $data)) { - $participant->assignedRoomId = $data['assignedRoomId']; - } - if (true === array_key_exists('remarksRoom', $data)) { - $participant->remarksRoom = $data['remarksRoom']; - } - } - /** * Applies service selections to participant, resolving IDs against Travel data. * @@ -463,95 +318,6 @@ class BookingEditDraftService } } - /** - * Applies voucher codes to participant. - */ - private function applyVoucherCodes(ParticipantDto $participant, array $data): void - { - if (true === array_key_exists('purchaseVoucherCode', $data)) { - $participant->purchaseVoucherCode = $data['purchaseVoucherCode']; - } - if (true === array_key_exists('promoVoucherCode', $data)) { - $participant->promoVoucherCode = $data['promoVoucherCode']; - } - } - - /** - * Resolves a single service ID to a Service object from the available services. - * - * @param int|null $serviceId The service ID to resolve - * @param array $services Available services indexed by ID - * - * @return object|null The service object if found, null otherwise - */ - private function resolveService(?int $serviceId, array $services): ?object - { - if (null === $serviceId) { - return null; - } - - return $services[$serviceId] ?? null; - } - - /** - * Resolves an array of service IDs to Service objects. - * - * @param array $serviceIds Array of service IDs - * @param array $services Available services indexed by ID - * - * @return array Array of resolved Service objects (missing services are skipped) - */ - private function resolveServiceArray(array $serviceIds, array $services): array - { - $resolved = []; - $addedIds = []; - - foreach ($serviceIds as $serviceId) { - if (isset($services[$serviceId]) && false === isset($addedIds[$serviceId])) { - $resolved[] = $services[$serviceId]; - $addedIds[$serviceId] = true; - } - } - - return $resolved; - } - - /** - * Resolves a pickup ID to a Pickup object. - */ - private function resolvePickup(?int $pickupId, Travel $travel): ?object - { - if (null === $pickupId) { - return null; - } - - return $travel->pickups[$pickupId] ?? $travel->dropOffs[$pickupId] ?? null; - } - - /** - * Resolves a drop-off ID to a Pickup object from the travel drop-offs list. - */ - private function resolveDropOff(?int $dropOffId, Travel $travel): ?object - { - if (null === $dropOffId) { - return null; - } - - return $travel->dropOffs[$dropOffId] ?? null; - } - - /** - * Resolves an insurance ID to an Insurance object. - */ - private function resolveInsurance(?string $insuranceId, Travel $travel): ?object - { - if (null === $insuranceId || '' === $insuranceId) { - return null; - } - - return $travel->insurances[$insuranceId] ?? null; - } - /** * Preserves mandatory services from API data when applying draft. * @@ -600,4 +366,64 @@ class BookingEditDraftService return $draftServices; } + + /** + * @param array $services + */ + private function resolveService(?int $serviceId, array $services): ?object + { + if (null === $serviceId) { + return null; + } + + return $services[$serviceId] ?? null; + } + + /** + * @param array $serviceIds + * @param array $services + * + * @return array + */ + private function resolveServiceArray(array $serviceIds, array $services): array + { + $resolved = []; + $addedIds = []; + + foreach ($serviceIds as $serviceId) { + if (isset($services[$serviceId]) && false === isset($addedIds[$serviceId])) { + $resolved[] = $services[$serviceId]; + $addedIds[$serviceId] = true; + } + } + + return $resolved; + } + + private function resolvePickup(?int $pickupId, Travel $travel): ?object + { + if (null === $pickupId) { + return null; + } + + return $travel->pickups[$pickupId] ?? $travel->dropOffs[$pickupId] ?? null; + } + + private function resolveDropOff(?int $dropOffId, Travel $travel): ?object + { + if (null === $dropOffId) { + return null; + } + + return $travel->dropOffs[$dropOffId] ?? null; + } + + private function resolveInsurance(?string $insuranceId, Travel $travel): ?object + { + if (null === $insuranceId || '' === $insuranceId) { + return null; + } + + return $travel->insurances[$insuranceId] ?? null; + } } diff --git a/tests/Service/BookingEditDraftServiceMutabilityTest.php b/tests/Service/BookingEditDraftServiceMutabilityTest.php index 4898e91..bc78531 100644 --- a/tests/Service/BookingEditDraftServiceMutabilityTest.php +++ b/tests/Service/BookingEditDraftServiceMutabilityTest.php @@ -14,6 +14,7 @@ use App\Form\Model\BookingDto; use App\Form\Model\ParticipantDto; use App\Repository\BookingEditDraftRepository; use App\Service\BookingEditDraftService; +use App\Service\BookingEditDraftParticipantApplier; use App\Service\BookingFingerprintService; use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\TestCase; @@ -36,6 +37,7 @@ class BookingEditDraftServiceMutabilityTest extends TestCase $this->createMock(BookingEditDraftRepository::class), $this->createMock(EntityManagerInterface::class), $this->createMock(BookingFingerprintService::class), + new BookingEditDraftParticipantApplier(), new NullLogger(), ); }