Revoir la gestion des temps ennemis pour les évènements récurrents (#73904) #32
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
||||
# 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'])
|
||||
|
|
|
@ -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',
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue
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é »).
oui ok, logique