feat: unassign rooms from other participants when unavailable

This commit is contained in:
Björn Fromme
2026-03-16 11:59:11 +01:00
parent 6cc5886073
commit 647fa8e212
8 changed files with 576 additions and 95 deletions
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\Form\ChoiceLoader;
use App\Form\Model\ParticipantDto;
use App\Form\Model\RoomSelectionDto;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
@@ -13,25 +12,21 @@ use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
/**
* Choice loader for participant room assignments.
*
* Generates room choices for each participant based on availability,
* current assignments, and room capacity constraints. This loader
* ensures participants can only select rooms with available capacity
* while maintaining their current assignment if applicable.
* Generates room choices for each participant from all selected room types
* in Step 1, regardless of current capacity or assignments. Capacity conflicts
* are automatically resolved by the field handler through intelligent
* auto-unassignment when needed.
*/
class ParticipantRoomChoiceLoader implements ChoiceLoaderInterface
{
private ?ChoiceListInterface $choiceList = null;
/**
* @param ParticipantDto[] $allParticipants all participant DTOs from the root form
* @param RoomSelectionDto[] $selectedRooms the rooms selected in the previous step
* @param int $participantIndex the index of the current participant
* @param RoomSelectionDto[] $selectedRooms the rooms selected in the previous step
*/
public function __construct(
private readonly ChoiceListFactoryInterface $factory,
private readonly array $allParticipants,
private readonly array $selectedRooms,
private readonly int $participantIndex,
) {
}
@@ -66,50 +61,20 @@ class ParticipantRoomChoiceLoader implements ChoiceLoaderInterface
/**
* Generates room choices for the specific participant.
*
* Always includes all selected rooms from Step 1 regardless of current capacity.
* Capacity conflicts are handled by the field handler during assignment.
*
* @return array<string, int>
*/
private function generateRoomChoicesForParticipant(): array
{
$roomOccupancy = $this->calculateRoomOccupancy();
$participantRoomChoices = [];
$assignedRoomId = $this->allParticipants[$this->participantIndex]->assignedRoomId ?? null;
foreach ($this->selectedRooms as $roomSelection) {
$currentOccupancy = $roomOccupancy[$roomSelection->roomId] ?? 0;
// If this participant is already assigned to this room, exclude them from occupancy count
$adjustedOccupancy = $currentOccupancy;
if ($assignedRoomId === $roomSelection->roomId) {
--$adjustedOccupancy;
}
$totalCapacity = $roomSelection->quantity * $roomSelection->capacity;
$remainingCapacity = $totalCapacity - $adjustedOccupancy;
// Include room if it has capacity OR if it's the participant's current assignment
if ($remainingCapacity > 0 || $assignedRoomId === $roomSelection->roomId) {
$participantRoomChoices[$roomSelection->roomLabel] = $roomSelection->roomId;
}
// Always include all selected rooms - capacity conflicts handled in field handler
$participantRoomChoices[$roomSelection->roomLabel] = $roomSelection->roomId;
}
return $participantRoomChoices;
}
/**
* Calculates room occupancy based on all participant assignments.
*
* @return array<int, int>
*/
private function calculateRoomOccupancy(): array
{
$roomOccupancy = [];
foreach ($this->allParticipants as $participant) {
if (null !== $participant->assignedRoomId) {
$roomOccupancy[$participant->assignedRoomId] = ($roomOccupancy[$participant->assignedRoomId] ?? 0) + 1;
}
}
return $roomOccupancy;
}
}