feat: move prepopulation guards into participant service

This commit is contained in:
Björn Fromme
2026-04-11 18:28:32 +02:00
parent dbc0b4acee
commit 63e8a068b3
9 changed files with 114 additions and 289 deletions
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Controller\Booking\Create;
use App\Entity\User;
use App\Controller\Booking\Traits\BookingCreateTrait;
use App\Controller\Booking\Traits\BookingExceptionHandlerTrait;
use App\Form\BookingCreateStep2Type;
@@ -65,12 +66,18 @@ class Step2Controller extends AbstractController
// Enrich with fresh availability data
$this->travelDataService->enrichWithFreshAvailabilities($bookingCreateDto->travel);
// Ensure correct number of participants with prepopulation callback
$this->participantCountService->ensureCorrectNumberOfParticipants(
$bookingCreateDto,
$this->getUser(),
fn ($user, $participant) => $this->prepopulationService->prepopulateApplicantFromUser($user, $participant)
);
// Ensure correct number of participants first, then prepopulate the applicant if needed.
$this->participantCountService->ensureCorrectNumberOfParticipants($bookingCreateDto);
$user = $this->getUser();
if ($user instanceof User
&& isset($bookingCreateDto->participants[0])
&& $this->prepopulationService->shouldPrepopulateApplicant($bookingCreateDto->participants[0])) {
$bookingCreateDto->participants[0] = $this->prepopulationService->prepopulateApplicantFromUser(
$user,
$bookingCreateDto->participants[0]
);
}
// Validate room assignments against current selection (handles back-navigation from step 2 to step 1)
$this->roomAssignmentService->validateAndResetInvalidAssignments($bookingCreateDto);
@@ -6,12 +6,12 @@ namespace App\Controller\Booking\Create;
use App\Form\BookingParticipantType;
use App\Form\Model\BookingDto;
use App\Form\Service\DummyDataFillService;
use App\Htmx\HxTrait;
use App\Service\BookingService;
use App\Service\BookingSessionService;
use App\Service\BookingSummaryDataService;
use App\Service\ParticipantFormSupportService;
use App\Service\ParticipantPrepopulationService;
use App\Service\TravelDataService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface;
@@ -31,7 +31,7 @@ class Step2ParticipantController extends AbstractController
private readonly BookingSessionService $bookingSessionService,
private readonly BookingSummaryDataService $summaryDataService,
private readonly TravelDataService $travelDataService,
private readonly DummyDataFillService $dummyDataFillService,
private readonly ParticipantPrepopulationService $prepopulationService,
private readonly ParticipantFormSupportService $participantFormSupportService,
) {
}
@@ -62,11 +62,11 @@ class Step2ParticipantController extends AbstractController
$isSubmitted = $form->isSubmitted();
$isDummyDataFill = $this
->dummyDataFillService
->isTokenMatch($bookingDto->participants[$index], $bookingDto->getMode())
->prepopulationService
->isDummyDataFillRequested($bookingDto->participants[$index], $bookingDto->getMode())
;
if (true === $isSubmitted && true === $isDummyDataFill) {
$this->dummyDataFillService->fill($bookingDto->participants[$index], $index);
$this->prepopulationService->fillDummyParticipant($bookingDto->participants[$index], $index);
$this->bookingService->preselectDefaultServices($bookingDto);
$this->bookingService->applyCreateBookingStatusRules($bookingDto);
-71
View File
@@ -1,71 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Form\Service;
use App\BusProNet\Model\Address;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use Carbon\CarbonImmutable;
/**
* Fills participant forms with generated dummy data for testing and demo workflows.
*
* When a special token is entered in the lastName field during booking creation,
* this service detects it and fills contact data fields with generated values
* based on the participant number and current time. Only active in create mode.
*/
class DummyDataFillService
{
public const TOKEN = '#KUN#';
/**
* Checks if the participant's lastName matches the fill token.
*
* Only matches in create mode to prevent accidental triggering during
* editing of existing bookings.
*
* @param ParticipantDto $participant The participant to check
* @param string $mode The booking mode (create or edit)
*
* @return bool True if the token matches and mode is create
*/
public function isTokenMatch(ParticipantDto $participant, string $mode): bool
{
if (BookingDto::MODE_CREATE !== $mode) {
return false;
}
return self::TOKEN === $participant->lastName;
}
/**
* Fills the participant DTO with generated dummy personal data.
*
* Generates firstName, lastName, email, mobile, date of birth and address
* values based on the participant number (1-based). The lastName includes
* the current time for easy identification of test bookings.
*
* @param ParticipantDto $participant The participant DTO to fill
* @param int $participantIndex The zero-based participant index
*/
public function fill(ParticipantDto $participant, int $participantIndex): void
{
$participantNumber = $participantIndex + 1;
$now = CarbonImmutable::now();
$participant->firstName = sprintf('Vorname %d', $participantNumber);
$participant->lastName = sprintf('Muster %d %s', $participantNumber, $now->format('H:i'));
$participant->mobile = '0171/111111';
$participant->email = sprintf('teilnehmer.in%[email protected]', $participantNumber);
$participant->dateOfBirth = $now->subYears(20)->toImmutable();
$address = new Address();
$address->street = 'Musterstr. 123';
$address->postCode = '99999';
$address->city = 'MusterOrt';
$address->country = 'Deutschland';
$participant->address = $address;
}
}
+2 -20
View File
@@ -7,7 +7,6 @@ namespace App\Service;
use App\BusProNet\Model\Travel;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Keeps participant-count shaping separate from booking orchestration.
@@ -17,13 +16,9 @@ class BookingParticipantCountService
/**
* Ensures the booking DTO has the expected number of participant objects.
*
* @param callable|null $prepopulateCallback fn(UserInterface, ParticipantDto): ParticipantDto
*/
public function ensureCorrectNumberOfParticipants(
BookingDto $bookingDto,
?UserInterface $user = null,
?callable $prepopulateCallback = null,
): void {
public function ensureCorrectNumberOfParticipants(BookingDto $bookingDto): void
{
$participantsCount = $this->calculateParticipantsCount($bookingDto->roomSelections, $bookingDto->travel);
$existingParticipants = $bookingDto->participants;
@@ -33,11 +28,6 @@ class BookingParticipantCountService
$participant = $existingParticipants[$i] ?? new ParticipantDto();
$participant->index = $i;
// Prepopulate applicant from authenticated user (index 0 only)
if (0 === $i && null !== $user && null !== $prepopulateCallback && $this->shouldPrepopulate($participant)) {
$participant = $prepopulateCallback($user, $participant);
}
$bookingDto->participants[$i] = $participant;
}
}
@@ -63,12 +53,4 @@ class BookingParticipantCountService
return $participantsCount;
}
/**
* Only prepopulate if the participant is fresh.
*/
private function shouldPrepopulate(ParticipantDto $participant): bool
{
return null === $participant->firstName || '' === $participant->firstName;
}
}
@@ -5,10 +5,13 @@ declare(strict_types=1);
namespace App\Service;
use App\BusProNet\ApiClient;
use App\BusProNet\Model\Address;
use App\BusProNet\Model\Notification;
use App\Entity\User;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\Security\Crypt;
use Carbon\CarbonImmutable;
use Psr\Log\LoggerInterface;
/**
@@ -20,6 +23,8 @@ use Psr\Log\LoggerInterface;
*/
class ParticipantPrepopulationService
{
public const TOKEN = '#KUN#';
public function __construct(
private readonly ApiClient $apiClient,
private readonly Crypt $crypt,
@@ -94,4 +99,50 @@ class ParticipantPrepopulationService
return $applicant;
}
}
/**
* Determines whether the applicant should be prepopulated.
*
* Only prepopulates if the participant is fresh.
*/
public function shouldPrepopulateApplicant(ParticipantDto $applicant): bool
{
return null === $applicant->firstName || '' === $applicant->firstName;
}
/**
* Checks if the participant's last name triggers dummy data fill.
*
* Only matches in create mode to avoid accidental triggering during edits.
*/
public function isDummyDataFillRequested(ParticipantDto $participant, string $mode): bool
{
if (BookingDto::MODE_CREATE !== $mode) {
return false;
}
return self::TOKEN === $participant->lastName;
}
/**
* Fills the participant DTO with generated dummy personal data.
*/
public function fillDummyParticipant(ParticipantDto $participant, int $participantIndex): void
{
$participantNumber = $participantIndex + 1;
$now = CarbonImmutable::now();
$participant->firstName = sprintf('Vorname %d', $participantNumber);
$participant->lastName = sprintf('Muster %d %s', $participantNumber, $now->format('H:i'));
$participant->mobile = '0171/111111';
$participant->email = sprintf('teilnehmer.in%[email protected]', $participantNumber);
$participant->dateOfBirth = $now->subYears(20)->toImmutable();
$address = new Address();
$address->street = 'Musterstr. 123';
$address->postCode = '99999';
$address->city = 'MusterOrt';
$address->country = 'Deutschland';
$participant->address = $address;
}
}