Files
myep/src/Form/Model/ParticipantDto.php
T

255 lines
8.2 KiB
PHP

<?php
namespace App\Form\Model;
use App\BusProNet\Model\Address;
use App\BusProNet\Model\Insurance;
use App\BusProNet\Model\PersonalData;
use App\BusProNet\Model\Pickup;
use App\BusProNet\Model\Service;
use App\Validator\Constraints as AppAssert;
use Symfony\Component\Validator\Constraints as Assert;
#[AppAssert\Participant(groups: ['booking_edit', 'booking_create'])]
class ParticipantDto
{
/**
* List of dynamic participant fields that can be conditionally hidden/shown.
*/
public const DYNAMIC_FIELDS = [
'assignedRoomId',
'remarksRoom',
'courses',
'additionalServices',
'board',
'rentals',
'rentalInsurance',
'skiPass',
'transportationOutbound',
'transportationInbound',
'pickup',
'parking',
'licensePlate',
'bulkInsuranceBooking',
'insurance',
];
public ?int $index = null;
public ?int $addressId = null;
public ?int $personId = null;
public ?string $status = null;
public bool $mutable = false;
public bool $touched = false;
public ?string $firstName = null;
public ?string $lastName = null;
public ?string $title = null;
public ?string $gender = null;
public ?string $nationality = null;
public ?string $height = null;
public ?string $shoeSize = null;
public ?string $weight = null;
public ?\DateTimeImmutable $dateOfBirth = null;
#[Assert\Email(message: 'Bitte eine gültige E-Mail Adresse angeben', mode: 'strict', groups: ['booking_edit', 'booking_create'])]
public ?string $email = null;
public ?string $mobile = null;
#[Assert\Valid(groups: ['booking_edit', 'booking_create'])]
#[Assert\NotNull(message: 'Bitte Adresse angeben', groups: ['applicant_address'])]
public ?Address $address = null;
#[Assert\NotNull(message: 'Bitte ein Zimmer auswählen', groups: ['booking_create'])]
public ?int $assignedRoomId = null;
public ?string $remarksRoom = null;
public array $courses = [];
public array $additionalServices = [];
public ?Service $skiPass = null;
public array $board = [];
public array $rentals = [];
public ?Service $rentalInsurance = null;
// Rental insurance checkbox state (boolean: true if rental insurance requested)
public bool $rentalInsuranceSelected = false;
// Transportation services with improved naming (outbound/inbound)
public ?Service $transportationOutbound = null;
public ?Service $transportationInbound = null;
// Pickup location (applies to both directions)
public ?Pickup $pickup = null;
// Parking service for self-organized transportation (boolean: true if parking requested)
public bool $parking = false;
// Parking service object for pricing calculation (new, contains actual service with pricing)
public ?Service $parkingService = null;
// License plate for participants with parking (optional, visible only when parking is selected)
public ?string $licensePlate = null;
// Selected insurance for this participant (individual insurance selection per participant)
public ?Insurance $insurance = null;
// Bulk insurance booking flag (applicant only: when checked, assigns same insurance type to all participants)
public bool $bulkInsuranceBooking = false;
/**
* @var array<array{type: string, message: string}> Notification messages for user feedback
*/
public array $notifications = [];
public function __construct()
{
$this->address = new Address();
}
public static function fromPersonalData(PersonalData $personalData): static
{
$instance = new static();
$instance->status = $personalData->status;
$instance->addressId = $personalData->addressId;
$instance->personId = $personalData->personId;
$instance->mutable = $personalData->mutable;
$instance->firstName = $personalData->firstName;
$instance->lastName = $personalData->name;
$instance->title = $personalData->title;
$instance->gender = $personalData->gender;
$instance->nationality = $personalData->nationality;
$instance->email = $personalData->communication?->email;
$instance->mobile = $personalData->communication?->mobile;
$instance->dateOfBirth = $personalData->dateOfBirth;
$instance->height = $personalData->height;
$instance->weight = $personalData->weight;
$instance->shoeSize = $personalData->shoeSize;
// Clone address to prevent shared object references that could cause mutations
$instance->address = null !== $personalData->address ? clone $personalData->address : null;
$instance->remarksRoom = $personalData->remarksRoom;
$instance->licensePlate = $personalData->licensePlate;
return $instance;
}
public function isApplicant(): bool
{
return 0 === $this->index;
}
public function isCanceled(): bool
{
return 'S' === $this->status;
}
public function isOption(): bool
{
return 'O' === $this->status;
}
/**
* Calculates the participant's age in complete years.
*
* Uses the same logic as existing age evaluators in the system
* for consistency across age-related calculations.
*
* @param \DateTimeImmutable|null $referenceDate The date to calculate age at (defaults to current date)
*
* @return int|null The calculated age in complete years, or null if no birth date
*/
public function getAge(?\DateTimeImmutable $referenceDate = null): ?int
{
if (null === $this->dateOfBirth) {
return null;
}
$referenceDate = $referenceDate ?? new \DateTimeImmutable();
return $this->dateOfBirth->diff($referenceDate)->y;
}
/**
* Checks if the participant has selected an insurance.
*
* @return bool True if an insurance is selected
*/
public function hasInsuranceSelected(): bool
{
return null !== $this->insurance;
}
/**
* Gets the insurance label for display purposes.
*
* @return string|null The insurance label or null if no insurance selected
*/
public function getInsuranceLabel(): ?string
{
return $this->insurance?->label;
}
/**
* Gets the insurance price for pricing calculations.
*
* @return float The insurance price (0.0 if no insurance selected)
*/
public function getInsurancePrice(): float
{
return $this->insurance?->price ?? 0.0;
}
/**
* Adds a notification message for user feedback.
*
* Uses an optional ID to prevent duplicate notifications. If no ID is provided,
* generates one from the type and message combination. Duplicate IDs will
* overwrite previous notifications, ensuring each unique notification appears once.
*
* @param string $type The notification type (info, warning, success)
* @param string $message The notification message
* @param string|null $id Optional unique identifier (auto-generated if null)
*/
public function addNotification(string $type, string $message, ?string $id = null): void
{
// Generate ID from type and message if not provided
$notificationId = $id ?? md5($type.'_'.$message);
// Use ID as key to automatically prevent duplicates
$this->notifications[$notificationId] = [
'type' => $type,
'message' => $message,
];
}
/**
* Determines if the participant is a child based on age at current date.
*
* A child is defined as someone under the specified age threshold (default: 16 years).
* This classification is used for email uniqueness validation (children can share emails with adults).
*
* @param int $ageThreshold The age threshold for child classification (default: 16)
*
* @return bool True if participant is under the age threshold, false otherwise or if age unknown
*/
public function isChild(int $ageThreshold = 16): bool
{
$age = $this->getAge();
// Treat unknown age as adult for safety (requires email uniqueness)
if (null === $age) {
return false;
}
return $ageThreshold > $age;
}
}