diff --git a/chrono/api/views.py b/chrono/api/views.py index 74af5617..458a77e4 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -18,6 +18,7 @@ from collections import defaultdict import datetime from django.core.urlresolvers import reverse +from django.db import transaction from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 from django.utils.dateparse import parse_date @@ -315,6 +316,7 @@ class SlotSerializer(serializers.Serializer): user_display_label = serializers.CharField(max_length=250, allow_blank=True) backoffice_url = serializers.URLField(allow_blank=True) count = serializers.IntegerField(min_value=1) + cancel_booking_id = serializers.IntegerField() class SlotsSerializer(SlotSerializer): @@ -381,6 +383,23 @@ class Fillslots(APIView): 'reason': 'count cannot be less than or equal to zero' }, status=status.HTTP_400_BAD_REQUEST) + to_cancel_booking = None + if 'cancel_booking_id' in payload: + cancel_error = None + try: + to_cancel_booking = Booking.objects.get(pk=payload.get('cancel_booking_id')) + if to_cancel_booking.cancellation_datetime: + cancel_error = 'cancel booking: booking already cancelled' + else: + to_cancel_places_count = to_cancel_booking.secondary_booking_set.count() + 1 + if places_count != to_cancel_places_count: + cancel_error = 'cancel booking: count is different' + except Booking.DoesNotExist: + cancel_error = 'cancel booking: booking does no exist' + + if cancel_error: + return Response({'err': 1, 'reason': cancel_error}) + extra_data = {} for k, v in request.data.items(): if k not in serializer.validated_data: @@ -447,22 +466,27 @@ class Fillslots(APIView): if (event.booked_places + places_count) > event.places: return Response({'err': 1, 'reason': 'sold out'}) - # now we have a list of events, book them. - primary_booking = None - for event in events: - for i in range(places_count): - new_booking = Booking(event_id=event.id, - in_waiting_list=in_waiting_list, - label=payload.get('label', ''), - user_name=payload.get('user_name', ''), - backoffice_url=payload.get('backoffice_url', ''), - user_display_label=payload.get('user_display_label', ''), - extra_data=extra_data) - if primary_booking is not None: - new_booking.primary_booking = primary_booking - new_booking.save() - if primary_booking is None: - primary_booking = new_booking + with transaction.atomic(): + if to_cancel_booking: + cancelled_booking_id = to_cancel_booking.pk + to_cancel_booking.cancel() + + # now we have a list of events, book them. + primary_booking = None + for event in events: + for i in range(places_count): + new_booking = Booking(event_id=event.id, + in_waiting_list=in_waiting_list, + label=payload.get('label', ''), + user_name=payload.get('user_name', ''), + backoffice_url=payload.get('backoffice_url', ''), + user_display_label=payload.get('user_display_label', ''), + extra_data=extra_data) + if primary_booking is not None: + new_booking.primary_booking = primary_booking + new_booking.save() + if primary_booking is None: + primary_booking = new_booking response = { 'err': 0, @@ -486,6 +510,8 @@ class Fillslots(APIView): response['desk'] = { 'label': available_desk.label, 'slug': available_desk.slug} + if to_cancel_booking: + response['cancelled_booking_id'] = cancelled_booking_id return Response(response) diff --git a/tests/test_api.py b/tests/test_api.py index 8b78f98d..b7952b6f 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -721,6 +721,75 @@ def test_booking_api_with_data(app, some_data, user): assert Booking.objects.count() == 1 assert Booking.objects.all()[0].extra_data == {'hello': 'world'} + +def test_booking_api_with_cancel_booking(app, some_data, user): + agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id + event_0, event_1, event_2, event_3 = Event.objects.filter(agenda_id=agenda_id)[0:4] + + app.authorization = ('Basic', ('john.doe', 'password')) + app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_0.id)) + assert Booking.objects.count() == 1 + first_booking = Booking.objects.first() + + # Book a new event and cancel previous booking + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_1.id), + params={'cancel_booking_id': first_booking.pk} + ) + assert resp.json['err'] == 0 + assert resp.json['cancelled_booking_id'] == first_booking.pk + assert Booking.objects.count() == 2 + first_booking = Booking.objects.get(pk=first_booking.pk) + assert first_booking.cancellation_datetime + + # Cancelling an already cancelled booking returns an error + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_1.id), + params={'cancel_booking_id': first_booking.pk} + ) + assert resp.json['err'] == 1 + assert resp.json['reason'] == 'cancel booking: booking already cancelled' + assert Booking.objects.count() == 2 + + # Cancelling a non existent booking returns an error + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_1.id), + params={'cancel_booking_id': '-1'} + ) + assert resp.json['err'] == 1 + assert resp.json['reason'] == 'cancel booking: booking does no exist' + assert Booking.objects.count() == 2 + + # Cancelling booking with different count than new booking + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_2.id), + params={'count': 2} + ) + assert resp.json['err'] == 0 + assert Booking.objects.count() == 4 + booking_id = resp.json['booking_id'] + + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_3.id), + params={'cancel_booking_id': booking_id, 'count': 1} + ) + assert resp.json['err'] == 1 + assert resp.json['reason'] == 'cancel booking: count is different' + assert Booking.objects.count() == 4 + + # cancel_booking_id must be an integer + app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_0.id), + params={'cancel_booking_id': 'no an integer'}, + status=400) + + # cancel_booking_id can't be empty if specified + app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_0.id), + params={'cancel_booking_id': ''}, + status=400) + + def test_booking_cancellation_api(app, some_data, user): agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id event = Event.objects.filter(agenda_id=agenda_id)[0]