import datetime import pytest from django.db import connection from django.test.utils import CaptureQueriesContext from django.utils.timezone import now from chrono.agendas.models import ( Agenda, Booking, Category, Desk, Event, EventsType, Person, SharedCustodyAgenda, SharedCustodyRule, Subscription, ) pytestmark = pytest.mark.django_db @pytest.mark.parametrize('action', ['book', 'update']) def test_recurring_events_api_fillslots(app, user, freezer, action): freezer.move_to('2021-09-06 12:00') events_type = EventsType.objects.create(label='Foo') agenda = Agenda.objects.create( label='Foo bar', kind='events', minimal_booking_delay=7, maximal_booking_delay=35, # only 4 bookable weeks events_type=events_type, ) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event.objects.create( label='Event', start_datetime=now(), recurrence_days=[0, 1, 3, 4], # Monday, Tuesday, Thursday, Friday places=2, waiting_list_places=1, agenda=agenda, recurrence_end_date=now() + datetime.timedelta(days=14), # 2 weeks ) event.create_all_recurrences() sunday_event = Event.objects.create( label='Sunday Event', start_datetime=now(), recurrence_days=[6], places=2, waiting_list_places=1, agenda=agenda, recurrence_end_date=now() + datetime.timedelta(days=14), # 2 weeks ) sunday_event.create_all_recurrences() resp = app.get('/api/agendas/recurring-events/?agendas=%s' % agenda.slug) assert len(resp.json['data']) == 5 app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = '/api/agendas/recurring-events/fillslots/?agendas=%s&action=%s' % (agenda.slug, action) params = {'user_external_id': 'user_id'} # Book Monday and Thursday of first event and Sunday of second event params['slots'] = 'foo-bar@event:0,foo-bar@event:3,foo-bar@sunday-event:6' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 3 assert 'booked_events' not in resp.json assert Booking.objects.count() == 3 assert Booking.objects.filter(event__primary_event=event).count() == 2 assert Booking.objects.filter(event__primary_event=sunday_event).count() == 1 events = Event.objects.filter(primary_event__isnull=False) assert events.filter(booked_places=1).count() == 3 # remove delays agenda.minimal_booking_delay = 0 agenda.maximal_booking_delay = 0 agenda.save() resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 3 assert 'booked_events' not in resp.json assert Booking.objects.count() == 6 assert Booking.objects.filter(event__primary_event=event).count() == 4 assert Booking.objects.filter(event__primary_event=sunday_event).count() == 2 events = Event.objects.filter(primary_event__isnull=False) assert events.filter(booked_places=1).count() == 6 # one recurrence is booked separately event = Event.objects.filter(primary_event__isnull=False).first() Booking.objects.create(event=event) params['user_external_id'] = 'user_id_2' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 6 assert not resp.json['full_events'] assert Booking.objects.count() == 13 events = Event.objects.filter(primary_event__isnull=False) assert events.filter(booked_places=2).count() == 6 # one booking has been put in waiting list assert events.filter(booked_waiting_list_places=1).count() == 1 params['user_external_id'] = 'user_id_3' with CaptureQueriesContext(connection) as ctx: resp = app.post_json(fillslots_url, params=params) assert len(ctx.captured_queries) in [12, 13] # everything goes in waiting list assert events.filter(booked_waiting_list_places=1).count() == 6 # but an event was full assert resp.json['booking_count'] == 5 assert len(resp.json['full_events']) == 1 assert resp.json['full_events'][0]['slug'] == event.slug resp = app.post_json(fillslots_url, params=params) # events are full params['user_external_id'] = 'user_id_4' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 0 # no event in range resp = app.post_json(fillslots_url + '&date_start=2020-09-13&date_end=2020-09-19', params=params) assert resp.json['booking_count'] == 0 params['slots'] = 'foo-bar@event:1' params['include_booked_events_detail'] = True resp = app.post_json(fillslots_url + '&date_start=2021-09-13&date_end=2021-09-19', params=params) assert resp.json['booking_count'] == 1 assert Booking.objects.filter(user_external_id='user_id_4').count() == 1 assert len(resp.json['booked_events']) == 1 assert resp.json['booked_events'][0]['id'] == 'foo-bar@event--2021-09-14-1400' assert ( resp.json['booked_events'][0]['booking']['id'] == Booking.objects.filter(user_external_id='user_id_4').get().pk ) resp = app.post_json(fillslots_url, params={'slots': 'foo-bar@event:0'}, status=400) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'invalid payload' assert resp.json['errors']['user_external_id'] == ['This field is required.'] resp = app.post_json(fillslots_url, params={'user_external_id': 'a'}, status=400) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'invalid payload' assert resp.json['errors']['slots'] == ['This field is required.'] resp = app.post_json(fillslots_url, params={'user_external_id': 'a', 'slots': 'foo-bar@a:a'}, status=400) assert resp.json['err'] == 1 assert resp.json['errors']['slots'] == ['invalid slot: foo-bar@a:a'] resp = app.post_json(fillslots_url, params={'user_external_id': 'a', 'slots': 'foo-bar@a:1'}, status=400) assert resp.json['err'] == 1 assert resp.json['errors']['slots'] == ['event a of agenda foo-bar is not bookable'] resp = app.post_json(fillslots_url, params={'user_external_id': 'a', 'slots': 'foo-bar'}, status=400) assert resp.json['err'] == 1 assert resp.json['errors']['slots'] == ['Invalid format for slot foo-bar'] missing_action_url = '/api/agendas/recurring-events/fillslots/?agendas=%s' % agenda.slug resp = app.post_json(missing_action_url, params=params, status=400) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'invalid payload' assert resp.json['errors']['action'] == ['This field is required.'] resp = app.post_json(missing_action_url + '&action=invalid', params=params, status=400) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'invalid payload' assert resp.json['errors']['action'] == ['"invalid" is not a valid choice.'] def test_recurring_events_api_fillslots_waiting_list(app, user, freezer): freezer.move_to('2021-09-06 12:00') agenda = Agenda.objects.create( label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=0 ) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event.objects.create( label='Event', start_datetime=now(), recurrence_days=[0], places=2, waiting_list_places=2, agenda=agenda, recurrence_end_date=now() + datetime.timedelta(days=30), ) event.create_all_recurrences() app.authorization = ('Basic', ('john.doe', 'password')) # create bookings in waiting list for recurrence in event.recurrences.all(): Booking.objects.create(event=recurrence, in_waiting_list=True) events = Event.objects.filter(primary_event__isnull=False) assert events.filter(booked_waiting_list_places=1).count() == 5 # check that new bookings are put in waiting list despite free slots on main list params = {'user_external_id': 'user_id', 'slots': 'foo-bar@event:0'} resp = app.post_json( '/api/agendas/recurring-events/fillslots/?agendas=%s&action=update' % agenda.slug, params=params ) assert resp.json['booking_count'] == 5 assert events.filter(booked_waiting_list_places=2).count() == 5 def test_recurring_events_api_fillslots_book_with_cancelled(app, user, freezer): freezer.move_to('2021-09-06 12:00') agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event.objects.create( label='Event', start_datetime=now(), recurrence_days=[0, 1], # Monday, Tuesday places=1, waiting_list_places=1, agenda=agenda, recurrence_end_date=now() + datetime.timedelta(days=21), ) event.create_all_recurrences() # create cancelled bookings for the user event_1_0 = Event.objects.get(primary_event=event, start_datetime=event.start_datetime) booking_1_0 = Booking.objects.create(event=event_1_0, user_external_id='user_id') booking_1_0.cancel() assert booking_1_0.cancellation_datetime is not None event_1_1 = Event.objects.get( primary_event=event, start_datetime=event.start_datetime + datetime.timedelta(days=1) ) booking_1_1 = Booking.objects.create(event=event_1_1, user_external_id='user_id') booking_1_1.cancel() assert booking_1_1.cancellation_datetime is not None # and non cancelled bookings for the user event_2_0 = Event.objects.get( primary_event=event, start_datetime=event.start_datetime + datetime.timedelta(days=7) ) booking_2_0 = Booking.objects.create(event=event_2_0, user_external_id='user_id') assert booking_2_0.cancellation_datetime is None event_2_1 = Event.objects.get( primary_event=event, start_datetime=event.start_datetime + datetime.timedelta(days=8) ) booking_2_1 = Booking.objects.create(event=event_2_1, user_external_id='user_id') assert booking_2_1.cancellation_datetime is None # and bookings for another user Booking.objects.create(event=event_1_0, user_external_id='user_id_foobar') other_booking = Booking.objects.create(event=event_2_0, user_external_id='user_id_foobar') other_booking.cancel() app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = '/api/agendas/recurring-events/fillslots/?agendas=%s&action=book' % agenda.slug params = {'user_external_id': 'user_id'} # Book Monday params['slots'] = 'foo-bar@event:0' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 2 assert resp.json['cancelled_booking_count'] == 0 assert Booking.objects.filter(user_external_id='user_id').count() == 5 assert Booking.objects.filter(user_external_id='user_id', cancellation_datetime__isnull=True).count() == 4 assert Booking.objects.filter(user_external_id='user_id', event__start_datetime__week_day=2).count() == 3 assert ( Booking.objects.filter( user_external_id='user_id', event__start_datetime__week_day=2, cancellation_datetime__isnull=True, ).count() == 3 ) assert Booking.objects.filter(user_external_id='user_id', event__start_datetime__week_day=3).count() == 2 assert ( Booking.objects.filter( user_external_id='user_id', event__start_datetime__week_day=3, cancellation_datetime__isnull=True, ).count() == 1 ) assert Booking.objects.filter(pk=booking_1_0.pk).exists() is False # cancelled booking deleted booking_1_1.refresh_from_db() booking_2_0.refresh_from_db() booking_2_1.refresh_from_db() assert booking_1_1.cancellation_datetime is not None assert booking_2_0.cancellation_datetime is None assert booking_2_1.cancellation_datetime is None # Book Tuesday params['slots'] = 'foo-bar@event:1' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 2 assert resp.json['cancelled_booking_count'] == 0 assert Booking.objects.filter(user_external_id='user_id').count() == 6 assert Booking.objects.filter(user_external_id='user_id', cancellation_datetime__isnull=True).count() == 6 assert Booking.objects.filter(user_external_id='user_id', event__start_datetime__week_day=2).count() == 3 assert ( Booking.objects.filter( user_external_id='user_id', event__start_datetime__week_day=2, cancellation_datetime__isnull=True, ).count() == 3 ) assert Booking.objects.filter(user_external_id='user_id', event__start_datetime__week_day=3).count() == 3 assert ( Booking.objects.filter( user_external_id='user_id', event__start_datetime__week_day=3, cancellation_datetime__isnull=True, ).count() == 3 ) assert Booking.objects.filter(pk=booking_1_0.pk).exists() is False # cancelled booking deleted assert Booking.objects.filter(pk=booking_1_1.pk).exists() is False # cancelled booking deleted booking_2_0.refresh_from_db() booking_2_1.refresh_from_db() assert booking_2_0.cancellation_datetime is None assert booking_2_1.cancellation_datetime is None def test_recurring_events_api_fillslots_update(app, user, freezer): freezer.move_to('2021-09-06 12:00') agenda = Agenda.objects.create( label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=0 ) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event.objects.create( label='Event', start_datetime=now(), recurrence_days=[0, 1, 3, 4], # Monday, Tuesday, Thursday, Friday places=1, waiting_list_places=1, agenda=agenda, recurrence_end_date=now() + datetime.timedelta(days=28), # 4 weeks ) event.create_all_recurrences() app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = '/api/agendas/recurring-events/fillslots/?agendas=%s&action=update' % agenda.slug params = {'user_external_id': 'user_id'} # Book Monday and Thursday params['slots'] = 'foo-bar@event:0,foo-bar@event:3' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 8 assert resp.json['cancelled_booking_count'] == 0 assert Booking.objects.count() == 8 assert Booking.objects.filter(event__start_datetime__week_day=2).count() == 4 assert Booking.objects.filter(event__start_datetime__week_day=5).count() == 4 # Book Friday without changing other bookings params['slots'] = 'foo-bar@event:4' resp = app.post_json(fillslots_url.replace('update', 'book'), params=params) assert resp.json['booking_count'] == 4 assert resp.json['cancelled_booking_count'] == 0 assert Booking.objects.count() == 12 assert Booking.objects.filter(event__start_datetime__week_day=2).count() == 4 assert Booking.objects.filter(event__start_datetime__week_day=5).count() == 4 assert Booking.objects.filter(event__start_datetime__week_day=6).count() == 4 # set booking delays - only 2 weeks bookable agenda.minimal_booking_delay = 7 agenda.maximal_booking_delay = 21 agenda.save() # Change booking to Monday and Tuesday params['slots'] = 'foo-bar@event:0,foo-bar@event:1' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 2 assert resp.json['cancelled_booking_count'] == 4 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 10 assert ( Booking.objects.filter( event__start_datetime__week_day=2, cancellation_datetime__isnull=True, ).count() == 4 ) assert ( Booking.objects.filter( event__start_datetime__week_day=3, cancellation_datetime__isnull=True, ).count() == 2 ) assert ( Booking.objects.filter( event__start_datetime__week_day=5, cancellation_datetime__isnull=True, ).count() == 2 ) assert ( Booking.objects.filter( event__start_datetime__week_day=6, cancellation_datetime__isnull=True, ).count() == 2 ) # remove delays agenda.minimal_booking_delay = 0 agenda.maximal_booking_delay = 0 agenda.save() resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 2 assert resp.json['cancelled_booking_count'] == 4 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 8 assert ( Booking.objects.filter( event__start_datetime__week_day=2, cancellation_datetime__isnull=True, ).count() == 4 ) assert ( Booking.objects.filter( event__start_datetime__week_day=3, cancellation_datetime__isnull=True, ).count() == 4 ) # Booking again does nothing resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 0 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 8 params = {'user_external_id': 'user_id_2'} params['slots'] = 'foo-bar@event:0,foo-bar@event:3' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 8 assert resp.json['cancelled_booking_count'] == 0 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 16 assert ( Booking.objects.filter( event__start_datetime__week_day=2, cancellation_datetime__isnull=True, ).count() == 8 ) assert ( Booking.objects.filter( event__start_datetime__week_day=3, cancellation_datetime__isnull=True, ).count() == 4 ) assert ( Booking.objects.filter( event__start_datetime__week_day=5, cancellation_datetime__isnull=True, ).count() == 4 ) events = Event.objects.filter(primary_event__isnull=False) assert events.filter(booked_places=1).count() == 12 assert events.filter(booked_waiting_list_places=1).count() == 4 params['slots'] = 'foo-bar@event:1,foo-bar@event:4' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 8 assert resp.json['cancelled_booking_count'] == 8 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 16 assert ( Booking.objects.filter( event__start_datetime__week_day=2, cancellation_datetime__isnull=True, ).count() == 4 ) assert ( Booking.objects.filter( event__start_datetime__week_day=3, cancellation_datetime__isnull=True, ).count() == 8 ) assert ( Booking.objects.filter( event__start_datetime__week_day=6, cancellation_datetime__isnull=True, ).count() == 4 ) events = Event.objects.filter(primary_event__isnull=False) assert events.filter(booked_places=1).count() == 12 assert events.filter(booked_waiting_list_places=1).count() == 4 # specifying end date cancels some bookings resp = app.post_json(fillslots_url + '&date_end=2021-09-26', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 2 assert ( Booking.objects.filter( user_external_id='user_id_2', cancellation_datetime__isnull=True, ).count() == 6 ) # past bookings are left in place freezer.move_to('2021-09-13 12:00') resp = app.post_json(fillslots_url + '&date_start=2021-09-06&date_end=2021-09-19', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 2 # only week from 13 to 19 of septembre was cancelled assert ( Booking.objects.filter( user_external_id='user_id_2', cancellation_datetime__isnull=True, ).count() == 4 ) # passing empty slots cancels all bookings params['slots'] = '' resp = app.post_json(fillslots_url, params=params) assert resp.json['cancelled_booking_count'] == 2 assert ( Booking.objects.filter( user_external_id='user_id_2', cancellation_datetime__isnull=True, ).count() == 2 ) assert ( Booking.objects.filter( user_external_id='user_id_2', event__start_datetime__gt=now(), cancellation_datetime__isnull=True, ).count() == 0 ) # only recurring events are impacted normal_event = Event.objects.create( start_datetime=now() + datetime.timedelta(days=1), places=2, agenda=agenda ) Booking.objects.create(event=normal_event, user_external_id='user_id') resp = app.post_json(fillslots_url, params={'user_external_id': 'user_id', 'slots': 'foo-bar@event:0'}) assert resp.json['cancelled_booking_count'] == 3 assert Booking.objects.filter(user_external_id='user_id', event=normal_event).count() == 1 def test_recurring_events_api_fillslots_update_with_cancelled(app, user, freezer): freezer.move_to('2021-09-06 12:00') agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event.objects.create( label='Event', start_datetime=now(), recurrence_days=[0, 1], # Monday, Tuesday places=1, waiting_list_places=1, agenda=agenda, recurrence_end_date=now() + datetime.timedelta(days=21), ) event.create_all_recurrences() # create cancelled bookings for the user event_1_0 = Event.objects.get(primary_event=event, start_datetime=event.start_datetime) booking_1_0 = Booking.objects.create(event=event_1_0, user_external_id='user_id') booking_1_0.cancel() assert booking_1_0.cancellation_datetime is not None event_1_1 = Event.objects.get( primary_event=event, start_datetime=event.start_datetime + datetime.timedelta(days=1) ) booking_1_1 = Booking.objects.create(event=event_1_1, user_external_id='user_id') booking_1_1.cancel() assert booking_1_1.cancellation_datetime is not None # and non cancelled bookings for the user event_2_0 = Event.objects.get( primary_event=event, start_datetime=event.start_datetime + datetime.timedelta(days=7) ) booking_2_0 = Booking.objects.create(event=event_2_0, user_external_id='user_id') assert booking_2_0.cancellation_datetime is None event_2_1 = Event.objects.get( primary_event=event, start_datetime=event.start_datetime + datetime.timedelta(days=8) ) booking_2_1 = Booking.objects.create(event=event_2_1, user_external_id='user_id') assert booking_2_1.cancellation_datetime is None # secondary booking for this one booking_2_1_secondary = Booking.objects.create(event=event_2_1, primary_booking=booking_2_1) # and bookings for another user Booking.objects.create(event=event_1_0, user_external_id='user_id_foobar') other_booking = Booking.objects.create(event=event_2_0, user_external_id='user_id_foobar') other_booking.cancel() app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = '/api/agendas/recurring-events/fillslots/?agendas=%s&action=update' % agenda.slug params = {'user_external_id': 'user_id'} # Book Monday params['slots'] = 'foo-bar@event:0' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 2 assert resp.json['cancelled_booking_count'] == 1 assert Booking.objects.filter(user_external_id='user_id').count() == 5 assert Booking.objects.filter(user_external_id='user_id', cancellation_datetime__isnull=True).count() == 3 assert Booking.objects.filter(user_external_id='user_id', event__start_datetime__week_day=2).count() == 3 assert ( Booking.objects.filter( user_external_id='user_id', event__start_datetime__week_day=2, cancellation_datetime__isnull=True, ).count() == 3 ) assert ( Booking.objects.filter(event__start_datetime__week_day=3, primary_booking__isnull=True).count() == 2 ) assert ( Booking.objects.filter( user_external_id='user_id', event__start_datetime__week_day=3, cancellation_datetime__isnull=True, ).count() == 0 ) assert Booking.objects.filter(pk=booking_1_0.pk).exists() is False # cancelled booking deleted booking_1_1.refresh_from_db() booking_2_0.refresh_from_db() booking_2_1.refresh_from_db() booking_2_1_secondary.refresh_from_db() assert booking_1_1.cancellation_datetime is not None assert booking_2_0.cancellation_datetime is None assert booking_2_1.cancellation_datetime is not None assert booking_2_1_secondary.cancellation_datetime is not None # Book Tuesday params['slots'] = 'foo-bar@event:1' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 3 assert resp.json['cancelled_booking_count'] == 3 assert Booking.objects.filter(user_external_id='user_id').count() == 6 assert Booking.objects.filter(user_external_id='user_id', cancellation_datetime__isnull=True).count() == 3 assert Booking.objects.filter(user_external_id='user_id', event__start_datetime__week_day=2).count() == 3 assert ( Booking.objects.filter( user_external_id='user_id', event__start_datetime__week_day=2, cancellation_datetime__isnull=True, ).count() == 0 ) assert Booking.objects.filter(user_external_id='user_id', event__start_datetime__week_day=3).count() == 3 assert ( Booking.objects.filter( user_external_id='user_id', event__start_datetime__week_day=3, cancellation_datetime__isnull=True, ).count() == 3 ) assert Booking.objects.filter(pk=booking_1_0.pk).exists() is False # cancelled booking deleted assert Booking.objects.filter(pk=booking_1_1.pk).exists() is False # cancelled booking deleted assert Booking.objects.filter(pk=booking_2_1.pk).exists() is False # cancelled booking deleted assert Booking.objects.filter(pk=booking_2_1_secondary.pk).exists() is False # cancelled booking deleted booking_2_0.refresh_from_db() assert booking_2_0.cancellation_datetime is not None def test_recurring_events_api_fillslots_unbook(app, user, freezer): freezer.move_to('2021-09-06 12:00') agenda = Agenda.objects.create( label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=0 ) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event.objects.create( label='Event', start_datetime=now(), recurrence_days=[0, 1, 3, 4], # Monday, Tuesday, Thursday, Friday places=2, waiting_list_places=1, agenda=agenda, recurrence_end_date=now() + datetime.timedelta(days=28), # 4 weeks ) event.create_all_recurrences() sunday_event = Event.objects.create( label='Sunday Event', start_datetime=now(), recurrence_days=[6], places=2, waiting_list_places=1, agenda=agenda, recurrence_end_date=now() + datetime.timedelta(days=28), # 4 weeks ) sunday_event.create_all_recurrences() app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = '/api/agendas/recurring-events/fillslots/?agendas=%s' % agenda.slug params = {'user_external_id': 'user_id'} params['slots'] = 'foo-bar@event:0,foo-bar@event:3,foo-bar@sunday-event:6' resp = app.post_json(fillslots_url + '&action=book', params=params) assert resp.json['booking_count'] == 12 assert Booking.objects.count() == 12 assert Booking.objects.filter(event__primary_event=event).count() == 8 assert Booking.objects.filter(event__primary_event=sunday_event).count() == 4 # set booking delays - only 2 weeks bookable agenda.minimal_booking_delay = 7 agenda.maximal_booking_delay = 21 agenda.save() params['slots'] = 'foo-bar@event:0' resp = app.post_json(fillslots_url + '&action=unbook', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 2 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 10 assert ( Booking.objects.filter( event__primary_event=event, cancellation_datetime__isnull=True, ).count() == 6 ) assert ( Booking.objects.filter( event__primary_event=sunday_event, cancellation_datetime__isnull=True, ).count() == 4 ) # remove delays agenda.minimal_booking_delay = 0 agenda.maximal_booking_delay = 0 agenda.save() resp = app.post_json(fillslots_url + '&action=unbook', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 2 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 8 assert ( Booking.objects.filter( event__primary_event=event, cancellation_datetime__isnull=True, ).count() == 4 ) assert ( Booking.objects.filter( event__primary_event=event, cancellation_datetime__isnull=False, ).count() == 4 ) assert ( Booking.objects.filter( event__primary_event=sunday_event, cancellation_datetime__isnull=True, ).count() == 4 ) params['slots'] = 'foo-bar@sunday-event:6' resp = app.post_json( fillslots_url + '&action=unbook&date_start=2021-09-13&date_end=2021-09-20', params=params ) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 1 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 7 assert ( Booking.objects.filter( event__primary_event=event, cancellation_datetime__isnull=True, ).count() == 4 ) assert ( Booking.objects.filter( event__primary_event=event, cancellation_datetime__isnull=False, ).count() == 4 ) assert ( Booking.objects.filter( event__primary_event=sunday_event, cancellation_datetime__isnull=True, ).count() == 3 ) assert ( Booking.objects.filter( event__primary_event=sunday_event, cancellation_datetime__isnull=False, ).count() == 1 ) assert not Booking.objects.filter( event__primary_event=sunday_event, event__start_datetime__range=( datetime.date(year=2021, month=9, day=13), datetime.date(year=2021, month=9, day=20), ), cancellation_datetime__isnull=True, ).exists() freezer.move_to('2021-09-13 12:00') # old bookings are not unbooked params['slots'] = 'foo-bar@event:3' resp = app.post_json(fillslots_url + '&action=unbook', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 3 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 4 assert ( Booking.objects.filter( event__primary_event=event, cancellation_datetime__isnull=True, ).count() == 1 ) assert not Booking.objects.filter( event__primary_event=event, event__start_datetime__gt=datetime.date(year=2021, month=9, day=13), cancellation_datetime__isnull=True, ).exists() assert ( Booking.objects.filter( event__primary_event=sunday_event, cancellation_datetime__isnull=True, ).count() == 3 ) # unbooking when there are no bookings does nothing params['user_external_id'] = 'user_id_2' resp = app.post_json(fillslots_url + '&action=unbook', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 0 def test_recurring_events_api_fillslots_unbook_with_cancelled(app, user, freezer): freezer.move_to('2021-09-06 12:00') agenda = Agenda.objects.create(label='Foo bar', kind='events') Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event.objects.create( label='Event', start_datetime=now(), recurrence_days=[0, 1], # Monday, Tuesday places=1, waiting_list_places=1, agenda=agenda, recurrence_end_date=now() + datetime.timedelta(days=21), ) event.create_all_recurrences() # create cancelled bookings for the user event_1_0 = Event.objects.get(primary_event=event, start_datetime=event.start_datetime) booking_1_0 = Booking.objects.create(event=event_1_0, user_external_id='user_id') booking_1_0.cancel() assert booking_1_0.cancellation_datetime is not None event_1_1 = Event.objects.get( primary_event=event, start_datetime=event.start_datetime + datetime.timedelta(days=1) ) booking_1_1 = Booking.objects.create(event=event_1_1, user_external_id='user_id') booking_1_1.cancel() assert booking_1_1.cancellation_datetime is not None # and non cancelled bookings for the user event_2_0 = Event.objects.get( primary_event=event, start_datetime=event.start_datetime + datetime.timedelta(days=7) ) booking_2_0 = Booking.objects.create(event=event_2_0, user_external_id='user_id') assert booking_2_0.cancellation_datetime is None event_2_1 = Event.objects.get( primary_event=event, start_datetime=event.start_datetime + datetime.timedelta(days=8) ) booking_2_1 = Booking.objects.create(event=event_2_1, user_external_id='user_id') assert booking_2_1.cancellation_datetime is None # and bookings for another user Booking.objects.create(event=event_1_0, user_external_id='user_id_foobar') other_booking = Booking.objects.create(event=event_2_0, user_external_id='user_id_foobar') other_booking.cancel() app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = '/api/agendas/recurring-events/fillslots/?agendas=%s&action=unbook' % agenda.slug params = {'user_external_id': 'user_id'} # unbook Monday params['slots'] = 'foo-bar@event:0' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 1 assert Booking.objects.filter(user_external_id='user_id').count() == 4 assert Booking.objects.filter(user_external_id='user_id', cancellation_datetime__isnull=True).count() == 1 assert Booking.objects.filter(user_external_id='user_id', event__start_datetime__week_day=2).count() == 2 assert ( Booking.objects.filter( user_external_id='user_id', event__start_datetime__week_day=2, cancellation_datetime__isnull=True, ).count() == 0 ) assert Booking.objects.filter(user_external_id='user_id', event__start_datetime__week_day=3).count() == 2 assert ( Booking.objects.filter( user_external_id='user_id', event__start_datetime__week_day=3, cancellation_datetime__isnull=True, ).count() == 1 ) booking_1_0.refresh_from_db() booking_1_1.refresh_from_db() booking_2_0.refresh_from_db() booking_2_1.refresh_from_db() assert booking_1_0.cancellation_datetime is not None assert booking_1_1.cancellation_datetime is not None assert booking_2_0.cancellation_datetime is not None assert booking_2_1.cancellation_datetime is None # unbook Tuesday params['slots'] = 'foo-bar@event:1' resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 1 assert Booking.objects.filter(user_external_id='user_id').count() == 4 assert Booking.objects.filter(user_external_id='user_id', cancellation_datetime__isnull=True).count() == 0 assert Booking.objects.filter(user_external_id='user_id', event__start_datetime__week_day=2).count() == 2 assert ( Booking.objects.filter( user_external_id='user_id', event__start_datetime__week_day=2, cancellation_datetime__isnull=True, ).count() == 0 ) assert Booking.objects.filter(user_external_id='user_id', event__start_datetime__week_day=3).count() == 2 assert ( Booking.objects.filter( user_external_id='user_id', event__start_datetime__week_day=3, cancellation_datetime__isnull=True, ).count() == 0 ) booking_1_0.refresh_from_db() booking_1_1.refresh_from_db() booking_2_0.refresh_from_db() booking_2_1.refresh_from_db() assert booking_1_0.cancellation_datetime is not None assert booking_1_1.cancellation_datetime is not None assert booking_2_0.cancellation_datetime is not None assert booking_2_1.cancellation_datetime is not None @pytest.mark.freeze_time('2021-09-06 12:00') def test_recurring_events_api_fillslots_subscribed(app, user): category = Category.objects.create(label='Category A') first_agenda = Agenda.objects.create( label='First agenda', kind='events', category=category, minimal_booking_delay=0, maximal_booking_delay=0, ) Desk.objects.create(agenda=first_agenda, slug='_exceptions_holder') category = Category.objects.create(label='Category B') second_agenda = Agenda.objects.create( label='Second agenda', kind='events', category=category, minimal_booking_delay=0, maximal_booking_delay=0, ) Desk.objects.create(agenda=second_agenda, slug='_exceptions_holder') event = Event.objects.create( slug='event', start_datetime=now(), recurrence_days=[0, 1, 3, 4], # Monday, Tuesday, Thursday, Friday places=2, waiting_list_places=1, agenda=first_agenda, recurrence_end_date=now() + datetime.timedelta(days=364), ) event.create_all_recurrences() sunday_event = Event.objects.create( slug='sunday-event', start_datetime=now(), recurrence_days=[6], places=2, waiting_list_places=1, agenda=second_agenda, recurrence_end_date=now() + datetime.timedelta(days=364), ) sunday_event.create_all_recurrences() subscription = Subscription.objects.create( agenda=first_agenda, user_external_id='xxx', date_start=now() + datetime.timedelta(days=14), # Monday 20/09 date_end=now() + datetime.timedelta(days=45), # Thursday 21/10 ) app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = '/api/agendas/recurring-events/fillslots/?action=update&subscribed=%s' params = {'user_external_id': 'xxx'} # book Monday and Thursday of first event, in subscription range params['slots'] = 'first-agenda@event:0,first-agenda@event:3' resp = app.post_json(fillslots_url % 'category-a', params=params) assert resp.json['booking_count'] == 9 assert Booking.objects.count() == 9 assert Booking.objects.order_by('event__start_datetime').filter(event__primary_event=event).count() == 9 assert ( Booking.objects.order_by('event__start_datetime').first().event.start_datetime.strftime('%d/%m') == '20/09' ) # first subscription's day assert Booking.objects.last().event.start_datetime.strftime('%d/%m') == '18/10' # last subscription's day subscription.date_end = now() + datetime.timedelta(days=46) # Friday 22/10 subscription.save() resp = app.post_json(fillslots_url % 'category-a', params=params) assert resp.json['booking_count'] == 1 assert Booking.objects.count() == 10 assert Booking.objects.filter(event__primary_event=event).count() == 10 assert ( Booking.objects.order_by('event__start_datetime').first().event.start_datetime.strftime('%d/%m') == '20/09' ) # first subscription's day assert ( Booking.objects.order_by('event__start_datetime').last().event.start_datetime.strftime('%d/%m') == '21/10' ) # last subscription's day # wrong category resp = app.post_json(fillslots_url % 'category-b', params=params, status=400) # book Monday and Thursday of first event, in subscription range, but with date_start and date_end params resp = app.post_json( fillslots_url % 'category-a' + '&date_start=2021-09-21&date_end=2021-10-21', params=params ) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 2 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 8 assert ( Booking.objects.filter(cancellation_datetime__isnull=True) .order_by('event__start_datetime') .first() .event.start_datetime.strftime('%d/%m') == '23/09' ) # first subscription's day assert ( Booking.objects.filter(cancellation_datetime__isnull=True) .order_by('event__start_datetime') .last() .event.start_datetime.strftime('%d/%m') == '18/10' ) resp = app.post_json( fillslots_url % 'category-a' + '&date_start=2021-09-01&date_end=2021-10-31', params=params ) assert resp.json['booking_count'] == 2 assert resp.json['cancelled_booking_count'] == 0 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 10 assert ( Booking.objects.filter(cancellation_datetime__isnull=True) .order_by('event__start_datetime') .first() .event.start_datetime.strftime('%d/%m') == '20/09' ) # first subscription's day assert ( Booking.objects.filter(cancellation_datetime__isnull=True) .order_by('event__start_datetime') .last() .event.start_datetime.strftime('%d/%m') == '21/10' ) # not subscribed category params['slots'] = 'second-agenda@sunday-event:6' resp = app.post_json(fillslots_url % 'category-b', params=params, status=400) # update bookings Subscription.objects.create( agenda=second_agenda, user_external_id='xxx', date_start=now() + datetime.timedelta(days=100), # Wednesday 15/12 date_end=now() + datetime.timedelta(days=150), # Thursday 03/02 ) params['slots'] = 'first-agenda@event:1,second-agenda@sunday-event:6' resp = app.post_json(fillslots_url % 'all', params=params) assert resp.json['booking_count'] == 12 assert resp.json['cancelled_booking_count'] == 10 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 12 booked_events_first_agenda = Event.objects.filter( primary_event=event, booking__isnull=False, booking__cancellation_datetime__isnull=True, ) assert [ x.strftime('%d/%m/%Y') for x in booked_events_first_agenda.values_list('start_datetime', flat=True) ] == ['21/09/2021', '28/09/2021', '05/10/2021', '12/10/2021', '19/10/2021'] booked_events_second_agenda = Event.objects.filter( primary_event=sunday_event, booking__isnull=False, booking__cancellation_datetime__isnull=True, ) assert [ x.strftime('%d/%m/%Y') for x in booked_events_second_agenda.values_list('start_datetime', flat=True) ] == ['19/12/2021', '26/12/2021', '02/01/2022', '09/01/2022', '16/01/2022', '23/01/2022', '30/01/2022'] # other user Subscription.objects.create( agenda=second_agenda, user_external_id='yyy', date_start=now(), date_end=now() + datetime.timedelta(days=10), ) # disjoint subscription Subscription.objects.create( agenda=second_agenda, user_external_id='yyy', date_start=now() + datetime.timedelta(days=60), date_end=now() + datetime.timedelta(days=70), ) params = {'user_external_id': 'yyy', 'slots': 'second-agenda@sunday-event:6'} resp = app.post_json(fillslots_url % 'category-b', params=params) assert resp.json['booking_count'] == 3 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 15 booked_events_user_yyy = Event.objects.filter( primary_event=sunday_event, booking__user_external_id='yyy', booking__cancellation_datetime__isnull=True, ) assert [ x.strftime('%d/%m/%Y') for x in booked_events_user_yyy.values_list('start_datetime', flat=True) ] == ['12/09/2021', '07/11/2021', '14/11/2021'] @pytest.mark.freeze_time('2021-09-06 12:00') def test_recurring_events_api_fillslots_multiple_agendas(app, user): agenda = Agenda.objects.create( label='First Agenda', kind='events', minimal_booking_delay=0, maximal_booking_delay=0 ) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') start, end = now(), now() + datetime.timedelta(days=30) event_a = Event.objects.create( label='A', start_datetime=start, places=2, recurrence_end_date=end, recurrence_days=[0, 2, 5], agenda=agenda, ) event_a.create_all_recurrences() event_b = Event.objects.create( label='B', start_datetime=start, places=2, recurrence_end_date=end, recurrence_days=[1], agenda=agenda ) event_b.create_all_recurrences() agenda2 = Agenda.objects.create(label='Second Agenda', kind='events') Desk.objects.create(agenda=agenda2, slug='_exceptions_holder') event_c = Event.objects.create( label='C', start_datetime=start, places=2, recurrence_end_date=end, recurrence_days=[2, 3], agenda=agenda2, ) event_c.create_all_recurrences() resp = app.get('/api/agendas/recurring-events/?agendas=first-agenda,second-agenda') assert len(resp.json['data']) == 6 app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = '/api/agendas/recurring-events/fillslots/?action=%s&agendas=%s' params = {'user_external_id': 'user_id', 'slots': 'first-agenda@a:0,first-agenda@a:5,second-agenda@c:3'} resp = app.post_json(fillslots_url % ('update', 'first-agenda,second-agenda'), params=params) assert resp.json['booking_count'] == 13 assert Booking.objects.count() == 13 assert Booking.objects.filter(event__primary_event=event_a).count() == 9 assert Booking.objects.filter(event__primary_event=event_b).count() == 0 assert Booking.objects.filter(event__primary_event=event_c).count() == 4 # add bookings params = {'user_external_id': 'user_id', 'slots': 'first-agenda@a:2,second-agenda@c:2'} resp = app.post_json(fillslots_url % ('book', 'first-agenda,second-agenda'), params=params) assert resp.json['booking_count'] == 8 assert resp.json['cancelled_booking_count'] == 0 assert Booking.objects.count() == 21 assert Booking.objects.filter(event__primary_event=event_a).count() == 13 assert Booking.objects.filter(event__primary_event=event_b).count() == 0 assert Booking.objects.filter(event__primary_event=event_c).count() == 8 # unbook last week bookings params = {'user_external_id': 'user_id', 'slots': 'first-agenda@a:2,second-agenda@c:2'} date_start_param = '&date_start=%s' % (end - datetime.timedelta(days=7)).strftime('%Y-%m-%d') resp = app.post_json( (fillslots_url % ('unbook', 'first-agenda,second-agenda')) + date_start_param, params=params ) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 2 assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 19 assert ( Booking.objects.filter( event__primary_event=event_a, cancellation_datetime__isnull=True, ).count() == 12 ) assert ( Booking.objects.filter( event__primary_event=event_b, cancellation_datetime__isnull=True, ).count() == 0 ) assert ( Booking.objects.filter( event__primary_event=event_c, cancellation_datetime__isnull=True, ).count() == 7 ) # update bookings params = {'user_external_id': 'user_id', 'slots': 'first-agenda@b: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'] == 19 assert ( Booking.objects.filter( event__primary_event=event_a, cancellation_datetime__isnull=True, ).count() == 0 ) assert ( Booking.objects.filter( event__primary_event=event_b, cancellation_datetime__isnull=True, ).count() == 5 ) assert ( Booking.objects.filter( event__primary_event=event_c, cancellation_datetime__isnull=True, ).count() == 0 ) # error if slot's agenda is not in querystring resp = app.post_json(fillslots_url % ('update', 'second-agenda'), params=params, status=400) assert resp.json['err'] == 1 assert resp.json['errors']['slots'] == [ 'Events from the following agendas cannot be booked: first-agenda' ] @pytest.mark.freeze_time('2021-09-06 12:00') def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user): events_type = EventsType.objects.create(label='Foo') for i in range(20): agenda = Agenda.objects.create(slug=f'{i}', kind='events', events_type=events_type) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') start, end = now(), now() + datetime.timedelta(days=30) event = Event.objects.create( start_datetime=start, places=2, recurrence_end_date=end, recurrence_days=[1, 2], agenda=agenda ) event.create_all_recurrences() agenda_slugs = ','.join(str(i) for i in range(20)) resp = app.get('/api/agendas/recurring-events/?action=update&agendas=%s' % agenda_slugs) events_to_book = [x['id'] for x in resp.json['data']] app.authorization = ('Basic', ('john.doe', 'password')) 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, 'user_external_id': 'user', 'include_booked_events_detail': True, 'check_overlaps': True, }, ) assert resp.json['booking_count'] == 180 assert len(ctx.captured_queries) == 14 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') agenda = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=now() ) SharedCustodyRule.objects.create(agenda=agenda, guardian=father, days=list(range(7)), weeks='even') SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, days=list(range(7)), weeks='odd') with CaptureQueriesContext(connection) as ctx: resp = app.post_json( '/api/agendas/recurring-events/fillslots/?action=update&agendas=%s&guardian_external_id=father_id' % agenda_slugs, params={'slots': events_to_book, 'user_external_id': 'xxx'}, ) assert resp.json['booking_count'] == 100 assert len(ctx.captured_queries) == 14 @pytest.mark.freeze_time('2022-03-07 14:00') # Monday of 10th week def test_recurring_events_api_fillslots_shared_custody(app, user, freezer): agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0) Desk.objects.create(agenda=agenda, slug='_exceptions_holder') event = Event.objects.create( label='Event', start_datetime=now(), recurrence_days=list(range(7)), places=2, waiting_list_places=1, agenda=agenda, recurrence_end_date=now() + datetime.timedelta(days=14), # 2 weeks ) event.create_all_recurrences() 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='child_id', first_name='James', last_name='Doe') agenda = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=now() ) SharedCustodyRule.objects.create(agenda=agenda, guardian=father, weeks='odd', days=[0, 1, 2]) SharedCustodyRule.objects.create(agenda=agenda, guardian=father, weeks='even', days=[3, 4, 5]) SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, weeks='even', days=[0, 1, 2]) SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, weeks='odd', days=[3, 4, 5]) SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, days=[6]) app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = ( '/api/agendas/recurring-events/fillslots/?agendas=foo-bar&action=update&guardian_external_id=%s' ) params = { 'user_external_id': 'child_id', 'slots': ','.join('foo-bar@event:%s' % i for i in range(7)), # book every days 'include_booked_events_detail': True, } resp = app.post_json(fillslots_url % 'father_id', params=params) assert resp.json['booking_count'] == 6 assert [x['date'] for x in resp.json['booked_events']] == [ '2022-03-10', '2022-03-11', '2022-03-12', '2022-03-14', '2022-03-15', '2022-03-16', ] resp = app.post_json(fillslots_url % 'mother_id', params=params) assert resp.json['booking_count'] == 8 assert [x['date'] for x in resp.json['booked_events']] == [ '2022-03-07', '2022-03-08', '2022-03-09', '2022-03-13', '2022-03-17', '2022-03-18', '2022-03-19', '2022-03-20', ] # give father full custody from 14/03/2022 agenda2 = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=datetime.date(year=2022, month=3, day=14), ) SharedCustodyRule.objects.create(agenda=agenda2, guardian=father, days=list(range(7))) Booking.objects.all().delete() resp = app.post_json(fillslots_url % 'father_id', params=params) assert [x['date'] for x in resp.json['booked_events']] == [ '2022-03-10', '2022-03-11', '2022-03-12', '2022-03-14', '2022-03-15', '2022-03-16', '2022-03-17', '2022-03-18', '2022-03-19', '2022-03-20', ] resp = app.post_json(fillslots_url % 'mother_id', params=params) assert [x['date'] for x in resp.json['booked_events']] == [ '2022-03-07', '2022-03-08', '2022-03-09', '2022-03-13', # last date before new agenda rules apply ] # check date_start/date_end params Booking.objects.all().delete() resp = app.post_json( (fillslots_url + '&date_start=2022-03-11&date_end=2022-03-18') % 'father_id', params=params ) assert [x['date'] for x in resp.json['booked_events']] == [ '2022-03-11', '2022-03-12', '2022-03-14', '2022-03-15', '2022-03-16', '2022-03-17', ] @pytest.mark.freeze_time('2021-09-06 12:00') def test_recurring_events_api_fillslots_overlapping_events(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.objects.create( label='Event 12-14', start_datetime=start, duration=120, places=2, 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), duration=60, places=2, 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), duration=120, places=2, 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( label='Event 12-18', start_datetime=start, duration=360, places=2, recurrence_end_date=end, recurrence_days=[1, 5], agenda=agenda2, ).create_all_recurrences() Event.objects.create( label='No duration', start_datetime=start, places=2, recurrence_end_date=end, recurrence_days=[5], agenda=agenda2, ).create_all_recurrences() app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = '/api/agendas/recurring-events/fillslots/?action=%s&agendas=%s' # booking without overlap params = { 'user_external_id': 'user_id', 'check_overlaps': True, '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) assert resp.json['booking_count'] == 14 # book again resp = app.post_json(fillslots_url % ('update', 'first-agenda,second-agenda'), params=params) assert resp.json['booking_count'] == 0 # change bookings params = {'user_external_id': 'user_id', 'check_overlaps': True, '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 # booking overlapping events is allowed if one has no duration params = { 'user_external_id': 'user_id', 'check_overlaps': True, '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) assert resp.json['booking_count'] == 8 assert resp.json['cancelled_booking_count'] == 5 # booking overlapping events with durations is forbidden params = { 'user_external_id': 'user_id', 'check_overlaps': True, '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) assert resp.json['err'] == 1 assert ( resp.json['err_desc'] == 'Some events occur at the same time: first-agenda@event-12-14:1 / second-agenda@event-12-18:1' ) params = { 'user_external_id': 'user_id', 'check_overlaps': True, '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' ), } resp = app.post_json(fillslots_url % ('update', 'first-agenda,second-agenda'), params=params) assert resp.json['err'] == 1 assert resp.json['err_desc'] == ( 'Some events occur at the same time: first-agenda@event-12-14:1 / second-agenda@event-12-18:1, ' 'first-agenda@event-15-17:1 / second-agenda@event-12-18:1, first-agenda@event-15-17:5 / second-agenda@event-12-18:5' ) # overlaps check is disabled by default params = { 'user_external_id': 'user_id', '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) assert resp.json['err'] == 0 assert resp.json['booking_count'] == 10