api: events fillslots, return details of cancelled events (#76326)
gitea/chrono/pipeline/head This commit looks good Details

This commit is contained in:
Lauréline Guérin 2023-04-06 14:38:08 +02:00
parent c7e9c0cd81
commit 41df2c58a5
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
4 changed files with 114 additions and 26 deletions

View File

@ -1797,7 +1797,7 @@ class Event(models.Model):
if end_datetime:
booked_events = booked_events.filter(start_datetime__lte=end_datetime)
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)

View File

@ -487,26 +487,13 @@ def get_event_detail(
bypass_delays=False,
with_status=False,
):
details = get_short_event_detail(
request=request,
event=event,
agenda=agenda,
multiple_agendas=multiple_agendas,
)
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:
details['booking'] = {
'id': booking.pk,
@ -598,6 +585,35 @@ def get_event_detail(
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(
request,
events,
@ -1777,7 +1793,7 @@ class RecurringFillslots(APIView):
end_datetime,
user_external_id,
guardian_external_id,
).values_list('pk', flat=True)
)
if payload.get('check_overlaps'):
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
)
events_by_id = {x.id: x for x in list(events_to_book) + list(events_to_unbook)}
with transaction.atomic():
# cancel existing bookings
cancellation_datetime = now()
Booking.objects.filter(primary_booking__in=bookings_to_cancel).update(
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)
# and delete outdated cancelled bookings
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],
}
if payload.get('include_booked_events_detail'):
events_to_book_by_id = {x.id: x for x in events_to_book}
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
]
response['cancelled_events'] = cancelled_events
return Response(response)
def get_event_recurrences(
@ -1903,7 +1928,7 @@ class RecurringFillslots(APIView):
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_unbook = [
e.pk
e
for agenda in agendas
for e in agenda.prefetched_events
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
)
events_by_id = {
x.id: x for x in (list(events) + events_to_unbook + events_to_unbook_out_of_min_delay)
}
with transaction.atomic():
# cancel existing bookings
cancellation_datetime = now()
@ -2049,12 +2077,28 @@ class EventsFillslots(APIView):
cancellation_datetime=cancellation_datetime,
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(
cancellation_datetime=cancellation_datetime, out_of_min_delay=True
)
Booking.objects.filter(primary_booking__in=bookings_to_cancel).update(
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(
cancellation_datetime=cancellation_datetime, out_of_min_delay=False
)
@ -2068,7 +2112,6 @@ class EventsFillslots(APIView):
# don't reload agendas and events types
for event in events:
event.agenda = agendas_by_ids[event.agenda_id]
events_by_id = {x.id: x for x in events}
response = {
'err': 0,
'booking_count': len(bookings),
@ -2087,6 +2130,7 @@ class EventsFillslots(APIView):
if x.event_id in waiting_list_event_ids
],
'cancelled_booking_count': cancelled_count,
'cancelled_events': cancelled_events,
}
return Response(response)

View File

@ -46,6 +46,8 @@ def test_api_events_fillslots(app, user):
== Booking.objects.filter(event=second_event).latest('pk').pk
)
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()
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']
== 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'
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
)
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
params = {'user_external_id': 'user_id', 'slots': 'event-2,event-3'}
resp = app.post_json(fillslots_url, params=params)
assert resp.json['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)
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)
assert resp.json['booking_count'] == 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]['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', 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
booking_2.refresh_from_db()

View File

@ -1311,8 +1311,23 @@ def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user):
},
)
assert resp.json['booking_count'] == 180
assert resp.json['cancelled_booking_count'] == 0
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')
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')
@ -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)
assert resp.json['booking_count'] == 5
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
params = {
'user_external_id': 'user_id',
'check_overlaps': 'first-agenda,second-agenda',
'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)
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 [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
params = {