From c93a5933f232e60b5fe1a46815817c978a51c754 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Sat, 30 May 2020 14:49:58 +0200
Subject: [PATCH 2/2] api: return end_datetime on booking events (#37352)

---
 chrono/agendas/models.py |  6 +--
 chrono/api/views.py      |  5 +++
 tests/test_api.py        | 95 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 102 insertions(+), 4 deletions(-)

diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py
index c305f24..90d680a 100644
--- a/chrono/agendas/models.py
+++ b/chrono/agendas/models.py
@@ -850,20 +850,18 @@ class Booking(models.Model):
             self.event.agenda.pk,
             self.pk,
         )
 
         vevent.add('summary').value = self.user_display_label or self.label
         vevent.add('dtstart').value = self.event.start_datetime
         if self.user_name:
             vevent.add('attendee').value = self.user_name
-        if self.event.meeting_type:
-            vevent.add('dtend').value = self.event.start_datetime + datetime.timedelta(
-                minutes=self.event.meeting_type.duration
-            )
+        if self.event.end_datetime:
+            vevent.add('dtend').value = self.event.end_datetime
 
         for field in ('description', 'location', 'comment', 'url'):
             field_value = request and request.GET.get(field) or self.extra_data.get(field)
             if field_value:
                 vevent.add(field).value = field_value
         ics.add(vevent)
         return ics.serialize()
 
diff --git a/chrono/api/views.py b/chrono/api/views.py
index dd3c8ec..a3f74f8 100644
--- a/chrono/api/views.py
+++ b/chrono/api/views.py
@@ -988,22 +988,27 @@ class Fillslots(APIView):
             response['desk'] = {'label': available_desk.label, 'slug': available_desk.slug}
         if to_cancel_booking:
             response['cancelled_booking_id'] = cancelled_booking_id
         if agenda.kind == 'events' and not multiple_booking:
             event = events[0]
             # event.full is not up to date, it might have been changed by previous new_booking.save().
             event.refresh_from_db()
             response['places'] = get_event_places(event)
+            if event.end_datetime:
+                response['end_datetime'] = format_response_datetime(event.end_datetime)
+            else:
+                response['end_datetime'] = None
         if agenda.kind == 'events' and multiple_booking:
             response['events'] = [
                 {
                     'slug': x.slug,
                     'text': str(x),
                     'datetime': format_response_datetime(x.start_datetime),
+                    'end_datetime': format_response_datetime(x.end_datetime) if x.end_datetime else None,
                     'description': x.description,
                 }
                 for x in events
             ]
         if agenda.kind == 'meetings':
             response['resources'] = [r.slug for r in resources]
 
         return Response(response)
diff --git a/tests/test_api.py b/tests/test_api.py
index dd9727f..96b13fd 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -3619,8 +3619,103 @@ def test_virtual_agendas_meetings_booking_default_policy(app, mock_now, user):
         )
         if next_agenda:
             assert booking.event.agenda == next_agenda
 
     foo_num_bookings = Booking.objects.filter(event__desk__agenda=foo_agenda).count()
     bar_num_bookings = Booking.objects.filter(event__desk__agenda=bar_agenda).count()
     assert foo_num_bookings == 2
     assert bar_num_bookings == 4
+
+
+@pytest.mark.freeze_time('2017-04-01')
+def test_duration_on_booking_api_fillslot_response(app, user):
+    agenda = Agenda(label=u'Foo bar')
+    agenda.save()
+    first_date = datetime.datetime(2017, 5, 20, 1, 12)
+    durations = [None, 0, 45]
+    evt = []
+    for i in range(3):
+        evt.append(Event(
+            start_datetime=first_date + datetime.timedelta(days=i),
+            duration=durations[i],
+            places=20,
+            agenda=agenda))
+        evt[i].save()
+
+    assert evt[0].end_datetime is None
+    assert evt[1].end_datetime == evt[1].start_datetime
+    assert evt[2].end_datetime == evt[2].start_datetime + datetime.timedelta(minutes=45)
+    app.authorization = ('Basic', ('john.doe', 'password'))
+
+    resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, evt[0].id))
+    assert resp.json['datetime'] == '2017-05-20 01:12:00'
+    assert resp.json['end_datetime'] is None
+    assert 'ics_url' in resp.json['api']
+    ics = app.get(resp.json['api']['ics_url']).text
+    assert 'DTSTART:20170519T231200Z' in ics
+    assert 'DTEND:' not in ics
+
+    resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, evt[1].id))
+    assert resp.json['datetime'] == '2017-05-21 01:12:00'
+    assert resp.json['end_datetime'] == resp.json['datetime']
+    assert 'ics_url' in resp.json['api']
+    ics = app.get(resp.json['api']['ics_url']).text
+    assert 'DTSTART:20170520T231200Z' in ics
+    assert 'DTEND:20170520T231200Z' in ics
+
+    resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, evt[2].id))
+    assert resp.json['datetime'] == '2017-05-22 01:12:00'
+    assert resp.json['end_datetime'] == '2017-05-22 01:57:00'
+    assert 'ics_url' in resp.json['api']
+    ics = app.get(resp.json['api']['ics_url']).text
+    assert 'DTSTART:20170521T231200Z' in ics
+    assert 'DTEND:20170521T235700Z' in ics
+
+
+@pytest.mark.freeze_time('2017-04-01')
+def test_duration_on_booking_api_fillslots_response(app, user):
+    agenda = Agenda(label=u'Foo bar')
+    agenda.save()
+    first_date = datetime.datetime(2017, 5, 20, 1, 12)
+    durations = [None, 0, 45]
+    evt = []
+    for i in range(3):
+        evt.append(Event(
+            start_datetime=first_date + datetime.timedelta(days=i),
+            duration=durations[i],
+            places=20,
+            agenda=agenda))
+        evt[i].save()
+
+    assert evt[0].end_datetime is None
+    assert evt[1].end_datetime == evt[1].start_datetime
+    assert evt[2].end_datetime == evt[2].start_datetime + datetime.timedelta(minutes=45)
+    app.authorization = ('Basic', ('john.doe', 'password'))
+
+    # first event having null duration
+    string_param = ','.join([str(e.id) for e in evt[::-1]])  # unordered parameters
+    resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': string_param})
+    r_evt = resp.json['events']
+
+    assert r_evt[0]['datetime'] == '2017-05-20 01:12:00'
+    assert r_evt[0]['end_datetime'] is None
+    assert r_evt[1]['datetime'] == '2017-05-21 01:12:00'
+    assert r_evt[1]['end_datetime'] == r_evt[1]['datetime']
+    assert r_evt[2]['datetime'] == '2017-05-22 01:12:00'
+    assert r_evt[2]['end_datetime'] == '2017-05-22 01:57:00'
+    assert 'ics_url' in resp.json['api']
+    ics = app.get(resp.json['api']['ics_url']).text
+    assert 'DTSTART:20170519T231200Z' in ics
+    assert 'DTEND:' not in ics
+
+    # first event having duration
+    evt[0].duration = 90
+    evt[0].save()
+    resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': string_param})
+    r_evt = resp.json['events']
+
+    assert r_evt[0]['datetime'] == '2017-05-20 01:12:00'
+    assert r_evt[0]['end_datetime'] == '2017-05-20 02:42:00'
+    assert 'ics_url' in resp.json['api']
+    ics = app.get(resp.json['api']['ics_url']).text
+    assert 'DTSTART:20170519T231200Z' in ics
+    assert 'DTEND:20170520T004200Z' in ics
-- 
2.26.2

