Files
myep/src/BusProNet/DataProcessor/ParticipantServiceProcessor.php
T

334 lines
14 KiB
PHP

<?php
declare(strict_types=1);
namespace App\BusProNet\DataProcessor;
use App\BusProNet\Model\Booking;
use App\BusProNet\Model\Service;
use App\BusProNet\Model\Travel;
use App\Form\Model\ParticipantDto;
use Psr\Log\LoggerInterface;
/**
* Processes participant service selections for booking updates.
*
* Handles the assignment and mapping of services (additional, transportation,
* pickup, room, insurance) from participant DTOs to the booking model during
* the update request workflow.
*/
class ParticipantServiceProcessor
{
public function __construct(
private readonly LoggerInterface $logger,
) {
}
/**
* Resets all existing participant-to-service mappings to start with a clean slate.
*
* This ensures that service assignments are rebuilt from scratch based on current form selections.
* Includes resetting room mappings to allow room reassignments during edit.
*
* @param Booking $bookingData The booking data object containing services to reset
*/
public function resetServiceMappings(Booking $bookingData): void
{
$servicesToReset = [
...$bookingData->additionalServices,
...$bookingData->transportationServices,
...$bookingData->pickups,
...$bookingData->dropOffs,
...$bookingData->rooms,
...$bookingData->insurances,
];
foreach ($servicesToReset as $service) {
$service->mapping = [];
}
}
/**
* Processes all services for a single participant.
*
* This orchestrator method handles the complete service assignment workflow for one participant,
* including additional services, transportation services, pickup locations, and room assignments.
*
* @param ParticipantDto $participant The participant data from the form
* @param Booking $bookingData The booking data object to update
* @param Travel $travelData The travel data containing available services
*/
public function processParticipantServices(ParticipantDto $participant, Booking $bookingData, Travel $travelData): void
{
if (true === $participant->isCanceled()) {
return;
}
$this->processAdditionalServices($participant, $bookingData, $travelData);
$this->processTransportationServices($participant, $bookingData, $travelData);
$this->processPickupLocations($participant, $bookingData);
$this->processDropOffLocations($participant, $bookingData);
$this->processRoomAssignment($participant, $bookingData);
$this->processInsurance($participant, $bookingData, $travelData);
}
/**
* Removes services and pickups with no participant mappings.
*
* Cleans up unused services to prevent empty services from being sent to the API.
* This includes additional services, transportation services, pickup locations, and insurances.
*
* @param Booking $bookingData The booking data object to clean up
*/
public function removeUnusedServices(Booking $bookingData): void
{
foreach ($bookingData->additionalServices as $service) {
if (0 === count($service->mapping)) {
unset($bookingData->additionalServices[$service->id]);
}
}
foreach ($bookingData->transportationServices as $service) {
if (0 === count($service->mapping)) {
unset($bookingData->transportationServices[$service->id]);
}
}
foreach ($bookingData->pickups as $pickup) {
if (0 === count($pickup->mapping)) {
unset($bookingData->pickups[$pickup->id]);
}
}
foreach ($bookingData->dropOffs as $dropOff) {
if (0 === count($dropOff->mapping)) {
unset($bookingData->dropOffs[$dropOff->id]);
}
}
foreach ($bookingData->insurances as $insurance) {
if (0 === count($insurance->mapping)) {
unset($bookingData->insurances[$insurance->id]);
}
}
}
/**
* Collects additional services from a participant for mapping.
*
* Extracts all additional services (courses, board, rentals, ski pass, rental insurance,
* parking, additional services) from a participant into a flat array of Service objects.
*
* @param ParticipantDto $participant The participant data
*
* @return array<Service> Array of services selected by this participant
*/
private function collectParticipantAdditionalServices(ParticipantDto $participant): array
{
$services = [
...$participant->courses,
...$participant->additionalServices,
...$participant->board,
...$participant->rentals,
];
// Veg is encoded as an additional service in the edit/update payload.
if (null !== $participant->veg) {
$services[] = $participant->veg;
}
// Add ski pass if selected (single service, not an array)
if (null !== $participant->skiPass) {
$services[] = $participant->skiPass;
}
// Add rental insurance if selected (single service, not an array)
if (null !== $participant->rentalInsurance) {
$services[] = $participant->rentalInsurance;
}
// Add parking service if selected (for self-organized PKW transportation)
if (true === $participant->parking && null !== $participant->parkingService) {
$services[] = $participant->parkingService;
}
return $services;
}
/**
* Processes additional services for a participant.
*
* Maps additional services (courses, ski passes, board options, rentals) to the participant.
* Adds new services to the booking if they don't already exist and sets individual pricing.
*
* @param ParticipantDto $participant The participant data from the form
* @param Booking $bookingData The booking data object to update
* @param Travel $travelData The travel data containing available services
*/
private function processAdditionalServices(ParticipantDto $participant, Booking $bookingData, Travel $travelData): void
{
$servicesToMap = $this->collectParticipantAdditionalServices($participant);
foreach ($servicesToMap as $service) {
if (false === isset($bookingData->additionalServices[$service->id])) {
$serviceToAdd = $travelData->additionalServices[$service->id] ?? null;
if (null === $serviceToAdd) {
$this->logger->warning('Additional service not found in travel data, skipping mapping', [
'bookingId' => $bookingData->id,
'serviceId' => $service->id,
'participantIndex' => $participant->index,
]);
continue;
}
$bookingData->additionalServices[$service->id] = $serviceToAdd;
$bookingData->additionalServices[$service->id]->individualPrice[$participant->index] = $serviceToAdd->price;
}
$bookingData->additionalServices[$service->id]->mapping[] = $participant->index;
}
}
/**
* Processes transportation services for a participant.
*
* Maps transportation services (both directions: to and from destination) to the participant.
* Adds new transportation services to the booking if they don't already exist.
*
* @param ParticipantDto $participant The participant data from the form
* @param Booking $bookingData The booking data object to update
* @param Travel $travelData The travel data containing available services
*/
private function processTransportationServices(ParticipantDto $participant, Booking $bookingData, Travel $travelData): void
{
foreach ([$participant->transportationOutbound, $participant->transportationInbound] as $service) {
if (null === $service) {
continue;
}
if (false === isset($bookingData->transportationServices[$service->id])) {
$serviceToAdd = $travelData->transportationServices[$service->id] ?? null;
if (null === $serviceToAdd) {
$this->logger->warning('Transportation service not found in travel data, skipping mapping', [
'bookingId' => $bookingData->id,
'serviceId' => $service->id,
'participantIndex' => $participant->index,
]);
continue;
}
$bookingData->transportationServices[$service->id] = $serviceToAdd;
$bookingData->transportationServices[$service->id]->individualPrice[$participant->index] = $serviceToAdd->price;
}
$bookingData->transportationServices[$service->id]->mapping[] = $participant->index;
}
}
/**
* Processes pickup locations for participants using bus transportation.
*
* Only processes pickup locations for bus transportation services and maps the participant
* to their selected pickup location.
*
* @param ParticipantDto $participant The participant data from the form
* @param Booking $bookingData The booking data object to update
*/
private function processPickupLocations(ParticipantDto $participant, Booking $bookingData): void
{
// Check if either transportation direction is BUS and pickup is selected
$hasOutboundBus = null !== $participant->transportationOutbound && 'BUS' === $participant->transportationOutbound->subType;
$hasInboundBus = null !== $participant->transportationInbound && 'BUS' === $participant->transportationInbound->subType;
if (($hasOutboundBus || $hasInboundBus) && null !== $selectedPickup = $participant->pickup) {
if (false === isset($bookingData->pickups[$selectedPickup->id])) {
$bookingData->pickups[$selectedPickup->id] = $selectedPickup;
}
$bookingData->pickups[$selectedPickup->id]->mapping[] = $participant->index;
}
}
/**
* Processes drop-off locations for participants using bus transportation.
*
* Only processes drop-off locations for bus transportation services and maps the participant
* to their selected drop-off location.
*
* @param ParticipantDto $participant The participant data from the form
* @param Booking $bookingData The booking data object to update
*/
private function processDropOffLocations(ParticipantDto $participant, Booking $bookingData): void
{
$hasOutboundBus = null !== $participant->transportationOutbound
&& 'BUS' === $participant->transportationOutbound->subType;
$hasInboundBus = null !== $participant->transportationInbound
&& 'BUS' === $participant->transportationInbound->subType;
if (($hasOutboundBus || $hasInboundBus) && null !== $selectedDropOff = $participant->dropOff) {
if (false === isset($bookingData->dropOffs[$selectedDropOff->id])) {
$bookingData->dropOffs[$selectedDropOff->id] = $selectedDropOff;
}
$bookingData->dropOffs[$selectedDropOff->id]->mapping[] = $participant->index;
}
}
/**
* Processes room assignment for a participant.
*
* Maps the participant to their assigned room. Allows room reassignment during
* booking edits while maintaining the constraint that participants can only be
* assigned to room types that have already been booked.
*
* @param ParticipantDto $participant The participant data from the form
* @param Booking $bookingData The booking data object to update
*/
private function processRoomAssignment(ParticipantDto $participant, Booking $bookingData): void
{
if (null === $participant->assignedRoomId) {
return;
}
// Find the room in the existing booking by ID
foreach ($bookingData->rooms as $room) {
if ($room->id === $participant->assignedRoomId) {
$room->mapping[] = $participant->index;
break;
}
}
}
/**
* Processes travel insurance for a participant.
*
* Maps insurance to the participant. Adds new insurances to the booking if they don't exist.
* Insurance can be either individual or package-based, with automatic price-tier adjustment.
*
* IMPORTANT: Excludes synthetic "no insurance" option from BPN transmission.
*
* @param ParticipantDto $participant The participant data from the form
* @param Booking $bookingData The booking data object to update
* @param Travel $travelData The travel data containing available insurances
*/
private function processInsurance(ParticipantDto $participant, Booking $bookingData, Travel $travelData): void
{
// Skip if no insurance or if participant selected "keine Versicherung gewünscht"
if (null === $participant->insurance || $participant->insurance->isNoInsurance()) {
return;
}
$insurance = $participant->insurance;
if (false === isset($bookingData->insurances[$insurance->id])) {
$insuranceToAdd = $travelData->insurances[$insurance->id] ?? null;
if (null !== $insuranceToAdd) {
$bookingData->insurances[$insurance->id] = $insuranceToAdd;
$bookingData->insurances[$insurance->id]->individualPrice[$participant->index] = $insuranceToAdd->price;
}
}
// Only add mapping if insurance exists in booking data
// This can legitimately be false if the insurance is no longer available in current travel data
if (isset($bookingData->insurances[$insurance->id])) {
$bookingData->insurances[$insurance->id]->mapping[] = $participant->index;
}
}
}