feat: group resources into rooms on the floor plan

This commit is contained in:
fromme committed 2026-09-14 08:25:05 +02:00
1 parent faef4d70a8
commit 8caf618950
14 files changed
+653 -34

No files matched your search

+104 -2
View File
@@ -7,6 +7,7 @@ namespace App\Tests\Controller;
use App\Booking\TimeRange;
use App\Entity\Booking;
use App\Entity\Resource;
use App\Entity\ResourceGroup;
use App\Entity\ResourceType;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
@@ -139,6 +140,107 @@ class FloorPlanTest extends WebTestCase
self::assertSame('Schreibtisch 01 — Bea K. 09:00–13:00, frei 13:00–24:00', $label);
}
public function testARoomIsDrawnWithItsNameAndHowManyAreFree(): void
{
$room = $this->room('Raum Nord', 'room-01');
$this->deskIn($room, 'desk-01', 'Schreibtisch 01');
$this->deskIn($room, 'desk-02', 'Schreibtisch 02');
$this->deskIn($room, 'desk-03', 'Schreibtisch 03');
self::assertSame(['Raum Nord · 3 frei'], $this->roomsOf($this->board()));
}
/**
* The badge counts what can still be had for the whole span, so a desk that is free only in
* the afternoon does not count -- it is already amber on the plan, and counting it would
* make the number disagree with the colour beside it.
*/
public function testAPartlyBookedDeskIsNotCountedAsFree(): void
{
$room = $this->room('Raum Nord', 'room-01');
$partly = $this->deskIn($room, 'desk-01', 'Schreibtisch 01');
$this->deskIn($room, 'desk-02', 'Schreibtisch 02');
$this->book($partly, $this->user('[email protected]', 'Bea Kranz'), 540, 780);
self::assertSame(['Raum Nord · 1 frei'], $this->roomsOf($this->board()));
}
/**
* The count means the same thing as the colours beside it, so it is relative to the board's
* span: the desk that is unavailable for the whole day is free for an afternoon board.
*/
public function testTheFreeCountFollowsTheSelectedSpan(): void
{
$room = $this->room('Raum Nord', 'room-01');
$desk = $this->deskIn($room, 'desk-01', 'Schreibtisch 01');
$this->book($desk, $this->user('[email protected]', 'Bea Kranz'), 540, 780);
self::assertSame(['Raum Nord · belegt'], $this->roomsOf($this->board()));
self::assertSame(['Raum Nord · 1 frei'], $this->roomsOf($this->board(query: '&from=14:00&to=18:00')));
}
public function testARoomWithNothingLeftSaysSoInsteadOfShowingAZero(): void
{
$room = $this->room('Raum Nord', 'room-01');
$desk = $this->deskIn($room, 'desk-01', 'Schreibtisch 01');
$this->book($desk, $this->user('[email protected]', 'Bea Kranz'), 0, 1440);
self::assertSame(['Raum Nord · belegt'], $this->roomsOf($this->board()));
}
public function testRoomsAreDrawnInTheirConfiguredOrder(): void
{
$this->deskIn($this->room('Zweiter Raum', 'room-02', 1), 'desk-04', 'Schreibtisch 04');
$this->deskIn($this->room('Erster Raum', 'room-01', 0), 'desk-01', 'Schreibtisch 01');
self::assertSame(['Erster Raum · 1 frei', 'Zweiter Raum · 1 frei'], $this->roomsOf($this->board()));
}
/**
* A resource with no group is still bookable and still drawn -- the plan simply does not
* frame it. That is the path the car park runs on.
*/
public function testAnUngroupedDeskIsDrawnWithoutARoom(): void
{
$this->desk('desk-01');
$crawler = $this->board();
self::assertSame([], $this->roomsOf($crawler));
self::assertSame(['Schreibtisch 01'], $this->linesOf($crawler));
}
/**
* The room captions, which sit directly under the <svg> rather than inside a shape's <g>.
*
* @return list<string>
*/
private function roomsOf(\Symfony\Component\DomCrawler\Crawler $crawler): array
{
return $crawler->filter('svg > text')->each(
static fn (\Symfony\Component\DomCrawler\Crawler $node): string => trim(preg_replace('/\s+/', ' ', $node->text()) ?? ''),
);
}
private function room(string $label, string $svgElementId, int $position = 0): ResourceGroup
{
$group = new ResourceGroup(ResourceType::DESK, $label, $position, $svgElementId);
$this->em->persist($group);
$this->em->flush();
return $group;
}
private function deskIn(ResourceGroup $group, string $svgElementId, string $label): Resource
{
$resource = new Resource(ResourceType::DESK, $label, $svgElementId);
$resource->setGroup($group);
$this->em->persist($resource);
$this->em->flush();
return $resource;
}
/**
* @return list<string>
*/
@@ -149,11 +251,11 @@ class FloorPlanTest extends WebTestCase
);
}
private function board(?User $as = null): \Symfony\Component\DomCrawler\Crawler
private function board(?User $as = null, string $query = ''): \Symfony\Component\DomCrawler\Crawler
{
$this->client->loginUser($as ?? $this->user('[email protected]', 'Anna Berger'));
return $this->client->request('GET', '/book/desk?date=2026-10-01');
return $this->client->request('GET', '/book/desk?date=2026-10-01'.$query);
}
private function desk(string $svgElementId): Resource