feat: refactor participant card DTOs and labels

This commit is contained in:
Björn Fromme
2026-04-11 18:28:32 +02:00
parent f22ceae41c
commit 1b0e479e49
10 changed files with 299 additions and 139 deletions
+43
View File
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Form\Model;
/**
* Typed participant card payload for create/edit overview rendering.
*/
final class ParticipantCardDataDto
{
/**
* @param array<string> $errorMessages
*/
public function __construct(
public readonly string $name,
public readonly string $email,
public readonly string $roomName,
public readonly ParticipantCardPriceDto $price,
public readonly bool $isCanceled,
public readonly bool $isValid = true,
public readonly array $errorMessages = [],
) {
}
/**
* Returns a copy with validation state applied.
*
* @param array<string> $errorMessages
*/
public function withValidation(bool $isValid, array $errorMessages): self
{
return new self(
name: $this->name,
email: $this->email,
roomName: $this->roomName,
price: $this->price,
isCanceled: $this->isCanceled,
isValid: $isValid,
errorMessages: $errorMessages,
);
}
}
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Form\Model;
/**
* Typed price display state for a participant card.
*/
final class ParticipantCardPriceDto
{
public function __construct(
public readonly ?float $amount,
public readonly bool $showDash,
) {
}
}
@@ -16,6 +16,7 @@ use App\Form\Model\RoomSelectionDto;
use App\Form\Service\Abstract\AbstractFieldOptionsProvider;
use App\Service\BookingPriceCalculatorService;
use App\Service\InsuranceService;
use App\Service\ServiceLabelFormatter;
use App\Service\ServiceAvailabilityCalculator;
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -42,6 +43,7 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
private readonly ServiceAvailabilityCalculator $serviceAvailabilityCalculator,
private readonly InsuranceService $insuranceService,
private readonly BookingPriceCalculatorService $priceCalculatorService,
private readonly ServiceLabelFormatter $serviceLabelFormatter,
private readonly TranslatorInterface $translator,
) {
parent::__construct();
@@ -303,11 +305,8 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
if (null === $service) {
return null;
}
$priceLabel = (null === $service->price || 0.0 === $service->price)
? 'inkl.'
: number_format($service->price, 2, ',', '.').' €';
return $service->label.' ('.$priceLabel.')';
return $this->serviceLabelFormatter->formatServiceLabel($service);
},
'choice_attr' => function (?Service $service) use ($bookingDto, $participantIndex) {
if (null === $service) {
@@ -405,7 +404,10 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
// Rental insurance field provider - provides rental insurance options when rental services are selected
$this->fieldOptionProviders['rentalInsurance'] = fn (BookingDto $bookingDto, int $participantIndex, array $options = []) => [
'label' => $this->getRentalInsuranceCheckboxLabel($bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_RENTAL_INSURANCE, true)),
'label' => $this->serviceLabelFormatter->formatServiceLabelForServices(
Constants::SERVICE_LABELS[Constants::TOKEN_RENTAL_INSURANCE],
$bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_RENTAL_INSURANCE, true)
),
'required' => false,
'property_path' => 'rentalInsuranceSelected',
'help' => $this->getRentalInsuranceDescription($bookingDto->travel->getAdditionalServicesBySubTypes(Constants::TOKEN_RENTAL_INSURANCE, true)),
@@ -645,7 +647,10 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
}
return [
'label' => $this->getParkingCheckboxLabel($parkingServices),
'label' => $this->serviceLabelFormatter->formatServiceLabelForServices(
Constants::SERVICE_LABELS[Constants::TOKEN_PARKING],
$parkingServices
),
'required' => false,
];
};
@@ -833,56 +838,6 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
});
}
/**
* Formats service label with pricing information.
*
* @param Service|null $service The service to format
*
* @return string The formatted label
*/
private function formatServiceLabelWithPrice(?Service $service): string
{
if (null === $service) {
return '';
}
if (null === $service->price) {
return $service->label;
}
if (0.0 === $service->price) {
return sprintf('%s (inkl.)', $service->label);
}
if ($service->price < 0) {
// Negative prices are discounts
return sprintf('%s (-%s€ Rabatt)', $service->label, number_format(abs($service->price), 2, ',', '.'));
}
return sprintf('%s (€%s)', $service->label, number_format($service->price, 2, ',', '.'));
}
/**
* Gets the parking checkbox label with pricing information.
*
* Creates a checkbox label for the single parking service including pricing.
* Since there's only ever one parking type, we take the first available service.
*
* @param array $parkingServices Array of available parking services
*
* @return string The formatted checkbox label with pricing
*/
private function getParkingCheckboxLabel(array $parkingServices): string
{
if (empty($parkingServices)) {
return Constants::SERVICE_LABELS[Constants::TOKEN_PARKING];
}
$parkingService = reset($parkingServices); // Get the first (and only) parking service
return $this->formatServiceLabelWithPrice($parkingService);
}
/**
* Checks if a service should be rendered as read-only due to unavailability.
*
@@ -1154,19 +1109,6 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
return '';
}
/**
* Generates label for rental insurance checkbox including pricing information.
*/
private function getRentalInsuranceCheckboxLabel(array $rentalInsuranceServices): string
{
if (empty($rentalInsuranceServices)) {
return Constants::SERVICE_LABELS[Constants::TOKEN_RENTAL_INSURANCE];
}
$rentalInsuranceService = reset($rentalInsuranceServices); // Get the first (and only) rental insurance service
return $this->formatServiceLabelWithPrice($rentalInsuranceService);
}
/**
* Gets the rental insurance description for help text.
*/