api: endpoint to resize a booking (#40039)

This commit is contained in:
Lauréline Guérin 2020-03-05 14:02:52 +01:00
parent bccba482b4
commit 8ea7b2ae37
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
4 changed files with 233 additions and 0 deletions

View File

@ -507,6 +507,21 @@ class Booking(models.Model):
ics.add(vevent)
return ics.serialize()
def clone(self, in_waiting_list=False, primary_booking=None, save=True):
new_booking = Booking(
primary_booking=primary_booking,
event_id=self.event_id,
in_waiting_list=in_waiting_list,
label=self.label,
user_name=self.user_name,
backoffice_url=self.backoffice_url,
user_display_label=self.user_display_label,
extra_data=self.extra_data,
)
if save:
new_booking.save()
return new_booking
class Desk(models.Model):
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE)

View File

@ -49,5 +49,6 @@ urlpatterns = [
url(r'^booking/(?P<booking_pk>\w+)/cancel/$', views.cancel_booking, name='api-cancel-booking'),
url(r'^booking/(?P<booking_pk>\w+)/accept/$', views.accept_booking, name='api-accept-booking'),
url(r'^booking/(?P<booking_pk>\w+)/suspend/$', views.suspend_booking, name='api-suspend-booking'),
url(r'^booking/(?P<booking_pk>\w+)/resize/$', views.resize_booking, name='api-resize-booking'),
url(r'^booking/(?P<booking_pk>\w+)/ics/$', views.booking_ics, name='api-booking-ics'),
]

View File

@ -845,6 +845,106 @@ class SuspendBooking(APIView):
suspend_booking = SuspendBooking.as_view()
class ResizeSerializer(serializers.Serializer):
count = serializers.IntegerField(min_value=1)
class ResizeBooking(APIView):
'''
Resize a booking.
It will return error codes if the booking was cancelled before (code 1)
if the booking is not primary (code 2)
if the event is sold out (code 3) or
if the booking is on multi events (code 4).
'''
permission_classes = (permissions.IsAuthenticated,)
serializer_class = ResizeSerializer
def post(self, request, booking_pk=None, format=None):
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
return Response(
{
'err': 1,
'err_class': 'invalid payload',
'err_desc': _('invalid payload'),
'errors': serializer.errors,
},
status=status.HTTP_400_BAD_REQUEST,
)
payload = serializer.validated_data
booking = get_object_or_404(Booking, pk=booking_pk)
event = booking.event
if booking.cancellation_datetime:
response = {
'err': 1,
'err_class': 'booking is cancelled',
'err_desc': _('booking is cancelled'),
}
return Response(response)
if booking.primary_booking is not None:
response = {
'err': 2,
'err_class': 'secondary booking',
'err_desc': _('secondary booking'),
}
return Response(response)
event_ids = set([event.pk])
secondary_bookings = booking.secondary_booking_set.all().order_by('-creation_datetime')
for secondary in secondary_bookings:
event_ids.add(secondary.event_id)
if len(event_ids) > 1:
response = {
'err': 4,
'err_class': 'can not resize multi event booking',
'err_desc': _('can not resize multi event booking'),
}
return Response(response)
count = payload['count']
booked_places = event.waiting_list_places and event.waiting_list or event.booked_places
if booked_places < count:
if (
event.waiting_list
and count > event.waiting_list_places
or not event.waiting_list
and count > event.places
):
response = {
'err': 3,
'err_class': 'sold out',
'err_desc': _('sold out'),
}
return Response(response)
with transaction.atomic():
if booked_places > count:
# decrease places
for secondary in secondary_bookings[: booked_places - count]:
secondary.delete()
elif booked_places < count:
# increase places
bulk_bookings = []
for i in range(0, count - booked_places):
bulk_bookings.append(
booking.clone(
in_waiting_list=bool(event.waiting_list_places and event.waiting_list),
primary_booking=booking,
save=False,
)
)
Booking.objects.bulk_create(bulk_bookings)
response = {'err': 0, 'booking_id': booking.pk}
return Response(response)
resize_booking = ResizeBooking.as_view()
class SlotStatus(APIView):
permission_classes = (permissions.IsAuthenticated,)

View File

@ -1419,6 +1419,123 @@ def test_suspend_booking(app, some_data, user):
assert primary.in_waiting_list is False
def test_resize_booking(app, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
event.places = 5
event.waiting_list_places = 5
event.save()
event2 = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[1]
# create a booking not on the waiting list
primary = Booking.objects.create(event=event, in_waiting_list=False)
secondary = Booking.objects.create(event=event, in_waiting_list=False, primary_booking=primary)
assert Booking.objects.filter(in_waiting_list=False).count() == 2
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/booking/%s/resize/' % secondary.pk, params={'count': 42})
assert resp.json['err'] == 2
assert resp.json['reason'] == 'secondary booking' # legacy
assert resp.json['err_class'] == 'secondary booking'
assert resp.json['err_desc'] == 'secondary booking'
# resize a booking that doesn't exist
resp = app.post('/api/booking/0/resize/', params={'count': 42}, status=404)
# decrease a booking to 0
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 0}, status=400)
assert resp.json['err'] == 1
# decrease a booking not in waiting list
assert Booking.objects.filter(in_waiting_list=False).count() == 2
assert Booking.objects.filter().count() == 2
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=False).count() == 1
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
assert Booking.objects.filter().count() == 1
# no changes
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=False).count() == 1
assert Booking.objects.filter().count() == 1
# increase a booking not in waiting list
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 2})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=False).count() == 2
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
assert Booking.objects.filter().count() == 2
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6})
assert resp.json['err'] == 3
assert resp.json['reason'] == 'sold out' # legacy
assert resp.json['err_class'] == 'sold out'
assert resp.json['err_desc'] == 'sold out'
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=False).count() == 5
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
assert Booking.objects.filter().count() == 5
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6})
assert resp.json['err'] == 3
assert resp.json['reason'] == 'sold out' # legacy
assert resp.json['err_class'] == 'sold out'
assert resp.json['err_desc'] == 'sold out'
# decrease a booking in waiting list
primary.suspend()
assert Booking.objects.filter(in_waiting_list=True).count() == 5
assert Booking.objects.filter().count() == 5
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=True).count() == 1
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
assert Booking.objects.filter().count() == 1
# no changes
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=True).count() == 1
assert Booking.objects.filter().count() == 1
# increase a booking in waiting list
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 2})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=True).count() == 2
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
assert Booking.objects.filter().count() == 2
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6})
assert resp.json['err'] == 3
assert resp.json['reason'] == 'sold out' # legacy
assert resp.json['err_class'] == 'sold out'
assert resp.json['err_desc'] == 'sold out'
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=True).count() == 5
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
assert Booking.objects.filter().count() == 5
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6})
assert resp.json['err'] == 3
assert resp.json['reason'] == 'sold out' # legacy
assert resp.json['err_class'] == 'sold out'
assert resp.json['err_desc'] == 'sold out'
# resize a booking that is on multi events
secondary = Booking.objects.create(event=event2, in_waiting_list=False, primary_booking=primary)
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42})
assert resp.json['err'] == 4
assert resp.json['reason'] == 'can not resize multi event booking' # legacy
assert resp.json['err_class'] == 'can not resize multi event booking'
assert resp.json['err_desc'] == 'can not resize multi event booking'
# resize a booking that was cancelled before
primary.cancel()
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42})
assert resp.json['err'] == 1
def test_multiple_booking_api(app, some_data, user):
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0]