feat: extract participant draft field applier

This commit is contained in:
Björn Fromme
2026-04-12 14:40:05 +02:00
parent 2597318488
commit 98dd685679
3 changed files with 227 additions and 245 deletions
@@ -0,0 +1,154 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
/**
* Applies participant-level draft payloads onto booking DTO participants.
*/
class BookingEditDraftParticipantApplier
{
public function apply(
BookingDto $bookingDto,
int $participantIndex,
ParticipantDto $participant,
array $data,
): void {
$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'];
}
// 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'];
}
}
}
+71 -245
View File
@@ -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<int, object> $services
*/
private function resolveService(?int $serviceId, array $services): ?object
{
if (null === $serviceId) {
return null;
}
return $services[$serviceId] ?? null;
}
/**
* @param array<int, int> $serviceIds
* @param array<int, object> $services
*
* @return array<int, object>
*/
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;
}
}