feat: proper determination of agency initiated bookings

For booking that are agency initiated (groups) different rules apply
concerning mutability and requirement of personal data and whether the
first participant is the applicant or not. This commit adds logic to
evaluate the agency id assigned to the booking data to decide.
This commit is contained in:
Björn Fromme
2026-03-16 12:02:27 +01:00
parent f55ed3dc4c
commit e222f448bb
20 changed files with 201 additions and 137 deletions
@@ -8,6 +8,7 @@ use App\BusProNet\ApiClient;
use App\BusProNet\DataProcessor\BookingDataProcessor;
use App\BusProNet\Model\Booking;
use App\BusProNet\Model\Notification;
use App\BusProNet\XmlLoader\AgencyLoader;
use App\Entity\User;
use App\Form\Model\BookingDto;
use App\Security\Crypt;
@@ -35,6 +36,7 @@ class BookingEditDataLoaderService
private readonly BookingFingerprintService $fingerprintService,
private readonly TravelDataService $travelDataService,
private readonly BookingEditDraftService $draftService,
private readonly AgencyLoader $agencyLoader,
private readonly Crypt $crypt,
private readonly TagAwareCacheInterface $bpnCache,
) {
@@ -125,6 +127,11 @@ class BookingEditDataLoaderService
$formData = $this->bookingDataProcessor->createBookingDtoFromBooking($bookingData, $travelData);
// Set agency code for internal agency detection (used by field state conditions)
$formData->agencyCode = null !== $bookingData->agencyId
? $this->agencyLoader->loadById($bookingData->agencyId)?->code
: null;
// Set original fingerprint BEFORE applying draft, so dirty detection
// compares against the original API data (not the draft-modified data)
$formData->originalFingerprint = $this->fingerprintService->generateFingerprint($formData, true);
+5
View File
@@ -5,6 +5,7 @@ namespace App\Service;
use App\BusProNet\Constants;
use App\BusProNet\Model\Room;
use App\BusProNet\Model\Travel;
use App\BusProNet\XmlLoader\AgencyLoader;
use App\Exception\BookingSessionNotFoundException;
use App\Exception\NoRoomsAvailableException;
use App\Form\Model\BookingDto;
@@ -28,6 +29,7 @@ class BookingService
private readonly TravelDataService $travelDataService,
private readonly BookingPriceCalculatorService $priceCalculator,
private readonly ParticipantEligibilityService $participantEligibilityService,
private readonly AgencyLoader $agencyLoader,
#[Autowire('%default_booking_status%')]
private readonly string $defaultBookingStatus,
) {
@@ -267,6 +269,9 @@ class BookingService
$bookingCreateDto->roomSelections = $roomSelections;
$bookingCreateDto->currentStep = 1;
$bookingCreateDto->agencyId = $agencyId;
$bookingCreateDto->agencyCode = null !== $agencyId
? $this->agencyLoader->loadById($agencyId)?->code
: null;
$bookingCreateDto->bookingStatus = $bookingStatus;
$this->saveBookingDto($request, $bookingCreateDto, BookingDto::MODE_CREATE);
+8 -3
View File
@@ -36,7 +36,7 @@ class ParticipantCardDataService
}
// Extract participant name with fallback
$name = $this->getParticipantName($participant, $index);
$name = $this->getParticipantName($bookingDto, $participant, $index);
// Extract email
$email = $participant->email ?? '';
@@ -78,7 +78,7 @@ class ParticipantCardDataService
/**
* Get participant name with fallback to generic label.
*/
private function getParticipantName(object $participant, int $index): string
private function getParticipantName(BookingDto $bookingDto, object $participant, int $index): string
{
$firstName = $participant->firstName ?? '';
$lastName = $participant->lastName ?? '';
@@ -86,7 +86,12 @@ class ParticipantCardDataService
$name = trim($firstName.' '.$lastName);
if ('' === $name) {
return 0 === $index ? 'Anmelder:in' : 'Teilnehmer:in';
// For internal agency bookings, first participant is not the applicant
if (0 === $index && false === $bookingDto->isInternalAgencyBooking()) {
return 'Anmelder:in';
}
return 'Teilnehmer:in';
}
return $name;