From 3a4c5be8ef1f7c20ebf44a0bc28147090204d3b8 Mon Sep 17 00:00:00 2001
From: Emmanuel Cazenave <ecazenave@entrouvert.com>
Date: Tue, 25 Feb 2020 10:30:47 +0100
Subject: [PATCH 2/2] virtual agendas: use real agendas booking delays (#40121)

If booking delays are defined on the virtual agenda, they will take
precedence.
---
 chrono/api/views.py | 36 ++++++++++++++++++++++++-----
 tests/test_api.py   | 55 +++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 83 insertions(+), 8 deletions(-)

diff --git a/chrono/api/views.py b/chrono/api/views.py
index 46592c2..5779a95 100644
--- a/chrono/api/views.py
+++ b/chrono/api/views.py
@@ -49,13 +49,24 @@ def get_exceptions_by_desk(agenda):
     return exceptions_by_desk
 
 
-def get_all_slots(agenda, meeting_type):
+def get_min_datetime(agenda):
+    if agenda.minimal_booking_delay is None:
+        return None
     min_datetime = now() + datetime.timedelta(days=agenda.minimal_booking_delay)
+    return min_datetime.replace(hour=0, minute=0, second=0, microsecond=0)
+
+
+def get_max_datetime(agenda):
+    if agenda.maximal_booking_delay is None:
+        return None
     max_datetime = now() + datetime.timedelta(days=agenda.maximal_booking_delay)
-    min_datetime = min_datetime.replace(hour=0, minute=0, second=0, microsecond=0)
     max_datetime = max_datetime.replace(hour=0, minute=0, second=0, microsecond=0)
-    max_datetime = max_datetime + datetime.timedelta(days=1)
+    return max_datetime + datetime.timedelta(days=1)
+
 
+def get_all_slots(agenda, meeting_type):
+    min_datetime = get_min_datetime(agenda)
+    max_datetime = get_max_datetime(agenda)
     time_period_filters = {
         'min_datetime': min_datetime,
         'max_datetime': max_datetime,
@@ -71,6 +82,12 @@ def get_all_slots(agenda, meeting_type):
         open_slots[agenda] = defaultdict(lambda: Intervals())
 
     for agenda in agendas:
+        used_time_period_filters = time_period_filters.copy()
+        if used_time_period_filters['min_datetime'] is None:
+            used_time_period_filters['min_datetime'] = get_min_datetime(agenda)
+        if used_time_period_filters['max_datetime'] is None:
+            used_time_period_filters['max_datetime'] = get_max_datetime(agenda)
+
         for time_period in TimePeriod.objects.filter(desk__agenda=agenda):
             duration = (
                 datetime.datetime.combine(base_date, time_period.end_time)
@@ -79,7 +96,7 @@ def get_all_slots(agenda, meeting_type):
             if duration < meeting_type.duration:
                 # skip time period that can't even hold a single meeting
                 continue
-            for slot in time_period.get_time_slots(**time_period_filters):
+            for slot in time_period.get_time_slots(**used_time_period_filters):
                 slot.full = False
                 open_slots[agenda][time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot)
 
@@ -93,11 +110,18 @@ def get_all_slots(agenda, meeting_type):
                 open_slots[agenda][desk].remove_overlap(localtime(begin), localtime(end))
 
     for agenda in agendas:
+        used_min_datetime = min_datetime
+        if used_min_datetime is None:
+            used_min_datetime = get_min_datetime(agenda)
+        used_max_datetime = max_datetime
+        if used_max_datetime is None:
+            used_max_datetime = get_max_datetime(agenda)
+
         for event in (
             agenda.event_set.filter(
                 agenda=agenda,
-                start_datetime__gte=min_datetime,
-                start_datetime__lte=max_datetime + datetime.timedelta(meeting_type.duration),
+                start_datetime__gte=used_min_datetime,
+                start_datetime__lte=used_max_datetime + datetime.timedelta(meeting_type.duration),
             )
             .select_related('meeting_type')
             .exclude(booking__cancellation_datetime__isnull=False)
diff --git a/tests/test_api.py b/tests/test_api.py
index 16296f6..be591d2 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -2180,8 +2180,8 @@ def test_virtual_agenda_detail(app, virtual_meetings_agenda):
             'text': 'Virtual Agenda',
             'id': 'virtual-agenda',
             'slug': 'virtual-agenda',
-            'minimal_booking_delay': 1,
-            'maximal_booking_delay': 56,
+            'minimal_booking_delay': None,
+            'maximal_booking_delay': None,
             'kind': 'virtual',
             'api': {
                 'meetings_url': 'http://testserver/api/agenda/%s/meetings/' % virtual_meetings_agenda.slug,
@@ -2340,6 +2340,57 @@ def test_virtual_agendas_meetings_datetimes_api(app, virtual_meetings_agenda):
     assert len(resp.json['data']) == 3
 
 
+def test_virtual_agendas_meetings_datetimes_delays_api(app, mock_now):
+    foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7)
+    MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
+    foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
+    TimePeriod.objects.create(
+        weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
+    )
+    TimePeriod.objects.create(
+        weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
+    )
+
+    bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7)
+    MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
+    bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
+    TimePeriod.objects.create(
+        weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=bar_desk_1,
+    )
+    TimePeriod.objects.create(
+        weekday=3, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=bar_desk_1,
+    )
+
+    virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
+
+    VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
+    VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
+    virt_meeting_type = virt_agenda.iter_meetingtypes()[0]
+    api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, virt_meeting_type.slug)
+    resp = app.get(api_url)
+    # 8 slots for m each agenda
+    assert len(resp.json['data']) == 16
+
+    # restrict foo's minimal_booking_delay : only bar's slots are left
+    foo_agenda.minimal_booking_delay = 6
+    foo_agenda.save()
+    resp = app.get(api_url)
+    assert len(resp.json['data']) == 8
+
+    # restrict bar's maximal_booking_delay : only half of bar's slots are left
+    bar_agenda.maximal_booking_delay = 4
+    bar_agenda.save()
+    resp = app.get(api_url)
+    assert len(resp.json['data']) == 4
+
+    # put back very slots from foo
+    foo_agenda.minimal_booking_delay = 1
+    foo_agenda.maximal_booking_delay = 7
+    foo_agenda.save()
+    resp = app.get(api_url)
+    assert len(resp.json['data']) == 12
+
+
 def test_virtual_agendas_meetings_exception(app, user, virtual_meetings_agenda):
     app.authorization = ('Basic', ('john.doe', 'password'))
     real_agenda = virtual_meetings_agenda.real_agendas.first()
-- 
2.20.1

