feat: prevent selection of baby rooms without regular rooms

This commit is contained in:
Björn Fromme
2026-03-16 11:59:11 +01:00
parent c6678d16a6
commit b872f2a5db
4 changed files with 247 additions and 11 deletions
+2 -11
View File
@@ -7,6 +7,7 @@ namespace App\Form\Model;
use App\BusProNet\Constants;
use App\BusProNet\Model\Booking;
use App\BusProNet\Model\Travel;
use App\Validator\Constraints as AppAssert;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
@@ -18,6 +19,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
* in participant DTOs regardless of mode, ensuring consistent data structure and
* simplifying pricing calculations, field handlers, and template rendering.
*/
#[AppAssert\RoomSelection(groups: ['booking_create_step_1'])]
class BookingDto
{
public const MODE_CREATE = 'create';
@@ -175,17 +177,6 @@ class BookingDto
return $room?->label;
}
#[Assert\Callback(callback: 'validateRoomSelection', groups: ['booking_create_step_1'])]
public function validateRoomSelection(ExecutionContextInterface $context): void
{
$selectedRooms = $this->getSelectedRooms();
if (0 === count($selectedRooms)) {
$context->buildViolation('Bitte mindestens ein Zimmer/Bett auswählen')
->addViolation();
}
}
#[Assert\Callback]
public function validateBankAccount(ExecutionContextInterface $context): void
{
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
#[\Attribute]
class RoomSelection extends Constraint
{
public const BABY_ROOM_CODE = 'Baby';
public string $noRoomSelectedMessage = 'Bitte mindestens ein Zimmer/Bett auswählen';
public string $onlyBabyRoomsMessage = 'Baby-Zimmer können nur in Kombination mit regulären Zimmern gebucht werden.';
public function getTargets(): array|string
{
return static::CLASS_CONSTRAINT;
}
}
@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace App\Validator\Constraints;
use App\Form\Model\BookingDto;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class RoomSelectionValidator extends ConstraintValidator
{
public function validate(mixed $value, Constraint $constraint): void
{
if (!$constraint instanceof RoomSelection) {
throw new UnexpectedTypeException($constraint, RoomSelection::class);
}
if (!$value instanceof BookingDto) {
throw new UnexpectedTypeException($value, BookingDto::class);
}
$selectedRooms = $value->getSelectedRooms();
// Check that at least one room is selected
if (0 === count($selectedRooms)) {
$this->context->buildViolation($constraint->noRoomSelectedMessage)
->addViolation();
return;
}
// Check that not only Baby rooms are selected
$this->assertNotOnlyBabyRooms($value, $constraint);
}
private function assertNotOnlyBabyRooms(BookingDto $bookingDto, RoomSelection $constraint): void
{
$availableRooms = $bookingDto->travel->getAvailableRooms();
$selectedRooms = $bookingDto->getSelectedRooms();
$hasRegularRoom = false;
$hasBabyRoom = false;
foreach ($selectedRooms as $roomSelection) {
$room = $availableRooms[$roomSelection->roomId] ?? null;
if (null === $room) {
continue;
}
if (RoomSelection::BABY_ROOM_CODE === $room->code) {
$hasBabyRoom = true;
} else {
$hasRegularRoom = true;
}
}
if ($hasBabyRoom && !$hasRegularRoom) {
$this->context->buildViolation($constraint->onlyBabyRoomsMessage)
->addViolation();
}
}
}