From e22a037d67ee141dd8301c3a1a0874522903fe42 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Mon, 5 Jul 2021 17:29:16 +0200 Subject: [PATCH] api: allow changing recurrence bookings (#54746) --- chrono/api/views.py | 23 ++++++++++++--- tests/api/test_fillslot.py | 60 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/chrono/api/views.py b/chrono/api/views.py index bb0a64c..fd6854e 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -1594,11 +1594,18 @@ class RecurringFillslots(APIView): events_to_book = Event.objects.filter(event_filter) events_to_book = events_to_book.filter(start_datetime__gte=start_datetime, cancelled=False) + if end_datetime: + events_to_book = events_to_book.filter(start_datetime__lte=end_datetime) + + events_to_unbook = list( + agenda.event_set.filter(booking__user_external_id=user_external_id) + .exclude(pk__in=events_to_book) + .values_list('pk', flat=True) + ) + events_to_book = events_to_book.exclude(booking__user_external_id=user_external_id) full_events = list(events_to_book.filter(full=True)) events_to_book = events_to_book.filter(full=False) - if end_datetime: - events_to_book = events_to_book.filter(start_datetime__lte=end_datetime) if not events_to_book.exists(): if full_events: raise APIError(_('all events are all full'), err_class='all events are all full') @@ -1614,12 +1621,19 @@ class RecurringFillslots(APIView): extra_data = {k: v for k, v in request.data.items() if k not in payload} bookings = [make_booking(event, payload, extra_data) for event in events_to_book] + events_to_update = Event.annotate_queryset( + agenda.event_set.filter(pk__in=events_to_unbook + [event.pk for event in events_to_book]) + ) with transaction.atomic(): + deleted_count = Booking.objects.filter( + user_external_id=user_external_id, event__in=events_to_unbook + ).delete()[0] Booking.objects.bulk_create(bookings) + if django.VERSION < (2, 0): from django.db.models import Case, When - events_to_book.update( + events_to_update.update( full=Case( When( Q(booked_places_count__gte=F('places'), waiting_list_places=0) @@ -1637,7 +1651,7 @@ class RecurringFillslots(APIView): ), ) else: - events_to_book.update( + events_to_update.update( full=Q(booked_places_count__gte=F('places'), waiting_list_places=0) | Q(waiting_list_places__gt=0, waiting_list_count__gte=F('waiting_list_places')), almost_full=Q(booked_places_count__gte=0.9 * F('places')), @@ -1646,6 +1660,7 @@ class RecurringFillslots(APIView): response = { 'err': 0, 'booking_count': len(bookings), + 'cancelled_booking_count': deleted_count, 'full_events': [get_event_detail(request, x, agenda=agenda) for x in full_events], } return Response(response) diff --git a/tests/api/test_fillslot.py b/tests/api/test_fillslot.py index 7acca28..ec2ebe2 100644 --- a/tests/api/test_fillslot.py +++ b/tests/api/test_fillslot.py @@ -2166,6 +2166,7 @@ def test_recurring_events_api_fillslots(app, user, freezer): assert resp.json['booking_count'] == 155 assert len(resp.json['full_events']) == 1 assert resp.json['full_events'][0]['slug'] == event.slug + resp = app.post_json(fillslots_url, params=params) params['user_external_id'] = 'user_id_4' resp = app.post_json(fillslots_url, params=params) @@ -2193,3 +2194,62 @@ def test_recurring_events_api_fillslots(app, user, freezer): resp = app.post_json(fillslots_url, params={'user_external_id': 'a', 'slots': 'a:1'}, status=400) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'event a is not bookable' + + +def test_recurring_events_api_fillslots_change_bookings(app, user, freezer): + freezer.move_to('2021-09-06 12:00') + agenda = Agenda.objects.create(label='Foo bar', kind='events') + event = Event.objects.create( + label='Event', + start_datetime=now(), + recurrence_days=[0, 1, 3, 4], # Monday, Tuesday, Thursday, Friday + places=1, + waiting_list_places=1, + agenda=agenda, + recurrence_end_date=now() + datetime.timedelta(days=364), + ) + event.create_all_recurrences() + + app.authorization = ('Basic', ('john.doe', 'password')) + fillslots_url = '/api/agenda/%s/recurring_fillslots/' % agenda.slug + params = {'user_external_id': 'user_id'} + # Book Monday and Thursday + params['slots'] = 'event:0,event:3' + resp = app.post_json(fillslots_url, params=params) + assert resp.json['booking_count'] == 104 + assert resp.json['cancelled_booking_count'] == 0 + assert Booking.objects.count() == 104 + assert Booking.objects.filter(event__start_datetime__week_day=2).count() == 52 + assert Booking.objects.filter(event__start_datetime__week_day=5).count() == 52 + + # Change booking to Monday and Tuesday + params['slots'] = 'event:0,event:1' + resp = app.post_json(fillslots_url, params=params) + assert resp.json['booking_count'] == 52 + assert resp.json['cancelled_booking_count'] == 52 + assert Booking.objects.count() == 104 + assert Booking.objects.filter(event__start_datetime__week_day=2).count() == 52 + assert Booking.objects.filter(event__start_datetime__week_day=3).count() == 52 + + params = {'user_external_id': 'user_id_2'} + params['slots'] = 'event:0,event:3' + resp = app.post_json(fillslots_url, params=params) + assert resp.json['booking_count'] == 104 + assert resp.json['cancelled_booking_count'] == 0 + assert Booking.objects.count() == 208 + assert Booking.objects.filter(event__start_datetime__week_day=2).count() == 104 + assert Booking.objects.filter(event__start_datetime__week_day=5).count() == 52 + events = Event.annotate_queryset(Event.objects.filter(primary_event__isnull=False)) + assert events.filter(booked_places_count=1).count() == 156 + assert events.filter(waiting_list_count=1).count() == 52 + + params['slots'] = 'event:1,event:4' + resp = app.post_json(fillslots_url, params=params) + assert resp.json['booking_count'] == 104 + assert resp.json['cancelled_booking_count'] == 104 + assert Booking.objects.count() == 208 + assert Booking.objects.filter(event__start_datetime__week_day=3).count() == 104 + assert Booking.objects.filter(event__start_datetime__week_day=6).count() == 52 + events = Event.annotate_queryset(Event.objects.filter(primary_event__isnull=False)) + assert events.filter(booked_places_count=1).count() == 156 + assert events.filter(waiting_list_count=1).count() == 52 -- 2.20.1