#19 - Merge timetable reservations

This commit was merged in pull request #21.
This commit is contained in:
Martin Slachta
2026-06-16 10:54:00 +02:00
parent 1c2a176d97
commit cfbdca238c
@@ -27,6 +27,9 @@ class RsvReservationService {
} }
public function create(RsvReservation $reservation) { public function create(RsvReservation $reservation) {
$reservation->timetable_reservations =
$this->merge_adjacent($reservation->timetable_reservations);
// Serialise the availability-check + insert per timetable so two // Serialise the availability-check + insert per timetable so two
// concurrent bookings for the last free slot can't both pass the // concurrent bookings for the last free slot can't both pass the
// capacity check and oversell. Locks are taken in a stable order to // capacity check and oversell. Locks are taken in a stable order to
@@ -80,6 +83,48 @@ class RsvReservationService {
return Db::prefix() . 'rsv_booking_' . $timetable_id; return Db::prefix() . 'rsv_booking_' . $timetable_id;
} }
/**
* Collapse runs of touching reservations into single spans, so a sequence
* of back-to-back blocks is stored and notified as one booking.
*
* @param list<RsvTimetableReservation> $timetable_reservations
* @return list<RsvTimetableReservation>
*/
private function merge_adjacent(array $timetable_reservations): array {
$by_timetable = [];
foreach ($timetable_reservations as $tr) {
$by_timetable[$tr->timetable_id][] = $tr;
}
$merged = [];
foreach ($by_timetable as $group) {
usort($group, fn($a, $b) => $a->start_utc <=> $b->start_utc);
$current = null;
foreach ($group as $tr) {
if ($current !== null && $tr->start_utc <= $current->end_utc) {
if ($tr->end_utc > $current->end_utc) {
$current->end_utc = $tr->end_utc;
}
continue;
}
if ($current !== null) {
$merged[] = $current;
}
$current = new RsvTimetableReservation(
null, $tr->timetable_id, $tr->start_utc, $tr->end_utc
);
}
if ($current !== null) {
$merged[] = $current;
}
}
return $merged;
}
/** Delete a reservation and its dependent timetable reservations and confirmations. */ /** Delete a reservation and its dependent timetable reservations and confirmations. */
public function delete(int $id): void { public function delete(int $id): void {
$this->repo->delete($id); $this->repo->delete($id);