fix: keep the booking room assignment when restoring an edit draft
This commit is contained in:
@@ -18,8 +18,9 @@ use App\Form\Model\ParticipantDto;
|
||||
*
|
||||
* Service selections are gated by travel-level mutability flags (additionalServicesMutable,
|
||||
* transportationServicesMutable, pickupsMutable). Insurance is always applied regardless
|
||||
* of mutability. Merge strategy (only apply if resolves to a valid service) is used for
|
||||
* single-select fields; overwrite strategy is used for multi-select and boolean fields.
|
||||
* of mutability. Merge strategy (only apply if it resolves against the travel data) is used
|
||||
* for single-select fields, room assignment included; overwrite strategy is used for
|
||||
* multi-select, boolean and free-text fields.
|
||||
*/
|
||||
class BookingEditDraftMerger
|
||||
{
|
||||
@@ -52,7 +53,7 @@ class BookingEditDraftMerger
|
||||
|
||||
// Room assignment
|
||||
if (true === isset($data['roomAssignment']) && true === is_array($data['roomAssignment'])) {
|
||||
$this->applyRoomAssignment($participant, $data['roomAssignment']);
|
||||
$this->applyRoomAssignment($participant, $data['roomAssignment'], $travel);
|
||||
}
|
||||
|
||||
// License plate
|
||||
@@ -156,12 +157,27 @@ class BookingEditDraftMerger
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $data */
|
||||
private function applyRoomAssignment(ParticipantDto $participant, array $data): void
|
||||
/**
|
||||
* Applies the drafted room assignment to the participant.
|
||||
*
|
||||
* The room id uses the same merge strategy as single-select services: it is only
|
||||
* applied when it resolves to a room of the current travel. A draft may therefore move
|
||||
* a participant to another room, but never unassign one. Edit mode renders the room as
|
||||
* static text and never submits assignedRoomId, so a null in the draft is only ever an
|
||||
* artefact of a snapshot taken before BusPro reported the assignment - replaying it
|
||||
* would drop the participant's room, both in the UI and in the outbound zuordnung.
|
||||
*
|
||||
* remarksRoom keeps overwrite semantics: it is free text the user can deliberately clear.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
private function applyRoomAssignment(ParticipantDto $participant, array $data, Travel $travel): void
|
||||
{
|
||||
if (true === array_key_exists('assignedRoomId', $data)) {
|
||||
$participant->assignedRoomId = $data['assignedRoomId'];
|
||||
$assignedRoomId = $data['assignedRoomId'] ?? null;
|
||||
if (null !== $assignedRoomId && null !== $travel->getRoomById((int) $assignedRoomId)) {
|
||||
$participant->assignedRoomId = (int) $assignedRoomId;
|
||||
}
|
||||
|
||||
if (true === array_key_exists('remarksRoom', $data)) {
|
||||
$participant->remarksRoom = $data['remarksRoom'];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Service;
|
||||
|
||||
use App\BusProNet\Model\Room;
|
||||
use App\BusProNet\Model\Travel;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use App\Service\BookingEditDraftMerger;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Tests that draft restoration never unassigns a room.
|
||||
*
|
||||
* Edit mode renders the room as static text and never submits assignedRoomId, so a null in
|
||||
* the draft payload is always an artefact of a snapshot taken before BusPro reported the
|
||||
* assignment - typically for seats added to a group booking shortly before the draft was
|
||||
* saved. Replaying such a null would strip the room from the UI and from the outbound
|
||||
* zuordnung, so the room id uses the same merge strategy as single-select services.
|
||||
*/
|
||||
class BookingEditDraftMergerRoomAssignmentTest extends TestCase
|
||||
{
|
||||
private BookingEditDraftMerger $merger;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->merger = new BookingEditDraftMerger();
|
||||
}
|
||||
|
||||
public function testDraftDoesNotUnassignRoomWhenDraftValueIsNull(): void
|
||||
{
|
||||
$participant = new ParticipantDto();
|
||||
$participant->mutable = true;
|
||||
$participant->assignedRoomId = 74;
|
||||
|
||||
$travel = $this->createTravel([74 => $this->createRoom(74, 'Bett im Mehrbettzimmer')]);
|
||||
|
||||
$this->apply($travel, $participant, ['assignedRoomId' => null]);
|
||||
|
||||
$this->assertSame(74, $participant->assignedRoomId);
|
||||
}
|
||||
|
||||
public function testDraftAppliesRoomThatResolvesAgainstTravel(): void
|
||||
{
|
||||
$participant = new ParticipantDto();
|
||||
$participant->mutable = true;
|
||||
$participant->assignedRoomId = 74;
|
||||
|
||||
$travel = $this->createTravel([
|
||||
74 => $this->createRoom(74, 'Bett im Mehrbettzimmer'),
|
||||
75 => $this->createRoom(75, 'Doppelzimmer'),
|
||||
]);
|
||||
|
||||
$this->apply($travel, $participant, ['assignedRoomId' => 75]);
|
||||
|
||||
$this->assertSame(75, $participant->assignedRoomId);
|
||||
}
|
||||
|
||||
public function testDraftSkipsRoomThatIsUnknownToTravel(): void
|
||||
{
|
||||
$participant = new ParticipantDto();
|
||||
$participant->mutable = true;
|
||||
$participant->assignedRoomId = 74;
|
||||
|
||||
$travel = $this->createTravel([74 => $this->createRoom(74, 'Bett im Mehrbettzimmer')]);
|
||||
|
||||
$this->apply($travel, $participant, ['assignedRoomId' => 999]);
|
||||
|
||||
$this->assertSame(74, $participant->assignedRoomId);
|
||||
}
|
||||
|
||||
public function testDraftClearsRoomRemarks(): void
|
||||
{
|
||||
$participant = new ParticipantDto();
|
||||
$participant->mutable = true;
|
||||
$participant->assignedRoomId = 74;
|
||||
$participant->remarksRoom = 'Bitte mit Lisa';
|
||||
|
||||
$travel = $this->createTravel([74 => $this->createRoom(74, 'Bett im Mehrbettzimmer')]);
|
||||
|
||||
$this->apply($travel, $participant, ['assignedRoomId' => null, 'remarksRoom' => null]);
|
||||
|
||||
$this->assertNull($participant->remarksRoom);
|
||||
$this->assertSame(74, $participant->assignedRoomId);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $roomAssignment */
|
||||
private function apply(Travel $travel, ParticipantDto $participant, array $roomAssignment): void
|
||||
{
|
||||
$dto = new BookingDto($travel, 1);
|
||||
$dto->participants = [1 => $participant];
|
||||
|
||||
$this->merger->apply($dto, 1, $participant, ['roomAssignment' => $roomAssignment], $travel);
|
||||
}
|
||||
|
||||
/** @param array<int, Room> $rooms */
|
||||
private function createTravel(array $rooms): Travel
|
||||
{
|
||||
$travel = new Travel();
|
||||
$travel->rooms = $rooms;
|
||||
|
||||
return $travel;
|
||||
}
|
||||
|
||||
private function createRoom(int $id, string $label): Room
|
||||
{
|
||||
$room = new Room();
|
||||
$room->id = $id;
|
||||
$room->label = $label;
|
||||
|
||||
return $room;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user