agendas: methods to compute start and end times from check times (#80842)

This commit is contained in:
Lauréline Guérin 2023-09-05 12:15:35 +02:00
parent df0223abf2
commit 16e3602391
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
2 changed files with 171 additions and 0 deletions

View File

@ -2903,6 +2903,63 @@ class Booking(models.Model):
def get_backoffice_url(self):
return translate_from_publik_url(self.backoffice_url)
def _get_previous_and_next_slots(self, _time):
minutes = {
'hour': 60,
'half_hour': 30,
'quarter': 15,
}[self.event.agenda.invoicing_unit]
time_minutes = _time.hour * 60 + _time.minute
previous_slot_minutes = math.trunc(time_minutes / minutes) * minutes
previous_slot = datetime.time(*divmod(previous_slot_minutes, 60))
next_slot = datetime.time(*divmod(previous_slot_minutes + minutes, 60))
return previous_slot, next_slot
def get_computed_start_time(self):
if self.user_check_start_time is None:
return None
start_time = self.user_check_start_time
if self.start_time:
start_time = min(start_time, self.start_time)
if self.event.agenda.invoicing_unit == 'minute':
return start_time
tolerance = self.event.agenda.invoicing_tolerance
# compute previous and next slot
previous_slot, next_slot = self._get_previous_and_next_slots(start_time)
# in tolerance ? take next_slot
if (next_slot.minute or 60 - start_time.minute) <= tolerance:
return next_slot
# else take previous_slot
return previous_slot
def get_computed_end_time(self):
if self.user_check_end_time is None:
return None
end_time = self.user_check_end_time
if self.end_time:
end_time = max(end_time, self.end_time)
if self.event.agenda.invoicing_unit == 'minute':
return end_time
tolerance = self.event.agenda.invoicing_tolerance
# compute previous and next slot
previous_slot, next_slot = self._get_previous_and_next_slots(end_time)
# in tolerance ? take previous_slot
if (end_time.minute - previous_slot.minute) <= tolerance:
return previous_slot
# else take next_slot
return next_slot
OpeningHour = collections.namedtuple('OpeningHour', ['begin', 'end'])

View File

@ -3917,3 +3917,117 @@ def test_agenda_event_overlaps_recurring():
# normal event, after not on a recurrence day
event_kwargs['start_datetime'] = make_aware(datetime.datetime(2023, 5, 10, 14, 00))
assert agenda.event_overlaps(**event_kwargs) is False
@pytest.mark.parametrize(
'start_time, user_check_start_time, tolerance, unit, expected',
[
# no check
(None, None, 0, 'hour', None),
(None, None, 0, 'half_hour', None),
(None, None, 0, 'quarter', None),
(None, None, 0, 'minutes', None),
# hour unit - no booking
(None, datetime.time(7, 50), 10, 'hour', datetime.time(8, 0)),
(None, datetime.time(7, 49), 10, 'hour', datetime.time(7, 0)),
(None, datetime.time(7, 50), 0, 'hour', datetime.time(7, 0)),
# hour unit - with booking
(datetime.time(8, 0), datetime.time(7, 50), 10, 'hour', datetime.time(8, 0)),
(datetime.time(8, 0), datetime.time(7, 49), 10, 'hour', datetime.time(7, 0)),
(datetime.time(8, 0), datetime.time(8, 30), 10, 'hour', datetime.time(8, 0)),
(datetime.time(8, 0), datetime.time(7, 50), 0, 'hour', datetime.time(7, 0)),
# half_hour unit - no booking
(None, datetime.time(7, 50), 10, 'half_hour', datetime.time(8, 0)),
(None, datetime.time(7, 49), 10, 'half_hour', datetime.time(7, 30)),
(None, datetime.time(7, 50), 0, 'half_hour', datetime.time(7, 30)),
# half_hour unit - with booking
(datetime.time(8, 0), datetime.time(7, 50), 10, 'half_hour', datetime.time(8, 0)),
(datetime.time(8, 0), datetime.time(7, 49), 10, 'half_hour', datetime.time(7, 30)),
(datetime.time(8, 0), datetime.time(8, 30), 10, 'half_hour', datetime.time(8, 0)),
(datetime.time(8, 0), datetime.time(7, 50), 0, 'half_hour', datetime.time(7, 30)),
# quarter unit - no booking
(None, datetime.time(7, 50), 10, 'quarter', datetime.time(8, 0)),
(None, datetime.time(7, 49), 10, 'quarter', datetime.time(7, 45)),
(None, datetime.time(7, 50), 0, 'quarter', datetime.time(7, 45)),
# quarter unit - with booking
(datetime.time(8, 0), datetime.time(7, 50), 10, 'quarter', datetime.time(8, 0)),
(datetime.time(8, 0), datetime.time(7, 49), 10, 'quarter', datetime.time(7, 45)),
(datetime.time(8, 0), datetime.time(8, 30), 10, 'quarter', datetime.time(8, 0)),
(datetime.time(8, 0), datetime.time(7, 50), 0, 'quarter', datetime.time(7, 45)),
# minute unit - no booking
(None, datetime.time(7, 50), 0, 'minute', datetime.time(7, 50)),
# minute unit - with booking
(datetime.time(8, 0), datetime.time(7, 50), 0, 'minute', datetime.time(7, 50)),
(datetime.time(8, 0), datetime.time(8, 5), 0, 'minute', datetime.time(8, 0)),
],
)
def test_booking_get_computed_start_time(start_time, user_check_start_time, tolerance, unit, expected):
agenda = Agenda.objects.create(
label='Agenda', kind='events', invoicing_unit=unit, invoicing_tolerance=tolerance
)
event = Event.objects.create(
agenda=agenda,
start_datetime=make_aware(datetime.datetime(2023, 5, 1, 14, 00)),
places=10,
)
booking = Booking.objects.create(
event=event, start_time=start_time, user_check_start_time=user_check_start_time
)
assert booking.get_computed_start_time() == expected
@pytest.mark.parametrize(
'end_time, user_check_end_time, tolerance, unit, expected',
[
# no check
(None, None, 0, 'hour', None),
(None, None, 0, 'half_hour', None),
(None, None, 0, 'quarter', None),
(None, None, 0, 'minutes', None),
# hour unit - no booking
(None, datetime.time(17, 10), 10, 'hour', datetime.time(17, 0)),
(None, datetime.time(17, 11), 10, 'hour', datetime.time(18, 0)),
(None, datetime.time(17, 10), 0, 'hour', datetime.time(18, 0)),
# hour unit - with booking
(datetime.time(17, 0), datetime.time(17, 10), 10, 'hour', datetime.time(17, 0)),
(datetime.time(17, 0), datetime.time(17, 11), 10, 'hour', datetime.time(18, 0)),
(datetime.time(17, 0), datetime.time(16, 30), 10, 'hour', datetime.time(17, 0)),
(datetime.time(17, 0), datetime.time(17, 10), 0, 'hour', datetime.time(18, 0)),
# half_hour unit - no booking
(None, datetime.time(17, 10), 10, 'half_hour', datetime.time(17, 0)),
(None, datetime.time(17, 11), 10, 'half_hour', datetime.time(17, 30)),
(None, datetime.time(17, 10), 0, 'half_hour', datetime.time(17, 30)),
# half_hour unit - with booking
(datetime.time(17, 0), datetime.time(17, 10), 10, 'half_hour', datetime.time(17, 0)),
(datetime.time(17, 0), datetime.time(17, 11), 10, 'half_hour', datetime.time(17, 30)),
(datetime.time(17, 0), datetime.time(16, 30), 10, 'half_hour', datetime.time(17, 0)),
(datetime.time(17, 0), datetime.time(17, 10), 0, 'half_hour', datetime.time(17, 30)),
# quarter unit - no booking
(None, datetime.time(17, 10), 10, 'quarter', datetime.time(17, 0)),
(None, datetime.time(17, 11), 10, 'quarter', datetime.time(17, 15)),
(None, datetime.time(17, 10), 0, 'quarter', datetime.time(17, 15)),
# quarter unit - with booking
(datetime.time(17, 0), datetime.time(17, 10), 10, 'quarter', datetime.time(17, 0)),
(datetime.time(17, 0), datetime.time(17, 11), 10, 'quarter', datetime.time(17, 15)),
(datetime.time(17, 0), datetime.time(16, 30), 10, 'quarter', datetime.time(17, 0)),
(datetime.time(17, 0), datetime.time(17, 10), 0, 'quarter', datetime.time(17, 15)),
# minute unit - no booking
(None, datetime.time(17, 10), 0, 'minute', datetime.time(17, 10)),
# minute unit - with booking
(datetime.time(17, 0), datetime.time(17, 10), 0, 'minute', datetime.time(17, 10)),
(datetime.time(17, 0), datetime.time(16, 50), 0, 'minute', datetime.time(17, 0)),
],
)
def test_booking_get_computed_end_time(end_time, user_check_end_time, tolerance, unit, expected):
agenda = Agenda.objects.create(
label='Agenda', kind='events', invoicing_unit=unit, invoicing_tolerance=tolerance
)
event = Event.objects.create(
agenda=agenda,
start_datetime=make_aware(datetime.datetime(2023, 5, 1, 14, 00)),
places=10,
)
booking = Booking.objects.create(event=event, end_time=end_time, user_check_end_time=user_check_end_time)
assert booking.get_computed_end_time() == expected