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:
|
||||
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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 = {
|
||||
|
|
Loading…
Reference in New Issue