feat: warn about overlapping bookings instead of refusing them
This commit is contained in:
1 parent
fae28b1fd7
commit
5a6db29205
8 files changed
+359
-71
No files matched your search
@@ -8,7 +8,6 @@ use App\Entity\Booking;
|
||||
use App\Entity\BookingSeries;
|
||||
use App\Entity\Resource;
|
||||
use App\Entity\User;
|
||||
use App\Exception\AlreadyHoldingResourceException;
|
||||
use App\Exception\ResourceAlreadyBookedException;
|
||||
use App\Model\SeriesResult;
|
||||
use App\Model\TimeRange;
|
||||
@@ -32,9 +31,12 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||
* protection now lives in code, so it belongs to this class alone: nothing else may persist a
|
||||
* Booking.
|
||||
*
|
||||
* The per-user rule ("not two desks at once") is deliberately NOT covered by the lock: it spans
|
||||
* resources, and locking every resource would serialise the whole app to protect against a user
|
||||
* racing themselves in two browser tabs.
|
||||
* **What is NOT guarded here.** One person holding two desks at the same time used to be refused
|
||||
* from this class. It is allowed now: booking a second desk for a customer you are meeting is a
|
||||
* real thing people do, and the refusal only ever landed after the modal was filled in, since
|
||||
* the floor plan offers every free desk regardless of what you already hold. Overlaps of that
|
||||
* kind are now reported as a warning flash once the booking exists -- see OverlapWarningTrait.
|
||||
* The only conflict left here is the physical one: two people, or two spans, on one resource.
|
||||
*/
|
||||
final readonly class BookingManager
|
||||
{
|
||||
@@ -45,8 +47,7 @@ final readonly class BookingManager
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AlreadyHoldingResourceException if the user already holds a resource of this kind in that span
|
||||
* @throws ResourceAlreadyBookedException if somebody else does
|
||||
* @throws ResourceAlreadyBookedException if the resource is not free for that span
|
||||
*/
|
||||
public function book(User $user, Resource $resource, \DateTimeImmutable $date, ?TimeRange $time = null): Booking
|
||||
{
|
||||
@@ -56,7 +57,7 @@ final readonly class BookingManager
|
||||
return $this->em->wrapInTransaction(function () use ($user, $resource, $date, $time): Booking {
|
||||
$this->em->lock($resource, LockMode::PESSIMISTIC_WRITE);
|
||||
|
||||
$this->assertFree($user, $resource, $date, $time);
|
||||
$this->assertFree($resource, $date, $time);
|
||||
|
||||
$booking = new Booking($user, $resource, $date, $time);
|
||||
$this->em->persist($booking);
|
||||
@@ -87,8 +88,8 @@ final readonly class BookingManager
|
||||
|
||||
foreach ($series->occurrences() as $date) {
|
||||
try {
|
||||
$this->assertFree($user, $resource, $date, $time);
|
||||
} catch (AlreadyHoldingResourceException|ResourceAlreadyBookedException $e) {
|
||||
$this->assertFree($resource, $date, $time);
|
||||
} catch (ResourceAlreadyBookedException $e) {
|
||||
$skipped[$date->format('Y-m-d')] = $e->getMessage();
|
||||
|
||||
continue;
|
||||
@@ -118,7 +119,7 @@ final readonly class BookingManager
|
||||
$resource = $booking->getResource();
|
||||
$this->em->lock($resource, LockMode::PESSIMISTIC_WRITE);
|
||||
|
||||
$this->assertFree($booking->getUser(), $resource, $booking->getDate(), $time, $booking);
|
||||
$this->assertFree($resource, $booking->getDate(), $time, $booking);
|
||||
|
||||
$booking->changeTime($time);
|
||||
$this->em->flush();
|
||||
@@ -161,21 +162,14 @@ final readonly class BookingManager
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AlreadyHoldingResourceException
|
||||
* @throws ResourceAlreadyBookedException
|
||||
*/
|
||||
private function assertFree(
|
||||
User $user,
|
||||
Resource $resource,
|
||||
\DateTimeImmutable $date,
|
||||
TimeRange $time,
|
||||
?Booking $ignore = null,
|
||||
): void {
|
||||
$mine = $this->bookings->findOverlappingForUser($user, $date, $time, $resource->getType(), $ignore);
|
||||
if (null !== $mine) {
|
||||
throw new AlreadyHoldingResourceException($mine);
|
||||
}
|
||||
|
||||
foreach ($this->bookings->findOverlapping($resource, $date, $time) as $clash) {
|
||||
if (null !== $ignore && $clash->getId() === $ignore->getId()) {
|
||||
continue;
|
||||
|
||||
Reference in new issue
Block a user