Skip to content

Commit

Permalink
Don't schedule new reservation on the same day if previous ongoing
Browse files Browse the repository at this point in the history
  • Loading branch information
matti-lamppu committed Nov 27, 2024
1 parent e2088a0 commit 04c3086
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -699,3 +699,59 @@ def test_recurring_reservations__reschedule_series__create_statistics__partial(g
# But those statistics should be for different reservations.
after = list(ReservationStatistic.objects.order_by("reservation").values_list("reservation", flat=True))
assert before != after


@freeze_time(local_datetime(year=2023, month=12, day=4, hour=10, minute=30)) # Monday
def test_recurring_reservations__reschedule_series__same_day_ongoing_reservation(graphql):
recurring_reservation = create_reservation_series()

data = get_minimal_reschedule_data(recurring_reservation, beginTime="11:00:00")

graphql.login_with_superuser()
response = graphql(RESCHEDULE_SERIES_MUTATION, input_data=data)

assert response.has_errors is False

# Series entries start at 11:00 instead of 10:00.
recurring_reservation.refresh_from_db()
assert recurring_reservation.begin_time == local_time(hour=11)

reservations = list(recurring_reservation.reservations.order_by("begin").all())
assert len(reservations) == 9
assert reservations[0].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=10)
assert reservations[1].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[2].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[3].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[4].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[5].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[6].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[7].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[8].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)


@freeze_time(local_datetime(year=2023, month=12, day=4, hour=8)) # Monday
def test_recurring_reservations__reschedule_series__same_day_future_reservation(graphql):
recurring_reservation = create_reservation_series()

data = get_minimal_reschedule_data(recurring_reservation, beginTime="11:00:00")

graphql.login_with_superuser()
response = graphql(RESCHEDULE_SERIES_MUTATION, input_data=data)

assert response.has_errors is False

# Series entries start at 11:00 instead of 10:00.
recurring_reservation.refresh_from_db()
assert recurring_reservation.begin_time == local_time(hour=11)

reservations = list(recurring_reservation.reservations.order_by("begin").all())
assert len(reservations) == 9
assert reservations[0].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[1].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[2].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[3].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[4].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[5].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[6].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[7].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
assert reservations[8].begin.astimezone(DEFAULT_TIMEZONE).timetz() == local_time(hour=11)
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ def validate(self, data: dict[str, Any]) -> dict[str, Any]:
return data

def save(self, **kwargs: Any) -> RecurringReservation:
skip_dates: list[datetime.date] = self.initial_data.get("skip_dates", [])
skip_dates: set[datetime.date] = set(self.initial_data.get("skip_dates", []))
buffer_time_before: datetime.timedelta | None = self.initial_data.get("buffer_time_before")
buffer_time_after: datetime.timedelta | None = self.initial_data.get("buffer_time_after")

Expand Down Expand Up @@ -472,35 +472,40 @@ def recreate_reservations(
instance: RecurringReservation,
buffer_time_before: datetime.timedelta | None,
buffer_time_after: datetime.timedelta | None,
skip_dates: list[datetime.date],
skip_dates: set[datetime.date],
) -> list[Reservation]:
now = local_datetime()
today = now.date()

# New reservations can overlap with existing reservations in this series
# New reservations can overlap with existing reservations in this series,
# since the existing ones will be deleted.
old_reservation_ids: list[int] = list(instance.reservations.values_list("pk", flat=True))

# Skip generating reservations for any dates where there is currently a non-confirmed reservation.
# It's unlikely that the reserver will want or can have the same date even if the time is changed.
# Any exceptions can be handled after the fact.
skip_dates += list(
instance.reservations.exclude(
state=ReservationStateChoice.CONFIRMED,
).values_list("begin__date", flat=True)
)
skip_dates |= set(instance.reservations.unconfirmed().values_list("begin__date", flat=True))

reservation_details = self.get_reservation_details(instance)
reservation_details["buffer_time_before"] = buffer_time_before or datetime.timedelta()
reservation_details["buffer_time_after"] = buffer_time_after or datetime.timedelta()

# Only recreate reservations from this moment onwards
if instance.begin_date < today:
skip_dates += [
# If the series has already started:
if instance.begin_date <= today:
# Only create reservation to the future.
skip_dates |= {
instance.begin_date + datetime.timedelta(days=delta) #
for delta in range((today - instance.begin_date).days)
]
if combine(today, instance.begin_time, tzinfo=DEFAULT_TIMEZONE) < now:
skip_dates.append(today)
}

# If new reservation would already have started, don't create it.
if combine(today, instance.begin_time, tzinfo=DEFAULT_TIMEZONE) <= now:
skip_dates.add(today)

# If series already has a reservation that is ongoing or in the past, don't create new one for today.
todays_reservation: Reservation | None = instance.reservations.filter(begin__date=today).first()
if todays_reservation is not None and todays_reservation.begin.astimezone(DEFAULT_TIMEZONE) <= now:
skip_dates.add(today)

slots = instance.actions.pre_calculate_slots(
check_buffers=True,
Expand Down
3 changes: 3 additions & 0 deletions tilavarauspalvelu/models/reservation/queryset.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ def future(self) -> Self:
"""Filter reservations have yet not begun."""
return self.going_to_occur().filter(begin__gt=local_datetime())

def unconfirmed(self) -> Self:
return self.exclude(state=ReservationStateChoice.CONFIRMED)

def inactive(self, older_than_minutes: int) -> Self:
"""Filter 'draft' reservations, which are older than X minutes old, and can be assumed to be inactive."""
return self.filter(
Expand Down

0 comments on commit 04c3086

Please sign in to comment.