Revoir la gestion des temps ennemis pour les évènements récurrents (#73904) #32

Merged
vdeniaud merged 4 commits from wip/73904-Revoir-la-gestion-des-temps-enne into main 2023-02-13 10:24:08 +01:00
5 changed files with 340 additions and 28 deletions

View File

@ -38,7 +38,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import connection, models, transaction
from django.db.models import Count, Exists, ExpressionWrapper, F, Func, Max, OuterRef, Prefetch, Q, Value
from django.db.models.functions import Cast, Coalesce, Concat, ExtractWeek
from django.db.models.functions import Cast, Coalesce, Concat, ExtractWeek, ExtractWeekDay
from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist, engines
from django.urls import reverse
from django.utils import functional
@ -936,14 +936,21 @@ class Agenda(models.Model):
return qs
@staticmethod
def prefetch_recurring_events(qs, with_overlaps=False):
def prefetch_recurring_events(
qs, with_overlaps=None, user_external_id=None, start_datetime=None, end_datetime=None
):
recurring_event_queryset = Event.objects.filter(
Q(publication_datetime__isnull=True) | Q(publication_datetime__lte=now()),
recurrence_days__isnull=False,
)
if with_overlaps:
recurring_event_queryset = Event.annotate_recurring_events_with_overlaps(recurring_event_queryset)
recurring_event_queryset = Event.annotate_recurring_events_with_overlaps(
recurring_event_queryset, agendas=qs
)
recurring_event_queryset = Event.annotate_recurring_events_with_booking_overlaps(
recurring_event_queryset, with_overlaps, user_external_id, start_datetime, end_datetime
)
qs = qs.prefetch_related(
Prefetch(
@ -1620,16 +1627,22 @@ class Event(models.Model):
return qs
@staticmethod
def annotate_queryset_with_overlaps(qs):
qs = qs.annotate(
computed_end_datetime=ExpressionWrapper(
def annotate_queryset_with_overlaps(qs, other_events=None):
if not other_events:
other_events = qs
common_annotations = {
'computed_end_datetime': ExpressionWrapper(
F('start_datetime') + datetime.timedelta(minutes=1) * F('duration'),
output_field=models.DateTimeField(),
),
computed_slug=Concat('agenda__slug', Value('@'), 'slug', output_field=models.CharField()),
)
'computed_slug': Concat('agenda__slug', Value('@'), 'slug', output_field=models.CharField()),
}
overlapping_events = qs.filter(
qs = qs.annotate(**common_annotations)
other_events = other_events.annotate(**common_annotations)
overlapping_events = other_events.filter(
start_datetime__lt=OuterRef('computed_end_datetime'),
computed_end_datetime__gt=OuterRef('start_datetime'),
).exclude(pk=OuterRef('pk'))
@ -1643,7 +1656,7 @@ class Event(models.Model):
)
@staticmethod
def annotate_recurring_events_with_overlaps(qs):
def annotate_recurring_events_with_overlaps(qs, agendas=None):
qs = qs.annotate(
start_hour=Cast('start_datetime', models.TimeField()),
computed_end_datetime=ExpressionWrapper(
@ -1660,6 +1673,9 @@ class Event(models.Model):
recurrence_days__overlap=F('recurrence_days'),
).exclude(pk=OuterRef('pk'))
if agendas:
overlapping_events = overlapping_events.filter(agenda__in=agendas)
if django.VERSION >= (3, 2):
from django.db.models.functions import JSONObject
@ -1684,6 +1700,44 @@ class Event(models.Model):
)
)
@staticmethod
def annotate_recurring_events_with_booking_overlaps(
qs, agenda_slugs, user_external_id, start_datetime, end_datetime
):
recurrences = Event.objects.filter(primary_event=OuterRef('pk'))
recurrences = recurrences.annotate(
dj_weekday=ExtractWeekDay('start_datetime'),
dj_weekday_int=Cast('dj_weekday', models.IntegerField()),
weekday=(F('dj_weekday_int') - 2) % 7,
)
recurrences_with_overlaps = Event.annotate_queryset_with_booked_event_overlaps(
recurrences, agenda_slugs, user_external_id, start_datetime, end_datetime
).filter(has_overlap=True)
return qs.annotate(
days_with_booking_overlaps=ArraySubquery(
recurrences_with_overlaps.values('weekday'), output_field=ArrayField(models.IntegerField())
)
)
@staticmethod
def annotate_queryset_with_booked_event_overlaps(
qs, agenda_slugs, user_external_id, start_datetime, end_datetime, exclude_events=None
):
booked_events = Event.objects.filter(
agenda__slug__in=agenda_slugs,
start_datetime__gte=start_datetime,
booking__user_external_id=user_external_id,
booking__cancellation_datetime__isnull=True,
)
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)
return Event.annotate_queryset_with_overlaps(qs, booked_events)
@property
def remaining_places(self):
return max(0, self.places - self.booked_places)

View File

@ -159,6 +159,9 @@ class MultipleAgendasEventsCheckStatusSerializer(AgendaSlugsMixin, DateRangeMixi
class RecurringFillslotsSerializer(MultipleAgendasEventsFillSlotsSerializer):
include_booked_events_detail = serializers.BooleanField(default=False)
check_overlaps = CommaSeparatedStringField(
required=False, child=serializers.SlugField(max_length=160, allow_blank=False)
)
def validate_slots(self, value):
super().validate_slots(value)
@ -405,7 +408,9 @@ class RecurringFillslotsQueryStringSerializer(AgendaOrSubscribedSlugsSerializer)
class RecurringEventsListSerializer(AgendaOrSubscribedSlugsSerializer):
sort = serializers.ChoiceField(required=False, choices=['day'])
check_overlaps = serializers.BooleanField(default=False)
check_overlaps = CommaSeparatedStringField(
required=False, child=serializers.SlugField(max_length=160, allow_blank=False)
)
class EventSerializer(serializers.ModelSerializer):

View File

@ -1117,13 +1117,18 @@ class RecurringEventsList(APIView):
if not serializer.is_valid():
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
data = serializer.validated_data
check_overlaps = bool(data.get('check_overlaps'))
check_overlaps = data.get('check_overlaps')
start_datetime, end_datetime = data.get('date_start'), data.get('date_end')
if not start_datetime or start_datetime < now():
start_datetime = now()
guardian_external_id = data.get('guardian_external_id')
if guardian_external_id:
user_external_id = data['user_external_id']
agendas = Agenda.prefetch_events(
data['agendas'],
user_external_id=data.get('user_external_id'),
user_external_id=user_external_id,
guardian_external_id=guardian_external_id,
annotate_for_user=False,
)
@ -1137,6 +1142,9 @@ class RecurringEventsList(APIView):
)
if check_overlaps:
recurring_events = Event.annotate_recurring_events_with_overlaps(recurring_events)
recurring_events = Event.annotate_recurring_events_with_booking_overlaps(
recurring_events, check_overlaps, user_external_id, start_datetime, end_datetime
)
events = []
for event in recurring_events:
for day in days_by_event[event.pk]:
@ -1144,7 +1152,9 @@ class RecurringEventsList(APIView):
event.day = day
events.append(event)
else:
agendas = Agenda.prefetch_recurring_events(data['agendas'], with_overlaps=check_overlaps)
agendas = Agenda.prefetch_recurring_events(
data['agendas'], check_overlaps, data.get('user_external_id'), start_datetime, end_datetime
)
events = []
for agenda in agendas:
for event in agenda.get_open_recurring_events():
@ -1161,6 +1171,7 @@ class RecurringEventsList(APIView):
for day in x['days']
if day == event.day
]
event.has_booking_overlaps = bool(event.day in event.days_with_booking_overlaps)
if 'agendas' in request.query_params:
agenda_querystring_indexes = {
@ -1202,6 +1213,7 @@ class RecurringEventsList(APIView):
'pricing': event.pricing,
'url': event.url,
'overlaps': event.overlaps if check_overlaps else None,
'has_booking_overlaps': event.has_booking_overlaps if check_overlaps else None,
}
for event in events
]
@ -1741,6 +1753,15 @@ class RecurringFillslots(APIView):
if payload.get('check_overlaps'):
self.check_for_overlaps(events_to_book, serializer.initial_slots)
events_to_book = Event.annotate_queryset_with_booked_event_overlaps(
events_to_book,
payload['check_overlaps'],
user_external_id,
start_datetime,
end_datetime,
exclude_events=events_to_unbook,
)
events_to_book = events_to_book.exclude(has_overlap=True)
# outdated bookings to remove (cancelled bookings to replace by an active booking)
events_cancelled_to_delete = events_to_book.filter(

View File

@ -7,6 +7,7 @@ from django.utils.timezone import make_aware, now
from chrono.agendas.models import (
Agenda,
Booking,
Category,
Desk,
Event,
@ -507,7 +508,7 @@ def test_recurring_events_api_list_overlapping_events(app):
recurrence_end_date=end,
recurrence_days=[1],
agenda=agenda,
)
).create_all_recurrences()
Event.objects.create(
label='Event 14-15',
start_datetime=start + datetime.timedelta(hours=2),
@ -516,7 +517,7 @@ def test_recurring_events_api_list_overlapping_events(app):
recurrence_end_date=end,
recurrence_days=[1],
agenda=agenda,
)
).create_all_recurrences()
Event.objects.create(
label='Event 15-17',
start_datetime=start + datetime.timedelta(hours=3),
@ -525,7 +526,7 @@ def test_recurring_events_api_list_overlapping_events(app):
recurrence_end_date=end,
recurrence_days=[1, 3, 5],
agenda=agenda,
)
).create_all_recurrences()
agenda2 = Agenda.objects.create(label='Second Agenda', kind='events')
Desk.objects.create(agenda=agenda2, slug='_exceptions_holder')
Event.objects.create(
@ -536,7 +537,7 @@ def test_recurring_events_api_list_overlapping_events(app):
recurrence_end_date=end,
recurrence_days=[1, 5],
agenda=agenda2,
)
).create_all_recurrences()
Event.objects.create(
label='No duration',
start_datetime=start,
@ -544,10 +545,22 @@ def test_recurring_events_api_list_overlapping_events(app):
recurrence_end_date=end,
recurrence_days=[5],
agenda=agenda2,
)
).create_all_recurrences()
for agenda in [agenda, agenda2]:
Subscription.objects.create(agenda=agenda, user_external_id='user_id', date_start=now(), date_end=end)
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='user_id', first_name='James', last_name='Doe')
custody_agenda = SharedCustodyAgenda.objects.create(
first_guardian=father, second_guardian=mother, child=child, date_start=now()
)
SharedCustodyRule.objects.create(agenda=custody_agenda, guardian=father, days=list(range(7)))
params = {'sort': 'day', 'check_overlaps': True, 'user_external_id': 'user_id'}
resp = app.get(
'/api/agendas/recurring-events/?agendas=first-agenda,second-agenda&sort=day&check_overlaps=true'
'/api/agendas/recurring-events/', params={'agendas': 'first-agenda,second-agenda', **params}
)
assert [(x['id'], set(x['overlaps'])) for x in resp.json['data']] == [
('first-agenda@event-12-14:1', {'second-agenda@event-12-18:1'}),
@ -563,5 +576,149 @@ def test_recurring_events_api_list_overlapping_events(app):
('first-agenda@event-15-17:5', {'second-agenda@event-12-18:5'}),
]
resp = app.get('/api/agendas/recurring-events/?agendas=first-agenda,second-agenda&sort=day')
# same result with shared custody filter
subscribed_resp = app.get(
'/api/agendas/recurring-events/',
params={'subscribed': 'all', 'guardian_external_id': 'father_id', **params},
)
assert [(x['id'], set(x['overlaps'])) for x in resp.json['data']] == [
(x['id'], set(x['overlaps'])) for x in subscribed_resp.json['data']
]
resp = app.get('/api/agendas/recurring-events/', params={'agendas': 'first-agenda', **params})
assert [(x['id'], x['overlaps']) for x in resp.json['data']] == [
('first-agenda@event-12-14:1', []),
('first-agenda@event-14-15:1', []),
('first-agenda@event-15-17:1', []),
('first-agenda@event-15-17:3', []),
('first-agenda@event-15-17:5', []),
]
del params['check_overlaps']
resp = app.get(
'/api/agendas/recurring-events/', params={'agendas': 'first-agenda,second-agenda', **params}
)
assert ['overlaps' not in x for x in resp.json['data']]
@pytest.mark.freeze_time('2021-09-06 12:00')
@pytest.mark.parametrize('shared_custody', (True, False))
def test_recurring_events_api_list_overlapping_events_booking(app, shared_custody):
agenda = Agenda.objects.create(label='First Agenda', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
start, end = now(), now() + datetime.timedelta(days=30)
Event.objects.create(
label='Event 12-14',
start_datetime=start,
duration=120,
places=2,
recurrence_end_date=end,
recurrence_days=[1, 2],
agenda=agenda,
).create_all_recurrences()
event_15_16 = Event.objects.create(
label='Event 15-16',
start_datetime=start + datetime.timedelta(hours=3),
duration=60,
places=2,
recurrence_end_date=end,
recurrence_days=[1],
agenda=agenda,
)
event_15_16.create_all_recurrences()
second_agenda = Agenda.objects.create(label='Second Agenda', kind='events')
Desk.objects.create(agenda=second_agenda, slug='_exceptions_holder')
event_13_15 = Event.objects.create(
label='Event 13-15',
start_datetime=start + datetime.timedelta(hours=1),
duration=120,
places=2,
recurrence_end_date=end,
recurrence_days=[1, 2],
agenda=second_agenda,
)
event_13_15.create_all_recurrences()
if shared_custody:
Subscription.objects.create(agenda=agenda, user_external_id='user_id', date_start=now(), date_end=end)
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='user_id', first_name='James', last_name='Doe')
custody_agenda = SharedCustodyAgenda.objects.create(
first_guardian=father, second_guardian=mother, child=child, date_start=now()
)
SharedCustodyRule.objects.create(agenda=custody_agenda, guardian=father, days=list(range(7)))
params = {
'sort': 'day',
'check_overlaps': 'first-agenda,second-agenda',
'user_external_id': 'user_id',
}
if shared_custody:
params['subscribed'] = 'all'
params['guardian_external_id'] = 'father_id'
else:
params['agendas'] = 'first-agenda'
resp = app.get('/api/agendas/recurring-events/', params=params)
assert len(resp.json['data']) == 3
assert resp.json['data'][0]['id'] == 'first-agenda@event-12-14:1'
assert resp.json['data'][0]['has_booking_overlaps'] is False
assert resp.json['data'][1]['id'] == 'first-agenda@event-15-16:1'
assert resp.json['data'][1]['has_booking_overlaps'] is False
assert resp.json['data'][2]['id'] == 'first-agenda@event-12-14:2'
assert resp.json['data'][2]['has_booking_overlaps'] is False
# create one booking on first day
event = Event.objects.get(start_datetime__date='2021-09-21', primary_event=event_13_15)
Booking.objects.create(event=event, user_external_id='user_id')
resp = app.get('/api/agendas/recurring-events/', params=params)
assert len(resp.json['data']) == 3
assert resp.json['data'][0]['id'] == 'first-agenda@event-12-14:1'
assert resp.json['data'][0]['has_booking_overlaps'] is True
assert resp.json['data'][1]['id'] == 'first-agenda@event-15-16:1'
assert resp.json['data'][1]['has_booking_overlaps'] is False
assert resp.json['data'][2]['id'] == 'first-agenda@event-12-14:2'
assert resp.json['data'][2]['has_booking_overlaps'] is False
lguerin marked this conversation as resolved Outdated

Tu pourrais ajouter un test avec une réservation posée sur une occurence de agenda 1 ? Pour qu'on sache si ça remonte bien en has_booking_overlaps ?

Tu pourrais ajouter un test avec une réservation posée sur une occurence de agenda 1 ? Pour qu'on sache si ça remonte bien en has_booking_overlaps ?

OK mais ça ne fonctionne pas comme ça, le test montre que ça ne remonte pas en has_booking_overlap (ce qui me paraît logique, si on permet de réserver les occurrences de l'évènement A et qu'il se trouve qu'une est déjà réservée, on n'a pas envie d'afficher de message « attention tout ne pourra pas etre réservé »).

OK mais ça ne fonctionne pas comme ça, le test montre que ça ne remonte pas en has_booking_overlap (ce qui me paraît logique, si on permet de réserver les occurrences de l'évènement A et qu'il se trouve qu'une est déjà réservée, on n'a pas envie d'afficher de message « attention tout ne pourra pas etre réservé »).

oui ok, logique

oui ok, logique
# create one booking on second day
event = Event.objects.get(start_datetime__date='2021-09-22', primary_event=event_13_15)
Booking.objects.create(event=event, user_external_id='user_id')
resp = app.get('/api/agendas/recurring-events/', params=params)
assert len(resp.json['data']) == 3
assert resp.json['data'][0]['id'] == 'first-agenda@event-12-14:1'
assert resp.json['data'][0]['has_booking_overlaps'] is True
assert resp.json['data'][1]['id'] == 'first-agenda@event-15-16:1'
assert resp.json['data'][1]['has_booking_overlaps'] is False
assert resp.json['data'][2]['id'] == 'first-agenda@event-12-14:2'
assert resp.json['data'][2]['has_booking_overlaps'] is True
# create one booking on first agenda
event = Event.objects.get(start_datetime__date='2021-09-21', primary_event=event_15_16)
Booking.objects.create(event=event, user_external_id='user_id')
resp = app.get('/api/agendas/recurring-events/', params=params)
assert len(resp.json['data']) == 3
assert resp.json['data'][0]['id'] == 'first-agenda@event-12-14:1'
assert resp.json['data'][0]['has_booking_overlaps'] is True
assert resp.json['data'][1]['id'] == 'first-agenda@event-15-16:1'
assert resp.json['data'][1]['has_booking_overlaps'] is False # event is not marked as overlapping
assert resp.json['data'][2]['id'] == 'first-agenda@event-12-14:2'
assert resp.json['data'][2]['has_booking_overlaps'] is True
# check date start
resp = app.get('/api/agendas/recurring-events/', params={'date_start': '2021-09-23', **params})
assert len(resp.json['data']) == 3
assert not any(x['has_booking_overlaps'] for x in resp.json['data'])
# check date end
resp = app.get('/api/agendas/recurring-events/', params={'date_end': '2021-09-20', **params})
assert len(resp.json['data']) == 3
assert not any(x['has_booking_overlaps'] for x in resp.json['data'])
# disable overlap check with second agenda
params['check_overlaps'] = 'first-agenda'
resp = app.get('/api/agendas/recurring-events/', params=params)
assert len(resp.json['data']) == 3
assert not any(x['has_booking_overlaps'] for x in resp.json['data'])

View File

@ -1288,11 +1288,11 @@ def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user):
'slots': events_to_book,
'user_external_id': 'user',
'include_booked_events_detail': True,
'check_overlaps': True,
'check_overlaps': agenda_slugs,
},
)
assert resp.json['booking_count'] == 180
assert len(ctx.captured_queries) == 14
assert len(ctx.captured_queries) == 15
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')
@ -1483,7 +1483,7 @@ def test_recurring_events_api_fillslots_overlapping_events(app, user):
# booking without overlap
params = {
'user_external_id': 'user_id',
'check_overlaps': True,
'check_overlaps': 'first-agenda,second-agenda',
'slots': 'first-agenda@event-12-14:1,first-agenda@event-14-15:1,second-agenda@event-12-18:5',
}
resp = app.post_json(fillslots_url % ('update', 'first-agenda,second-agenda'), params=params)
@ -1494,7 +1494,11 @@ def test_recurring_events_api_fillslots_overlapping_events(app, user):
assert resp.json['booking_count'] == 0
# change bookings
params = {'user_external_id': 'user_id', 'check_overlaps': True, 'slots': 'second-agenda@event-12-18:1'}
params = {
'user_external_id': 'user_id',
'check_overlaps': 'first-agenda,second-agenda',
'slots': 'second-agenda@event-12-18:1',
}
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
@ -1502,7 +1506,7 @@ def test_recurring_events_api_fillslots_overlapping_events(app, user):
# booking overlapping events is allowed if one has no duration
params = {
'user_external_id': 'user_id',
'check_overlaps': True,
'check_overlaps': 'first-agenda,second-agenda',
'slots': 'second-agenda@event-12-18:5,second-agenda@no-duration:5',
}
resp = app.post_json(fillslots_url % ('update', 'first-agenda,second-agenda'), params=params)
@ -1512,7 +1516,7 @@ def test_recurring_events_api_fillslots_overlapping_events(app, user):
# booking overlapping events with durations is forbidden
params = {
'user_external_id': 'user_id',
'check_overlaps': True,
'check_overlaps': 'first-agenda,second-agenda',
'slots': 'first-agenda@event-12-14:1,second-agenda@event-12-18:1',
}
resp = app.post_json(fillslots_url % ('update', 'first-agenda,second-agenda'), params=params)
@ -1524,7 +1528,7 @@ def test_recurring_events_api_fillslots_overlapping_events(app, user):
params = {
'user_external_id': 'user_id',
'check_overlaps': True,
'check_overlaps': 'first-agenda,second-agenda',
'slots': (
'first-agenda@event-12-14:1,first-agenda@event-15-17:1,first-agenda@event-15-17:3,first-agenda@event-15-17:5,second-agenda@event-12-18:1,'
'second-agenda@event-12-18:5,second-agenda@no-duration:5'
@ -1545,3 +1549,74 @@ 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['err'] == 0
assert resp.json['booking_count'] == 10
@pytest.mark.freeze_time('2021-09-06 12:00')
def test_recurring_events_api_fillslots_overlapping_events_partial_booking(app, user):
agenda = Agenda.objects.create(label='First Agenda', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
start, end = now(), now() + datetime.timedelta(days=30)
event_12_14 = Event.objects.create(
label='Event 12-14',
start_datetime=start,
duration=120,
places=2,
recurrence_end_date=end + datetime.timedelta(days=7),
recurrence_days=[1],
agenda=agenda,
)
event_12_14.create_all_recurrences()
agenda2 = Agenda.objects.create(label='Second Agenda', kind='events')
Desk.objects.create(agenda=agenda2, slug='_exceptions_holder')
Event.objects.create(
label='Event 13-15',
start_datetime=start + datetime.timedelta(hours=1),
duration=120,
places=2,
recurrence_end_date=end,
recurrence_days=[1],
agenda=agenda2,
).create_all_recurrences()
app.authorization = ('Basic', ('john.doe', 'password'))
fillslots_url = '/api/agendas/recurring-events/fillslots/?action=update&agendas=%s'
# create one booking
event = Event.objects.get(start_datetime__date='2021-09-07', primary_event=event_12_14)
Booking.objects.create(event=event, user_external_id='user_id')
params = {
'user_external_id': 'user_id',
'check_overlaps': 'first-agenda,second-agenda',
'slots': 'second-agenda@event-13-15:1',
'include_booked_events_detail': True,
}
resp = app.post_json(fillslots_url % 'second-agenda', params=params)
assert resp.json['booking_count'] == 4
assert resp.json['cancelled_booking_count'] == 0
assert [x['date'] for x in resp.json['booked_events']] == [
# event 09-07 is not booked
'2021-09-14',
'2021-09-21',
'2021-09-28',
'2021-10-05',
]
params['slots'] = 'first-agenda@event-12-14:1'
resp = app.post_json(fillslots_url % 'first-agenda', params=params)
assert resp.json['booking_count'] == 1
assert resp.json['cancelled_booking_count'] == 0
# event ends later
assert [x['date'] for x in resp.json['booked_events']] == ['2021-10-12']
# disable overlap checking with second agenda
params['check_overlaps'] = 'first-agenda'
resp = app.post_json(fillslots_url % 'first-agenda', params=params)
assert resp.json['booking_count'] == 4
assert resp.json['cancelled_booking_count'] == 0
assert [x['date'] for x in resp.json['booked_events']] == [
'2021-09-14',
'2021-09-21',
'2021-09-28',
'2021-10-05',
]