api: endpoint to resize a booking (#40039)
This commit is contained in:
parent
bccba482b4
commit
8ea7b2ae37
|
@ -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)
|
||||
|
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
@ -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,)
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue