api: events fillslots, return details of cancelled events (#76326)
gitea/chrono/pipeline/head This commit looks good
Details
gitea/chrono/pipeline/head This commit looks good
Details
This commit is contained in:
parent
c7e9c0cd81
commit
41df2c58a5
|
@ -1797,7 +1797,7 @@ class Event(models.Model):
|
||||||
if end_datetime:
|
if end_datetime:
|
||||||
booked_events = booked_events.filter(start_datetime__lte=end_datetime)
|
booked_events = booked_events.filter(start_datetime__lte=end_datetime)
|
||||||
if exclude_events:
|
if exclude_events:
|
||||||
booked_events = booked_events.exclude(pk__in=exclude_events)
|
booked_events = booked_events.exclude(pk__in=[e.pk for e in exclude_events])
|
||||||
|
|
||||||
return Event.annotate_queryset_with_overlaps(qs, booked_events)
|
return Event.annotate_queryset_with_overlaps(qs, booked_events)
|
||||||
|
|
||||||
|
|
|
@ -487,26 +487,13 @@ def get_event_detail(
|
||||||
bypass_delays=False,
|
bypass_delays=False,
|
||||||
with_status=False,
|
with_status=False,
|
||||||
):
|
):
|
||||||
|
details = get_short_event_detail(
|
||||||
|
request=request,
|
||||||
|
event=event,
|
||||||
|
agenda=agenda,
|
||||||
|
multiple_agendas=multiple_agendas,
|
||||||
|
)
|
||||||
agenda = agenda or event.agenda
|
agenda = agenda or event.agenda
|
||||||
details = {
|
|
||||||
'id': '%s@%s' % (agenda.slug, event.slug) if multiple_agendas else event.slug,
|
|
||||||
'slug': event.slug, # kept for compatibility
|
|
||||||
'text': get_event_text(event, agenda),
|
|
||||||
'label': event.label or '',
|
|
||||||
'agenda_label': agenda.label,
|
|
||||||
'date': format_response_date(event.start_datetime),
|
|
||||||
'datetime': format_response_datetime(event.start_datetime),
|
|
||||||
'end_datetime': format_response_datetime(event.end_datetime) if event.end_datetime else '',
|
|
||||||
'description': event.description,
|
|
||||||
'pricing': event.pricing,
|
|
||||||
'url': event.url,
|
|
||||||
'duration': event.duration,
|
|
||||||
'checked': event.checked,
|
|
||||||
'check_locked': event.check_locked,
|
|
||||||
'invoiced': event.invoiced,
|
|
||||||
}
|
|
||||||
for key, value in event.get_custom_fields().items():
|
|
||||||
details['custom_field_%s' % key] = value
|
|
||||||
if booking:
|
if booking:
|
||||||
details['booking'] = {
|
details['booking'] = {
|
||||||
'id': booking.pk,
|
'id': booking.pk,
|
||||||
|
@ -598,6 +585,35 @@ def get_event_detail(
|
||||||
return details
|
return details
|
||||||
|
|
||||||
|
|
||||||
|
def get_short_event_detail(
|
||||||
|
request,
|
||||||
|
event,
|
||||||
|
agenda=None,
|
||||||
|
multiple_agendas=False,
|
||||||
|
):
|
||||||
|
agenda = agenda or event.agenda
|
||||||
|
details = {
|
||||||
|
'id': '%s@%s' % (agenda.slug, event.slug) if multiple_agendas else event.slug,
|
||||||
|
'slug': event.slug, # kept for compatibility
|
||||||
|
'text': get_event_text(event, agenda),
|
||||||
|
'label': event.label or '',
|
||||||
|
'agenda_label': agenda.label,
|
||||||
|
'date': format_response_date(event.start_datetime),
|
||||||
|
'datetime': format_response_datetime(event.start_datetime),
|
||||||
|
'end_datetime': format_response_datetime(event.end_datetime) if event.end_datetime else '',
|
||||||
|
'description': event.description,
|
||||||
|
'pricing': event.pricing,
|
||||||
|
'url': event.url,
|
||||||
|
'duration': event.duration,
|
||||||
|
'checked': event.checked,
|
||||||
|
'check_locked': event.check_locked,
|
||||||
|
'invoiced': event.invoiced,
|
||||||
|
}
|
||||||
|
for key, value in event.get_custom_fields().items():
|
||||||
|
details['custom_field_%s' % key] = value
|
||||||
|
return details
|
||||||
|
|
||||||
|
|
||||||
def get_events_meta_detail(
|
def get_events_meta_detail(
|
||||||
request,
|
request,
|
||||||
events,
|
events,
|
||||||
|
@ -1777,7 +1793,7 @@ class RecurringFillslots(APIView):
|
||||||
end_datetime,
|
end_datetime,
|
||||||
user_external_id,
|
user_external_id,
|
||||||
guardian_external_id,
|
guardian_external_id,
|
||||||
).values_list('pk', flat=True)
|
)
|
||||||
|
|
||||||
if payload.get('check_overlaps'):
|
if payload.get('check_overlaps'):
|
||||||
self.check_for_overlaps(events_to_book, serializer.initial_slots)
|
self.check_for_overlaps(events_to_book, serializer.initial_slots)
|
||||||
|
@ -1830,12 +1846,21 @@ class RecurringFillslots(APIView):
|
||||||
user_external_id=user_external_id, event__in=events_to_unbook, cancellation_datetime__isnull=True
|
user_external_id=user_external_id, event__in=events_to_unbook, cancellation_datetime__isnull=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
events_by_id = {x.id: x for x in list(events_to_book) + list(events_to_unbook)}
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
# cancel existing bookings
|
# cancel existing bookings
|
||||||
cancellation_datetime = now()
|
cancellation_datetime = now()
|
||||||
Booking.objects.filter(primary_booking__in=bookings_to_cancel).update(
|
Booking.objects.filter(primary_booking__in=bookings_to_cancel).update(
|
||||||
cancellation_datetime=cancellation_datetime
|
cancellation_datetime=cancellation_datetime
|
||||||
)
|
)
|
||||||
|
cancelled_events = [
|
||||||
|
get_short_event_detail(
|
||||||
|
request,
|
||||||
|
events_by_id[x.event_id],
|
||||||
|
multiple_agendas=True,
|
||||||
|
)
|
||||||
|
for x in bookings_to_cancel
|
||||||
|
]
|
||||||
cancelled_count = bookings_to_cancel.update(cancellation_datetime=cancellation_datetime)
|
cancelled_count = bookings_to_cancel.update(cancellation_datetime=cancellation_datetime)
|
||||||
# and delete outdated cancelled bookings
|
# and delete outdated cancelled bookings
|
||||||
Booking.objects.filter(
|
Booking.objects.filter(
|
||||||
|
@ -1851,11 +1876,11 @@ class RecurringFillslots(APIView):
|
||||||
'full_events': [get_event_detail(request, x, multiple_agendas=True) for x in full_events],
|
'full_events': [get_event_detail(request, x, multiple_agendas=True) for x in full_events],
|
||||||
}
|
}
|
||||||
if payload.get('include_booked_events_detail'):
|
if payload.get('include_booked_events_detail'):
|
||||||
events_to_book_by_id = {x.id: x for x in events_to_book}
|
|
||||||
response['booked_events'] = [
|
response['booked_events'] = [
|
||||||
get_event_detail(request, events_to_book_by_id[x.event_id], booking=x, multiple_agendas=True)
|
get_event_detail(request, events_by_id[x.event_id], booking=x, multiple_agendas=True)
|
||||||
for x in created_bookings
|
for x in created_bookings
|
||||||
]
|
]
|
||||||
|
response['cancelled_events'] = cancelled_events
|
||||||
return Response(response)
|
return Response(response)
|
||||||
|
|
||||||
def get_event_recurrences(
|
def get_event_recurrences(
|
||||||
|
@ -1903,7 +1928,7 @@ class RecurringFillslots(APIView):
|
||||||
def get_events_to_unbook(self, agendas, events_to_book):
|
def get_events_to_unbook(self, agendas, events_to_book):
|
||||||
events_to_book_ids = set(events_to_book.values_list('pk', flat=True))
|
events_to_book_ids = set(events_to_book.values_list('pk', flat=True))
|
||||||
events_to_unbook = [
|
events_to_unbook = [
|
||||||
e.pk
|
e
|
||||||
for agenda in agendas
|
for agenda in agendas
|
||||||
for e in agenda.prefetched_events
|
for e in agenda.prefetched_events
|
||||||
if (e.user_places_count or e.user_waiting_places_count)
|
if (e.user_places_count or e.user_waiting_places_count)
|
||||||
|
@ -2042,6 +2067,9 @@ class EventsFillslots(APIView):
|
||||||
user_external_id=user_external_id, event__in=events_to_unbook, cancellation_datetime__isnull=True
|
user_external_id=user_external_id, event__in=events_to_unbook, cancellation_datetime__isnull=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
events_by_id = {
|
||||||
|
x.id: x for x in (list(events) + events_to_unbook + events_to_unbook_out_of_min_delay)
|
||||||
|
}
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
# cancel existing bookings
|
# cancel existing bookings
|
||||||
cancellation_datetime = now()
|
cancellation_datetime = now()
|
||||||
|
@ -2049,12 +2077,28 @@ class EventsFillslots(APIView):
|
||||||
cancellation_datetime=cancellation_datetime,
|
cancellation_datetime=cancellation_datetime,
|
||||||
out_of_min_delay=True,
|
out_of_min_delay=True,
|
||||||
)
|
)
|
||||||
|
cancelled_events = [
|
||||||
|
get_short_event_detail(
|
||||||
|
request,
|
||||||
|
events_by_id[x.event_id],
|
||||||
|
multiple_agendas=self.multiple_agendas,
|
||||||
|
)
|
||||||
|
for x in bookings_to_cancel_out_of_min_delay
|
||||||
|
]
|
||||||
cancelled_count = bookings_to_cancel_out_of_min_delay.update(
|
cancelled_count = bookings_to_cancel_out_of_min_delay.update(
|
||||||
cancellation_datetime=cancellation_datetime, out_of_min_delay=True
|
cancellation_datetime=cancellation_datetime, out_of_min_delay=True
|
||||||
)
|
)
|
||||||
Booking.objects.filter(primary_booking__in=bookings_to_cancel).update(
|
Booking.objects.filter(primary_booking__in=bookings_to_cancel).update(
|
||||||
cancellation_datetime=cancellation_datetime, out_of_min_delay=False
|
cancellation_datetime=cancellation_datetime, out_of_min_delay=False
|
||||||
)
|
)
|
||||||
|
cancelled_events += [
|
||||||
|
get_short_event_detail(
|
||||||
|
request,
|
||||||
|
events_by_id[x.event_id],
|
||||||
|
multiple_agendas=self.multiple_agendas,
|
||||||
|
)
|
||||||
|
for x in bookings_to_cancel
|
||||||
|
]
|
||||||
cancelled_count += bookings_to_cancel.update(
|
cancelled_count += bookings_to_cancel.update(
|
||||||
cancellation_datetime=cancellation_datetime, out_of_min_delay=False
|
cancellation_datetime=cancellation_datetime, out_of_min_delay=False
|
||||||
)
|
)
|
||||||
|
@ -2068,7 +2112,6 @@ class EventsFillslots(APIView):
|
||||||
# don't reload agendas and events types
|
# don't reload agendas and events types
|
||||||
for event in events:
|
for event in events:
|
||||||
event.agenda = agendas_by_ids[event.agenda_id]
|
event.agenda = agendas_by_ids[event.agenda_id]
|
||||||
events_by_id = {x.id: x for x in events}
|
|
||||||
response = {
|
response = {
|
||||||
'err': 0,
|
'err': 0,
|
||||||
'booking_count': len(bookings),
|
'booking_count': len(bookings),
|
||||||
|
@ -2087,6 +2130,7 @@ class EventsFillslots(APIView):
|
||||||
if x.event_id in waiting_list_event_ids
|
if x.event_id in waiting_list_event_ids
|
||||||
],
|
],
|
||||||
'cancelled_booking_count': cancelled_count,
|
'cancelled_booking_count': cancelled_count,
|
||||||
|
'cancelled_events': cancelled_events,
|
||||||
}
|
}
|
||||||
return Response(response)
|
return Response(response)
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,8 @@ def test_api_events_fillslots(app, user):
|
||||||
== Booking.objects.filter(event=second_event).latest('pk').pk
|
== Booking.objects.filter(event=second_event).latest('pk').pk
|
||||||
)
|
)
|
||||||
assert len(resp.json['waiting_list_events']) == 0
|
assert len(resp.json['waiting_list_events']) == 0
|
||||||
|
assert resp.json['cancelled_booking_count'] == 0
|
||||||
|
assert len(resp.json['cancelled_events']) == 0
|
||||||
|
|
||||||
events = Event.objects.all()
|
events = Event.objects.all()
|
||||||
assert events.filter(booked_places=1).count() == 2
|
assert events.filter(booked_places=1).count() == 2
|
||||||
|
@ -64,6 +66,8 @@ def test_api_events_fillslots(app, user):
|
||||||
resp.json['booked_events'][1]['booking']['id']
|
resp.json['booked_events'][1]['booking']['id']
|
||||||
== Booking.objects.filter(event=second_event).latest('pk').pk
|
== Booking.objects.filter(event=second_event).latest('pk').pk
|
||||||
)
|
)
|
||||||
|
assert resp.json['cancelled_booking_count'] == 0
|
||||||
|
assert len(resp.json['cancelled_events']) == 0
|
||||||
|
|
||||||
params['user_external_id'] = 'user_id_3'
|
params['user_external_id'] = 'user_id_3'
|
||||||
resp = app.post_json(fillslots_url, params=params)
|
resp = app.post_json(fillslots_url, params=params)
|
||||||
|
@ -81,12 +85,16 @@ def test_api_events_fillslots(app, user):
|
||||||
== Booking.objects.filter(event=event).latest('pk').pk
|
== Booking.objects.filter(event=event).latest('pk').pk
|
||||||
)
|
)
|
||||||
assert Booking.objects.filter(in_waiting_list=True, event=event).count() == 1
|
assert Booking.objects.filter(in_waiting_list=True, event=event).count() == 1
|
||||||
|
assert resp.json['cancelled_booking_count'] == 0
|
||||||
|
assert len(resp.json['cancelled_events']) == 0
|
||||||
|
|
||||||
# change bookings
|
# change bookings
|
||||||
params = {'user_external_id': 'user_id', 'slots': 'event-2,event-3'}
|
params = {'user_external_id': 'user_id', 'slots': 'event-2,event-3'}
|
||||||
resp = app.post_json(fillslots_url, params=params)
|
resp = app.post_json(fillslots_url, params=params)
|
||||||
assert resp.json['booking_count'] == 1
|
assert resp.json['booking_count'] == 1
|
||||||
assert resp.json['cancelled_booking_count'] == 1
|
assert resp.json['cancelled_booking_count'] == 1
|
||||||
|
assert len(resp.json['cancelled_events']) == 1
|
||||||
|
assert [x['date'] for x in resp.json['cancelled_events']] == ['2021-09-07']
|
||||||
|
|
||||||
user_bookings = Booking.objects.filter(user_external_id='user_id', cancellation_datetime__isnull=True)
|
user_bookings = Booking.objects.filter(user_external_id='user_id', cancellation_datetime__isnull=True)
|
||||||
assert {b.event.slug for b in user_bookings} == {'event-2', 'event-3'}
|
assert {b.event.slug for b in user_bookings} == {'event-2', 'event-3'}
|
||||||
|
@ -181,7 +189,6 @@ def test_api_events_fillslots_with_cancelled(app, user):
|
||||||
resp = app.post_json(fillslots_url, params=params)
|
resp = app.post_json(fillslots_url, params=params)
|
||||||
assert resp.json['booking_count'] == 2
|
assert resp.json['booking_count'] == 2
|
||||||
assert len(resp.json['booked_events']) == 2
|
assert len(resp.json['booked_events']) == 2
|
||||||
assert resp.json['cancelled_booking_count'] == 0
|
|
||||||
assert resp.json['booked_events'][0]['id'] == 'event-1'
|
assert resp.json['booked_events'][0]['id'] == 'event-1'
|
||||||
assert (
|
assert (
|
||||||
resp.json['booked_events'][0]['booking']['id']
|
resp.json['booked_events'][0]['booking']['id']
|
||||||
|
@ -194,6 +201,8 @@ def test_api_events_fillslots_with_cancelled(app, user):
|
||||||
)
|
)
|
||||||
assert Booking.objects.filter(user_external_id='user_id').count() == 3
|
assert Booking.objects.filter(user_external_id='user_id').count() == 3
|
||||||
assert Booking.objects.filter(user_external_id='user_id', cancellation_datetime__isnull=True).count() == 3
|
assert Booking.objects.filter(user_external_id='user_id', cancellation_datetime__isnull=True).count() == 3
|
||||||
|
assert resp.json['cancelled_booking_count'] == 0
|
||||||
|
assert len(resp.json['cancelled_events']) == 0
|
||||||
|
|
||||||
assert Booking.objects.filter(pk=booking_1.pk).exists() is False # cancelled booking deleted
|
assert Booking.objects.filter(pk=booking_1.pk).exists() is False # cancelled booking deleted
|
||||||
booking_2.refresh_from_db()
|
booking_2.refresh_from_db()
|
||||||
|
|
|
@ -1311,8 +1311,23 @@ def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert resp.json['booking_count'] == 180
|
assert resp.json['booking_count'] == 180
|
||||||
|
assert resp.json['cancelled_booking_count'] == 0
|
||||||
assert len(ctx.captured_queries) == 15
|
assert len(ctx.captured_queries) == 15
|
||||||
|
|
||||||
|
with CaptureQueriesContext(connection) as ctx:
|
||||||
|
resp = app.post_json(
|
||||||
|
'/api/agendas/recurring-events/fillslots/?action=update&agendas=%s' % agenda_slugs,
|
||||||
|
params={
|
||||||
|
'slots': events_to_book[1:],
|
||||||
|
'user_external_id': 'user',
|
||||||
|
'include_booked_events_detail': True,
|
||||||
|
'check_overlaps': agenda_slugs,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert resp.json['booking_count'] == 0
|
||||||
|
assert resp.json['cancelled_booking_count'] == 5
|
||||||
|
assert len(ctx.captured_queries) == 17
|
||||||
|
|
||||||
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
||||||
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
||||||
child = Person.objects.create(user_external_id='xxx', first_name='James', last_name='Doe')
|
child = Person.objects.create(user_external_id='xxx', first_name='James', last_name='Doe')
|
||||||
|
@ -1521,16 +1536,36 @@ def test_recurring_events_api_fillslots_overlapping_events(app, user):
|
||||||
resp = app.post_json(fillslots_url % ('update', 'first-agenda,second-agenda'), params=params)
|
resp = app.post_json(fillslots_url % ('update', 'first-agenda,second-agenda'), params=params)
|
||||||
assert resp.json['booking_count'] == 5
|
assert resp.json['booking_count'] == 5
|
||||||
assert resp.json['cancelled_booking_count'] == 14
|
assert resp.json['cancelled_booking_count'] == 14
|
||||||
|
assert 'booked_events' not in resp.json
|
||||||
|
assert 'cancelled_events' not in resp.json
|
||||||
|
|
||||||
# booking overlapping events is allowed if one has no duration
|
# booking overlapping events is allowed if one has no duration
|
||||||
params = {
|
params = {
|
||||||
'user_external_id': 'user_id',
|
'user_external_id': 'user_id',
|
||||||
'check_overlaps': 'first-agenda,second-agenda',
|
'check_overlaps': 'first-agenda,second-agenda',
|
||||||
'slots': 'second-agenda@event-12-18:5,second-agenda@no-duration:5',
|
'slots': 'second-agenda@event-12-18:5,second-agenda@no-duration:5',
|
||||||
|
'include_booked_events_detail': True,
|
||||||
}
|
}
|
||||||
resp = app.post_json(fillslots_url % ('update', 'first-agenda,second-agenda'), params=params)
|
resp = app.post_json(fillslots_url % ('update', 'first-agenda,second-agenda'), params=params)
|
||||||
assert resp.json['booking_count'] == 8
|
assert resp.json['booking_count'] == 8
|
||||||
|
assert [x['date'] for x in resp.json['booked_events']] == [
|
||||||
|
'2021-09-11',
|
||||||
|
'2021-09-11',
|
||||||
|
'2021-09-18',
|
||||||
|
'2021-09-18',
|
||||||
|
'2021-09-25',
|
||||||
|
'2021-09-25',
|
||||||
|
'2021-10-02',
|
||||||
|
'2021-10-02',
|
||||||
|
]
|
||||||
assert resp.json['cancelled_booking_count'] == 5
|
assert resp.json['cancelled_booking_count'] == 5
|
||||||
|
assert [x['date'] for x in resp.json['cancelled_events']] == [
|
||||||
|
'2021-09-07',
|
||||||
|
'2021-09-14',
|
||||||
|
'2021-09-21',
|
||||||
|
'2021-09-28',
|
||||||
|
'2021-10-05',
|
||||||
|
]
|
||||||
|
|
||||||
# booking overlapping events with durations is forbidden
|
# booking overlapping events with durations is forbidden
|
||||||
params = {
|
params = {
|
||||||
|
|
Loading…
Reference in New Issue