diff --git a/src/Config/BookingHorizon.php b/src/Config/BookingHorizon.php index 4fcbbc2..efc07e6 100644 --- a/src/Config/BookingHorizon.php +++ b/src/Config/BookingHorizon.php @@ -13,7 +13,8 @@ namespace App\Config; * nine). It governs single bookings and series alike, which is why it lives here rather than on * BookingSeries. * - * ROLE_ADMIN is exempt -- see BookingRequest::$unlimitedHorizon. + * It binds everybody: the booking modal always books for the person using it, so there is no + * on-behalf case an administrator would need it lifted for. */ final class BookingHorizon { diff --git a/src/Controller/Booking/CreateController.php b/src/Controller/Booking/CreateController.php index 7f61fea..badab51 100644 --- a/src/Controller/Booking/CreateController.php +++ b/src/Controller/Booking/CreateController.php @@ -19,7 +19,6 @@ use App\Model\SeriesResult; use App\Model\Spot; use App\Repository\BookingRepository; use App\Routing\DateParam; -use App\Security\Role; use App\Service\BoardContextFactory; use App\Service\BookingManager; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -73,13 +72,10 @@ final class CreateController extends AbstractController $this->currentUser(), ); $data = new BookingRequest($selected, $spot->suggested() ?? $boardTime); - // An admin books on behalf of people and cleans up after the fact, so the three-month - // horizon that keeps desks from being blocked half a year ahead does not bind them. - $data->unlimitedHorizon = $this->isGranted(Role::ADMIN); $form = $this->createForm(BookingType::class, $data, [ 'action' => $this->generateUrl('app_booking_new', ['id' => $resource->getId(), 'date' => $date] + $this->timeParameters($boardTime)), - 'horizon' => $data->unlimitedHorizon ? null : BookingHorizon::latest(), + 'horizon' => BookingHorizon::latest(), ]); $form->handleRequest($request); diff --git a/src/Form/BookingRequest.php b/src/Form/BookingRequest.php index 93d5d35..e4de703 100644 --- a/src/Form/BookingRequest.php +++ b/src/Form/BookingRequest.php @@ -20,13 +20,6 @@ final class BookingRequest #[Assert\GreaterThanOrEqual('today', message: 'Für vergangene Tage kann nicht gebucht werden.')] public ?\DateTimeImmutable $date = null; - /** - * Lifts the BookingHorizon cap for this request. Set by the controller for ROLE_ADMIN -- a DTO - * cannot reach the security container, and giving it one would make these rules untestable - * in isolation. - */ - public bool $unlimitedHorizon = false; - public bool $allDay = true; public ?\DateTimeImmutable $startTime = null; @@ -110,7 +103,7 @@ final class BookingRequest return; } - if (!$this->unlimitedHorizon && $this->until > BookingHorizon::latest()) { + if ($this->until > BookingHorizon::latest()) { $context->buildViolation(\sprintf('Eine Wiederholung darf höchstens %d Monate in die Zukunft laufen.', BookingHorizon::MONTHS)) ->atPath('until') ->addViolation(); @@ -119,12 +112,12 @@ final class BookingRequest /** * The booking horizon, measured from today rather than from the chosen day -- see BookingHorizon. - * Not an attribute constraint because it has to be liftable per role, which an attribute - * cannot express. + * Not an attribute constraint because the cap moves with today, which an attribute cannot + * express. */ private function validateHorizon(ExecutionContextInterface $context): void { - if ($this->unlimitedHorizon || null === $this->date) { + if (null === $this->date) { return; } diff --git a/tests/Controller/BookingControllerTest.php b/tests/Controller/BookingControllerTest.php index 994f9bd..d442ed0 100644 --- a/tests/Controller/BookingControllerTest.php +++ b/tests/Controller/BookingControllerTest.php @@ -522,14 +522,21 @@ class BookingControllerTest extends WebTestCase self::assertStringContainsString('höchstens 3 Monate in die Zukunft', (string) $response->getContent()); } - public function testAnAdminIsNotBoundByTheHorizon(): void + /** + * The horizon used to be lifted for ROLE_ADMIN on the grounds that an admin books for other + * people. They do not -- this modal always books for whoever is using it, and the admin area + * is read-only -- so the exemption only let an admin block a desk for themselves half a year + * ahead. + */ + public function testAnAdminIsBoundByTheHorizonToo(): void { $this->client->loginUser($this->admin('chef@ep-reisen.de')); $beyond = BookingHorizon::latest()->modify('+2 months'); $response = $this->submitBookingModal($this->resource('D1'), $beyond->format('Y-m-d'), ['allDay' => '1']); - self::assertSame(Response::HTTP_OK, $response->getStatusCode()); + self::assertSame(Response::HTTP_UNPROCESSABLE_ENTITY, $response->getStatusCode()); + self::assertStringContainsString('höchstens 3 Monate im Voraus', (string) $response->getContent()); } /**