diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index 9f348882..81b95791 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -659,12 +659,14 @@ class Agenda(models.Model): # reference is now, in local timezone t = localtime(now()) - # add delay - t += datetime.timedelta(days=self.maximal_booking_delay) + maximal_booking_delay = self.maximal_booking_delay + if self.minimal_booking_time is None or t.time() < self.minimal_booking_time: + maximal_booking_delay -= 1 + t += datetime.timedelta(days=maximal_booking_delay) - # replace time if needed if self.minimal_booking_time: - t = datetime.datetime.combine(t.date(), self.minimal_booking_time, tzinfo=t.tzinfo) + t = t.replace(hour=0, minute=0, second=0, microsecond=0) + # t could not exist, recompute it as an existing datetime by converting to UTC then to localtime return localtime(t.astimezone(utc)) diff --git a/tests/api/datetimes/test_all.py b/tests/api/datetimes/test_all.py index 313af7fe..44edd84d 100644 --- a/tests/api/datetimes/test_all.py +++ b/tests/api/datetimes/test_all.py @@ -1399,3 +1399,159 @@ def test_events_datetimes_min_booking_datetime_with_minimal_booking_time(app): resp = app.get(api_url) assert resp.json['data'][0]['datetime'] == '2023-04-03 09:00:00' assert resp.json['data'][1]['datetime'] == '2023-04-03 11:00:00' + + +def test_events_datetimes_max_booking_datetime_with_minimal_booking_time(app, freezer): + agenda = Agenda.objects.create( + label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=3 + ) + Event.objects.create( + slug='event-slug-0', + start_datetime=make_aware(datetime.datetime(year=2023, month=4, day=4, hour=9)), + places=5, + agenda=agenda, + ) + Event.objects.create( + slug='event-slug-1', + start_datetime=make_aware(datetime.datetime(year=2023, month=4, day=4, hour=11)), + places=5, + agenda=agenda, + ) + Event.objects.create( + slug='event-slug-2', + start_datetime=make_aware(datetime.datetime(year=2023, month=4, day=5, hour=9)), + places=5, + agenda=agenda, + ) + Event.objects.create( + slug='event-slug-3', + start_datetime=make_aware(datetime.datetime(year=2023, month=4, day=5, hour=11)), + places=5, + agenda=agenda, + ) + Event.objects.create( + slug='event-slug-4', + start_datetime=make_aware(datetime.datetime(year=2023, month=4, day=6, hour=9)), + places=5, + agenda=agenda, + ) + Event.objects.create( + slug='event-slug-5', + start_datetime=make_aware(datetime.datetime(year=2023, month=4, day=6, hour=11)), + places=5, + agenda=agenda, + ) + + # last slots visible are the one on J + maximal_booking_delay (3) -1 + freezer.move_to('2023-04-03T00:00:00+02:00') + api_url = '/api/agenda/%s/datetimes/' % agenda.slug + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 11:00:00' + + # # move to noon, no changes + freezer.move_to('2023-04-03T12:00:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 11:00:00' + + # set a minimal minimal_booking_time earlier than current time, no changes + agenda.minimal_booking_time = datetime.time(10, 0, 0) + agenda.save() + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 11:00:00' + + # set a minimal minimal_booking_time later than current time, slots of 2023-04-05 disappear + agenda.minimal_booking_time = datetime.time(14, 0, 0) + agenda.save() + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-04 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-04 11:00:00' + + # move to a time superior to minimal_booking_time (14:00), slots of 2023-04-05 re-appear + freezer.move_to('2023-04-03T15:00:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 11:00:00' + + # move to the day after, prior to minimal_booking_time (14:00), no changes + freezer.move_to('2023-04-04T12:00:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 11:00:00' + + # move to the day after, after minimal_booking_time (14:00), new slots available + freezer.move_to('2023-04-04T15:00:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-06 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-06 11:00:00' + + +def test_events_datetimes_max_booking_datetime_with_minimal_booking_time_to_none(app, freezer): + agenda = Agenda.objects.create( + label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=3 + ) + Event.objects.create( + slug='event-slug-0', + start_datetime=make_aware(datetime.datetime(year=2023, month=4, day=4, hour=9)), + places=5, + agenda=agenda, + ) + Event.objects.create( + slug='event-slug-1', + start_datetime=make_aware(datetime.datetime(year=2023, month=4, day=4, hour=11)), + places=5, + agenda=agenda, + ) + Event.objects.create( + slug='event-slug-2', + start_datetime=make_aware(datetime.datetime(year=2023, month=4, day=5, hour=9)), + places=5, + agenda=agenda, + ) + Event.objects.create( + slug='event-slug-3', + start_datetime=make_aware(datetime.datetime(year=2023, month=4, day=5, hour=11)), + places=5, + agenda=agenda, + ) + Event.objects.create( + slug='event-slug-4', + start_datetime=make_aware(datetime.datetime(year=2023, month=4, day=6, hour=9)), + places=5, + agenda=agenda, + ) + Event.objects.create( + slug='event-slug-5', + start_datetime=make_aware(datetime.datetime(year=2023, month=4, day=6, hour=11)), + places=5, + agenda=agenda, + ) + + # last slots visible are the one on J + maximal_booking_delay (3) -1 + freezer.move_to('2023-04-03T00:00:00+02:00') + api_url = '/api/agenda/%s/datetimes/' % agenda.slug + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 11:00:00' + + # set a minimal minimal_booking_time to None, 2023-04-05 disappear + # because current time is 00:00 and slots starts later + agenda.minimal_booking_time = None + agenda.save() + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-04 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-04 11:00:00' + + # move a few hours later, juste after the first slot time of a day + # a new slot becomes available + freezer.move_to('2023-04-03T09:01:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-1]['datetime'] == '2023-04-05 09:00:00' + + # at 12:00, every slots are available + freezer.move_to('2023-04-03T12:00:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 11:00:00' diff --git a/tests/api/datetimes/test_meetings.py b/tests/api/datetimes/test_meetings.py index 7d1db0a5..ccf3aa69 100644 --- a/tests/api/datetimes/test_meetings.py +++ b/tests/api/datetimes/test_meetings.py @@ -2692,3 +2692,118 @@ def test_datetimes_api_meetings_min_booking_datetime_with_minimal_booking_time(a agenda.save() resp = app.get(api_url) assert resp.json['data'][0]['datetime'] == '2023-04-03 09:00:00' + + +def test_datetimes_api_meetings_max_booking_datetime_with_minimal_booking_time(app, freezer): + agenda = Agenda.objects.create( + label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=3 + ) + desk = Desk.objects.create(agenda=agenda, slug='desk') + meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo', duration=30) + for weekday in [0, 1, 2, 3, 4, 5]: + TimePeriod.objects.create( + weekday=weekday, + start_time=datetime.time(9, 0), + end_time=datetime.time(10, 00), + desk=desk, + ) + + # last slots visible are the one on J + maximal_booking_delay (3) -1 + freezer.move_to('2023-04-03T00:00:00+02:00') # 2023-04-03 is a monday + api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug) + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 09:30:00' + + # move to noon, no changes + freezer.move_to('2023-04-03T12:00:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 09:30:00' + + # set a minimal minimal_booking_time earlier than current time, no changes + agenda.minimal_booking_time = datetime.time(10, 0, 0) + agenda.save() + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 09:30:00' + + # set a minimal minimal_booking_time later than current time, slots of 2023-04-05 disappear + agenda.minimal_booking_time = datetime.time(14, 0, 0) + agenda.save() + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-04 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-04 09:30:00' + + # move to a time superior to minimal_booking_time (14:00), slots of 2023-04-05 re-appear + freezer.move_to('2023-04-03T15:00:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 09:30:00' + + # move to the day after, prior to minimal_booking_time (14:00), no changes + freezer.move_to('2023-04-04T12:00:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 09:30:00' + + # move to the day after, after minimal_booking_time (14:00), new slots available + freezer.move_to('2023-04-04T15:00:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-06 09:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-06 09:30:00' + + +def test_datetimes_api_meetings_max_booking_datetime_with_minimal_booking_time_to_none(app, freezer): + agenda = Agenda.objects.create( + label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=3 + ) + desk = Desk.objects.create(agenda=agenda, slug='desk') + meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo', duration=30) + for weekday in [0, 1, 2, 3, 4, 5]: + TimePeriod.objects.create( + weekday=weekday, + start_time=datetime.time(11, 00), + end_time=datetime.time(14, 00), + desk=desk, + ) + + # last slots visible are the one on J + maximal_booking_delay (3) -1 + freezer.move_to('2023-04-03T00:00:00+02:00') # 2023-04-03 is a monday + api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug) + resp = app.get(api_url) + assert resp.json['data'][-6]['datetime'] == '2023-04-05 11:00:00' + assert resp.json['data'][-5]['datetime'] == '2023-04-05 11:30:00' + assert resp.json['data'][-4]['datetime'] == '2023-04-05 12:00:00' + assert resp.json['data'][-3]['datetime'] == '2023-04-05 12:30:00' + assert resp.json['data'][-2]['datetime'] == '2023-04-05 13:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 13:30:00' + + # set a minimal minimal_booking_time to None, 2023-04-05 disappear + # because current time is 00:00 and slots starts later + agenda.minimal_booking_time = None + agenda.save() + resp = app.get(api_url) + assert resp.json['data'][-1]['datetime'] == '2023-04-04 13:30:00' + + # move a few hours later, juste after the first slot time of a day + # a new slot becomes available + freezer.move_to('2023-04-03T11:01:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-1]['datetime'] == '2023-04-05 11:00:00' + + # move juste after the second slot time, one more slot availalbe + freezer.move_to('2023-04-03T11:31:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-2]['datetime'] == '2023-04-05 11:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 11:30:00' + + # at 15:00, every slots are available + freezer.move_to('2023-04-03T15:00:00+02:00') + resp = app.get(api_url) + assert resp.json['data'][-6]['datetime'] == '2023-04-05 11:00:00' + assert resp.json['data'][-5]['datetime'] == '2023-04-05 11:30:00' + assert resp.json['data'][-4]['datetime'] == '2023-04-05 12:00:00' + assert resp.json['data'][-3]['datetime'] == '2023-04-05 12:30:00' + assert resp.json['data'][-2]['datetime'] == '2023-04-05 13:00:00' + assert resp.json['data'][-1]['datetime'] == '2023-04-05 13:30:00' diff --git a/tests/test_agendas.py b/tests/test_agendas.py index 6136d288..7fdaaa9b 100644 --- a/tests/test_agendas.py +++ b/tests/test_agendas.py @@ -368,22 +368,22 @@ def test_agenda_minimal_booking_delay_minimal_booking_time_at_8(freezer, current @pytest.mark.parametrize( 'current_time,max_booking_datetime', [ - ('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 13, 8)), - ('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 22, 7)), + ('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 12, 8)), + ('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 21, 7)), # summer DST change on sunday 28th - ('2021-03-25T01:30:00+01:00', datetime.datetime(2021, 3, 29, 1, 30)), - ('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 29, 2, 30)), - ('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 29, 3, 30)), - ('2021-03-28T01:30:00+01:00', datetime.datetime(2021, 4, 1, 1, 30)), - ('2021-03-28T03:30:00+02:00', datetime.datetime(2021, 4, 1, 3, 30)), + ('2021-03-25T01:30:00+01:00', datetime.datetime(2021, 3, 28, 1, 30)), + ('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 28, 3, 30)), + ('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 28, 3, 30)), + ('2021-03-28T01:30:00+01:00', datetime.datetime(2021, 3, 31, 1, 30)), + ('2021-03-28T03:30:00+02:00', datetime.datetime(2021, 3, 31, 3, 30)), # winter DST change on sunday 31th - ('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 2, 1, 30)), - ('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 2, 30)), - ('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 2, 30)), - ('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 4, 1, 30)), - ('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 4, 2, 30)), - ('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 4, 2, 30)), - ('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 4, 3, 30)), + ('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 1, 1, 30)), + ('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 1, 2, 30)), + ('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 1, 2, 30)), + ('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 3, 1, 30)), + ('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 3, 2, 30)), + ('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 3, 2, 30)), + ('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 3, 3, 30)), ], ids=delay_parameter_to_label, ) @@ -396,19 +396,19 @@ def test_agenda_maximal_booking_delay_no_minimal_booking_time(freezer, current_t @pytest.mark.parametrize( 'current_time,max_booking_datetime', [ - ('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 13, 8)), - ('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 22, 8)), + ('2021-07-09T08:00:00+02:00', datetime.datetime(2021, 7, 13, 0)), + ('2021-03-18T07:00:00+01:00', datetime.datetime(2021, 3, 21, 0)), # summer DST change on sunday - ('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 29, 8)), - ('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 29, 8)), + ('2021-03-25T02:30:00+01:00', datetime.datetime(2021, 3, 28, 0)), + ('2021-03-25T03:30:00+01:00', datetime.datetime(2021, 3, 28, 0)), # winter DST change on sunday - ('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 2, 8)), - ('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 8)), - ('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 2, 8)), - ('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 4, 8)), - ('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 4, 8)), - ('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 4, 8)), - ('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 4, 8)), + ('2021-10-29T01:30:00+02:00', datetime.datetime(2021, 11, 1, 0)), + ('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 1, 0)), + ('2021-10-29T02:30:00+02:00', datetime.datetime(2021, 11, 1, 0)), + ('2021-10-31T01:30:00+02:00', datetime.datetime(2021, 11, 3, 0)), + ('2021-10-31T02:30:00+02:00', datetime.datetime(2021, 11, 3, 0)), + ('2021-10-31T02:30:00+01:00', datetime.datetime(2021, 11, 3, 0)), + ('2021-10-31T03:30:00+01:00', datetime.datetime(2021, 11, 3, 0)), ], ids=delay_parameter_to_label, )