import datetime import pytest from django.db import connection from django.test.utils import CaptureQueriesContext from django.utils.timezone import localtime, make_aware, now from chrono.agendas.models import ( Agenda, Booking, Category, Desk, Event, EventsType, Person, SharedCustodyAgenda, SharedCustodyRule, Subscription, ) pytestmark = pytest.mark.django_db @pytest.mark.freeze_time('2021-09-06 12:00') def test_api_events_fillslots_preserve_past_bookings_multiple_agendas(app, user, freezer): agenda = Agenda.objects.create(label='Foo bar', kind='events') event = Event.objects.create( label='Event', start_datetime=now() + datetime.timedelta(days=5), places=2, agenda=agenda ) second_event = Event.objects.create( label='Event 2', start_datetime=now() + datetime.timedelta(days=10), places=2, agenda=agenda ) app.authorization = ('Basic', ('john.doe', 'password')) params = {'user_external_id': 'user_id', 'slots': 'foo-bar@event,foo-bar@event-2'} resp = app.post_json('/api/agendas/events/fillslots/?agendas=foo-bar', params=params) assert resp.json['booking_count'] == 2 assert resp.json['cancelled_booking_count'] == 0 # book only second event while first event is in the past freezer.move_to('2021-09-12') params = {'user_external_id': 'user_id', 'slots': 'foo-bar@event-2'} resp = app.post_json('/api/agendas/events/fillslots/?agendas=foo-bar', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 0 assert event.booking_set.count() == 1 assert second_event.booking_set.count() == 1 # cancel all future bookings params = {'user_external_id': 'user_id', 'slots': ''} resp = app.post_json('/api/agendas/events/fillslots/?agendas=foo-bar', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 1 assert event.booking_set.count() == 1 assert second_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 0 @pytest.mark.freeze_time('2021-09-06 12:00') def test_api_events_fillslots_preserve_out_of_delays_bookings_multiple_agendas(app, user, freezer): agenda = Agenda.objects.create( label='Foo bar', kind='events', minimal_booking_delay=2, maximal_booking_delay=10 ) event = Event.objects.create( label='Event', start_datetime=now() + datetime.timedelta(days=5), places=2, agenda=agenda ) second_event = Event.objects.create( label='Event 2', start_datetime=now() + datetime.timedelta(days=9), places=2, agenda=agenda ) app.authorization = ('Basic', ('john.doe', 'password')) params = {'user_external_id': 'user_id', 'slots': 'foo-bar@event,foo-bar@event-2'} resp = app.post_json('/api/agendas/events/fillslots/?agendas=foo-bar', params=params) assert resp.json['booking_count'] == 2 assert resp.json['cancelled_booking_count'] == 0 assert event.booking_set.get().out_of_min_delay is False assert second_event.booking_set.get().out_of_min_delay is False # book only second event while first event is out of delay with multiple agendas API freezer.move_to('2021-09-10') params = {'user_external_id': 'user_id', 'slots': 'foo-bar@event-2'} resp = app.post_json('/api/agendas/events/fillslots/?agendas=foo-bar', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 0 assert event.booking_set.filter(cancellation_datetime__isnull=True).count() == 1 assert event.booking_set.get().out_of_min_delay is False assert second_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 1 assert second_event.booking_set.get().out_of_min_delay is False booking = event.booking_set.get() # except if we want to bypass delays params = {'user_external_id': 'user_id', 'slots': 'foo-bar@event-2', 'bypass_delays': True} resp = app.post_json('/api/agendas/events/fillslots/?agendas=foo-bar', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 1 assert event.booking_set.filter(cancellation_datetime__isnull=True).count() == 0 assert event.booking_set.get().out_of_min_delay is True assert second_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 1 assert second_event.booking_set.get().out_of_min_delay is False booking.save() # reset # cancel all bookings in delays params = {'user_external_id': 'user_id', 'slots': ''} resp = app.post_json('/api/agendas/events/fillslots/?agendas=foo-bar', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 1 assert event.booking_set.count() == 1 assert event.booking_set.get().out_of_min_delay is False assert second_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 0 assert second_event.booking_set.get().out_of_min_delay is False # bypass delays params = {'user_external_id': 'user_id', 'slots': '', 'bypass_delays': True} resp = app.post_json('/api/agendas/events/fillslots/?agendas=foo-bar', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 1 assert event.booking_set.filter(cancellation_datetime__isnull=True).count() == 0 assert event.booking_set.get().out_of_min_delay is True assert second_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 0 assert second_event.booking_set.get().out_of_min_delay is False booking.save() # reset # book only first event while second event is out of delay with multiple agendas API freezer.move_to('2021-09-04') params = {'user_external_id': 'user_id', 'slots': 'foo-bar@event'} resp = app.post_json('/api/agendas/events/fillslots/?agendas=foo-bar', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 0 assert event.booking_set.filter(cancellation_datetime__isnull=True).count() == 1 assert second_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 0 # bypass_delays has no effect on maximal_booking_delay params = {'user_external_id': 'user_id', 'slots': 'foo-bar@event', 'bypass_delays': True} resp = app.post_json('/api/agendas/events/fillslots/?agendas=foo-bar', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 0 assert event.booking_set.filter(cancellation_datetime__isnull=True).count() == 1 assert second_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 0 @pytest.mark.freeze_time('2021-09-06 12:00') def test_api_events_fillslots_multiple_agendas(app, user): events_type = EventsType.objects.create(label='Foo') first_agenda = Agenda.objects.create(label='First agenda', kind='events', events_type=events_type) Desk.objects.create(agenda=first_agenda, slug='_exceptions_holder') first_event = Event.objects.create( label='Event', start_datetime=now() + datetime.timedelta(days=5), places=2, agenda=first_agenda, ) second_agenda = Agenda.objects.create(label='Second agenda', kind='events') Desk.objects.create(agenda=second_agenda, slug='_exceptions_holder') second_event = Event.objects.create( label='Event', start_datetime=now() + datetime.timedelta(days=6), places=2, agenda=second_agenda, ) agenda_slugs = '%s,%s' % (first_agenda.slug, second_agenda.slug) resp = app.get('/api/agendas/datetimes/', params={'agendas': agenda_slugs}) event_slugs = ','.join((resp.json['data'][0]['id'], resp.json['data'][1]['id'])) assert event_slugs == 'first-agenda@event,second-agenda@event' app.authorization = ('Basic', ('john.doe', 'password')) params = {'user_external_id': 'user_id', 'check_overlaps': True, 'slots': event_slugs} with CaptureQueriesContext(connection) as ctx: resp = app.post_json('/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params) assert len(ctx.captured_queries) == 17 assert resp.json['booking_count'] == 2 assert len(resp.json['booked_events']) == 2 assert resp.json['booked_events'][0]['id'] == 'first-agenda@event' assert ( resp.json['booked_events'][0]['booking']['id'] == Booking.objects.filter(event__agenda=first_agenda, event=first_event).latest('pk').pk ) assert resp.json['booked_events'][1]['id'] == 'second-agenda@event' assert ( resp.json['booked_events'][1]['booking']['id'] == Booking.objects.filter(event__agenda=second_agenda, event=second_event).latest('pk').pk ) assert first_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 1 assert second_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 1 # booking modification params = {'user_external_id': 'user_id', 'slots': 'first-agenda@event'} resp = app.post_json('/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params) assert resp.json['booking_count'] == 0 assert len(resp.json['booked_events']) == 0 assert resp.json['cancelled_booking_count'] == 1 assert first_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 1 assert second_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 0 params = {'user_external_id': 'user_id_2', 'slots': event_slugs} resp = app.post_json('/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params) assert resp.json['booking_count'] == 2 assert len(resp.json['booked_events']) == 2 assert resp.json['booked_events'][0]['id'] == 'first-agenda@event' assert ( resp.json['booked_events'][0]['booking']['id'] == Booking.objects.filter(event__agenda=first_agenda, event=first_event).latest('pk').pk ) assert resp.json['booked_events'][1]['id'] == 'second-agenda@event' assert ( resp.json['booked_events'][1]['booking']['id'] == Booking.objects.filter(event__agenda=second_agenda, event=second_event).latest('pk').pk ) assert first_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 2 assert second_event.booking_set.filter(cancellation_datetime__isnull=True).count() == 1 params = {'user_external_id': 'user_id_3', 'slots': event_slugs} resp = app.post_json('/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'some events are full: Event' # invalid agenda slugs in querystring resp = app.post_json( '/api/agendas/events/fillslots/?agendas=first-agenda,xxx,yyy', params=params, status=400 ) assert resp.json['errors']['agendas'][0] == 'invalid slugs: xxx, yyy' # invalid agenda slugs in payload params = {'user_external_id': 'user_id_3', 'slots': 'first-agenda@event,xxx@event,yyy@event'} resp = app.post_json( '/api/agendas/events/fillslots/?agendas=%s' % agenda_slugs, params=params, status=400 ) assert resp.json['errors']['slots'] == ['Events from the following agendas cannot be booked: xxx, yyy'] # missing agendas parameter resp = app.post_json('/api/agendas/events/fillslots/', params=params, status=400) assert resp.json['errors']['non_field_errors'] == [ 'Either "agendas" or "subscribed" parameter is required.' ] # valid agendas parameter and event slugs, but mismatch between the two params = {'user_external_id': 'user_id_3', 'slots': event_slugs} resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400) assert resp.json['errors']['slots'] == [ 'Events from the following agendas cannot be booked: second-agenda' ] # missing @ in slot params['slots'] = 'first-agenda' resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400) assert resp.json['errors']['slots'] == ['Invalid format for slot first-agenda'] # empty event slug params['slots'] = 'first-agenda@' resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400) assert resp.json['errors']['slots'] == ['Missing event slug in slot first-agenda@'] # empty agenda slug params['slots'] = '@event' resp = app.post_json('/api/agendas/events/fillslots/?agendas=first-agenda', params=params, status=400) assert resp.json['errors']['slots'] == ['Missing agenda slug in slot @event'] @pytest.mark.freeze_time('2021-09-06 12:00') def test_api_events_fillslots_multiple_agendas_with_cancelled(app, user): agenda_1 = Agenda.objects.create(label='Agenda 1', kind='events') Desk.objects.create(agenda=agenda_1, slug='_exceptions_holder') event_1 = Event.objects.create( label='Event 1', start_datetime=now() + datetime.timedelta(days=1), places=2, agenda=agenda_1, ) agenda_2 = Agenda.objects.create(label='Agenda 2', kind='events') Desk.objects.create(agenda=agenda_2, slug='_exceptions_holder') event_2 = Event.objects.create( label='Event 2', start_datetime=now() + datetime.timedelta(days=2), places=2, agenda=agenda_2, ) event_3 = Event.objects.create( label='Event 3', start_datetime=now() + datetime.timedelta(days=3), places=2, agenda=agenda_2, ) # create cancelled booking for the user booking_1 = Booking.objects.create(event=event_1, user_external_id='user_id') booking_1.cancel() assert booking_1.cancellation_datetime is not None # and non cancelled booking for the user booking_2 = Booking.objects.create(event=event_2, user_external_id='user_id') assert booking_2.cancellation_datetime is None # secondary booking for this one booking_2_secondary = Booking.objects.create(event=event_2, primary_booking=booking_2) # and bookings for another user Booking.objects.create(event=event_1, user_external_id='user_id_foobar') other_booking = Booking.objects.create(event=event_2, user_external_id='user_id_foobar') other_booking.cancel() app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = '/api/agendas/events/fillslots/?agendas=%s,%s' % (agenda_1.slug, agenda_2.slug) params = {'user_external_id': 'user_id', 'slots': 'agenda-1@event-1,agenda-2@event-2,agenda-2@event-3'} resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 2 assert len(resp.json['booked_events']) == 2 assert resp.json['cancelled_booking_count'] == 0 assert resp.json['booked_events'][0]['id'] == 'agenda-1@event-1' assert ( resp.json['booked_events'][0]['booking']['id'] == Booking.objects.filter(event__agenda=agenda_1, event=event_1).latest('pk').pk ) assert resp.json['booked_events'][1]['id'] == 'agenda-2@event-3' assert ( resp.json['booked_events'][1]['booking']['id'] == Booking.objects.filter(event__agenda=agenda_2, event=event_3).latest('pk').pk ) assert Booking.objects.filter(user_external_id='user_id').count() == 3 assert Booking.objects.filter(user_external_id='user_id', cancellation_datetime__isnull=True).count() == 3 assert Booking.objects.filter(pk=booking_1.pk).exists() is False # cancelled booking deleted booking_2.refresh_from_db() booking_2_secondary.refresh_from_db() assert booking_2.cancellation_datetime is None assert booking_2_secondary.cancellation_datetime is None params = {'user_external_id': 'user_id', 'slots': 'agenda-2@event-3'} resp = app.post_json(fillslots_url, params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 2 assert Booking.objects.filter(user_external_id='user_id').count() == 3 assert Booking.objects.filter(user_external_id='user_id', cancellation_datetime__isnull=True).count() == 1 assert Booking.objects.filter(pk=booking_1.pk).exists() is False # cancelled booking deleted booking_2.refresh_from_db() booking_2_secondary.refresh_from_db() assert booking_2.cancellation_datetime is not None assert booking_2_secondary.cancellation_datetime is not None def test_api_events_fillslots_multiple_agendas_check_delays(app, user): agenda = Agenda.objects.create( label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=7 ) Event.objects.create( slug='event-slug', start_datetime=localtime() + datetime.timedelta(days=5), places=5, agenda=agenda, ) app.authorization = ('Basic', ('john.doe', 'password')) resp = app.post( '/api/agendas/events/fillslots/?agendas=foo-bar', params={'user_external_id': 'user_id', 'slots': 'foo-bar@event-slug'}, ) assert resp.json['err'] == 0 booking = Booking.objects.latest('pk') assert booking.out_of_min_delay is False booking.delete() # test minimal_booking_delay agenda.minimal_booking_delay = 6 agenda.save() resp = app.post( '/api/agendas/events/fillslots/?agendas=foo-bar', params={'user_external_id': 'user_id', 'slots': 'foo-bar@event-slug'}, ) assert resp.json['err'] == 1 assert resp.json['err_class'] == 'event not bookable' agenda.save() resp = app.post( '/api/agendas/events/fillslots/?agendas=foo-bar', params={'user_external_id': 'user_id', 'slots': 'foo-bar@event-slug', 'bypass_delays': True}, ) assert resp.json['err'] == 0 booking = Booking.objects.latest('pk') assert booking.out_of_min_delay is True booking.delete() # test maximal_booking_delay agenda.minimal_booking_delay = 0 agenda.maximal_booking_delay = 3 agenda.save() resp = app.post( '/api/agendas/events/fillslots/?agendas=foo-bar', params={'user_external_id': 'user_id', 'slots': 'foo-bar@event-slug'}, ) assert resp.json['err'] == 1 assert resp.json['err_class'] == 'event not bookable' agenda.save() resp = app.post( '/api/agendas/events/fillslots/?agendas=foo-bar', params={'user_external_id': 'user_id', 'slots': 'foo-bar@event-slug', 'bypass_delays': True}, ) assert resp.json['err'] == 0 booking = Booking.objects.latest('pk') assert booking.out_of_min_delay is False @pytest.mark.freeze_time('2021-09-06 12:00') def test_api_events_fillslots_multiple_agendas_subscribed(app, user): category = Category.objects.create(label='Category A') first_agenda = Agenda.objects.create(label='First agenda', kind='events', category=category) second_agenda = Agenda.objects.create(label='Second agenda', kind='events', category=category) category = Category.objects.create(label='Category B') third_agenda = Agenda.objects.create(label='Third agenda', kind='events', category=category) for agenda in Agenda.objects.all(): Event.objects.create( slug='event', start_datetime=now() + datetime.timedelta(days=10), places=5, agenda=agenda, ) Event.objects.create( slug='event-2', start_datetime=now() + datetime.timedelta(days=20), places=5, agenda=agenda, ) # add subscriptions to first and second agenda for agenda in (first_agenda, second_agenda): Subscription.objects.create( agenda=agenda, user_external_id='xxx', date_start=now(), date_end=now() + datetime.timedelta(days=10), # too soon ) # book events app.authorization = ('Basic', ('john.doe', 'password')) params = {'user_external_id': 'xxx', 'slots': 'first-agenda@event,second-agenda@event'} resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-a', params=params, status=400) # first event on the last subscription's day Subscription.objects.all().update(date_end=now() + datetime.timedelta(days=11)) resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-a', params=params) assert resp.json['booking_count'] == 2 assert ( Event.objects.get(agenda=first_agenda, slug='event') .booking_set.filter(cancellation_datetime__isnull=True) .count() == 1 ) assert ( Event.objects.get(agenda=second_agenda, slug='event') .booking_set.filter(cancellation_datetime__isnull=True) .count() == 1 ) assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 2 # update bookings for category-a params = {'user_external_id': 'xxx', 'slots': 'second-agenda@event'} resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-a', params=params) assert resp.json['booking_count'] == 0 assert resp.json['cancelled_booking_count'] == 1 assert ( Event.objects.get(agenda=first_agenda, slug='event') .booking_set.filter(cancellation_datetime__isnull=True) .count() == 0 ) assert ( Event.objects.get(agenda=second_agenda, slug='event') .booking_set.filter(cancellation_datetime__isnull=True) .count() == 1 ) assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 1 # try to book event from agenda with no subscription TODO messages params = {'user_external_id': 'xxx', 'slots': 'third-agenda@event'} for slug in ('all', 'category-a', 'category-b'): resp = app.post_json('/api/agendas/events/fillslots/?subscribed=%s' % slug, params=params, status=400) assert ( resp.json['errors']['slots'][0] == 'Events from the following agendas cannot be booked: third-agenda' ) # add subscription to third agenda subscription = Subscription.objects.create( agenda=third_agenda, user_external_id='xxx', date_start=now(), date_end=now() + datetime.timedelta(days=10), ) params = {'user_external_id': 'xxx', 'slots': 'third-agenda@event'} resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-b', params=params, status=400) subscription.date_end = now() + datetime.timedelta(days=11) subscription.save() resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-b', params=params) assert resp.json['booking_count'] == 1 assert ( Event.objects.get(agenda=first_agenda, slug='event') .booking_set.filter(cancellation_datetime__isnull=True) .count() == 0 ) assert ( Event.objects.get(agenda=second_agenda, slug='event') .booking_set.filter(cancellation_datetime__isnull=True) .count() == 1 ) assert ( Event.objects.get(agenda=third_agenda, slug='event') .booking_set.filter(cancellation_datetime__isnull=True) .count() == 1 ) assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 2 # add subscription to first agenda (disjoint) spanning event-2 for agenda in (first_agenda, second_agenda): Subscription.objects.create( agenda=agenda, user_external_id='xxx', date_start=now() + datetime.timedelta(days=15), date_end=now() + datetime.timedelta(days=25), ) # book event-2 while updating all bookings params = {'user_external_id': 'xxx', 'slots': 'first-agenda@event,second-agenda@event-2'} resp = app.post_json('/api/agendas/events/fillslots/?subscribed=all', params=params) assert resp.json['booking_count'] == 2 assert resp.json['cancelled_booking_count'] == 2 assert ( Event.objects.get(agenda=first_agenda, slug='event') .booking_set.filter(cancellation_datetime__isnull=True) .count() == 1 ) assert ( Event.objects.get(agenda=second_agenda, slug='event-2') .booking_set.filter(cancellation_datetime__isnull=True) .count() == 1 ) assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 2 # other user for agenda in (first_agenda, second_agenda): Subscription.objects.create( agenda=agenda, user_external_id='yyy', date_start=now(), date_end=now() + datetime.timedelta(days=25), ) params = {'user_external_id': 'yyy', 'slots': 'first-agenda@event,second-agenda@event-2'} resp = app.post_json('/api/agendas/events/fillslots/?subscribed=all', params=params) assert resp.json['booking_count'] == 2 assert resp.json['cancelled_booking_count'] == 0 assert ( Event.objects.get(agenda=first_agenda, slug='event') .booking_set.filter(cancellation_datetime__isnull=True) .count() == 2 ) assert ( Event.objects.get(agenda=second_agenda, slug='event-2') .booking_set.filter(cancellation_datetime__isnull=True) .count() == 2 ) assert Booking.objects.filter(cancellation_datetime__isnull=True).count() == 4 # try to book event outside subscription date range params = {'user_external_id': 'xxx', 'slots': 'third-agenda@event-2'} resp = app.post_json('/api/agendas/events/fillslots/?subscribed=all', params=params, status=400) assert resp.json['err_class'] == 'Some events are outside user subscriptions: third-agenda@event-2' # mismatch between subscribed parameter and event params = {'user_external_id': 'xxx', 'slots': 'third-agenda@event'} resp = app.post_json('/api/agendas/events/fillslots/?subscribed=category-a', params=params, status=400) assert ( resp.json['errors']['slots'][0] == 'Events from the following agendas cannot be booked: third-agenda' ) # missing user_external_id params = {'slots': 'third-agenda@event'} resp = app.post_json('/api/agendas/events/fillslots/?subscribed=all', params=params, status=400) assert 'required' in resp.json['errors']['user_external_id'][0] @pytest.mark.freeze_time('2022-03-07 14:00') # Monday of 10th week def test_api_events_fillslots_multiple_agendas_shared_custody(app, user): agenda = Agenda.objects.create(label='First agenda', kind='events') Desk.objects.create(agenda=agenda, slug='_exceptions_holder') Event.objects.create( slug='event-wednesday', start_datetime=make_aware(datetime.datetime(year=2022, month=3, day=9, hour=14, minute=0)), places=5, agenda=agenda, ) Event.objects.create( slug='event-thursday', start_datetime=make_aware(datetime.datetime(year=2022, month=3, day=10, hour=14, minute=0)), places=5, agenda=agenda, ) Subscription.objects.create( agenda=agenda, user_external_id='child_id', date_start=now(), date_end=now() + datetime.timedelta(days=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='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, days=[0, 1, 2]) SharedCustodyRule.objects.create(agenda=agenda, guardian=mother, days=[3, 4, 5, 6]) app.authorization = ('Basic', ('john.doe', 'password')) params = {'user_external_id': 'child_id', 'slots': 'first-agenda@event-wednesday'} resp = app.post_json( '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=father_id', params=params ) assert resp.json['booking_count'] == 1 resp = app.post_json( '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=mother_id', params=params, status=400, ) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'Some events are outside guardian custody: first-agenda@event-wednesday' params['slots'] = 'first-agenda@event-thursday' resp = app.post_json( '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=mother_id', params=params ) assert resp.json['booking_count'] == 1 # unknown guardian Booking.objects.all().delete() resp = app.post_json( '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=unknown_id', params=params, status=400, ) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'Some events are outside guardian custody: first-agenda@event-thursday' # guardian_external_id parameter is ignored if there is no custody agenda for child agenda.delete() resp = app.post_json( '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=mother_id', params=params, ) assert resp.json['booking_count'] == 1 @pytest.mark.freeze_time('2022-03-07 14:00') # Monday of 10th week def test_api_events_fillslots_multiple_agendas_shared_custody_date_start(app, user): agenda = Agenda.objects.create(label='First agenda', kind='events') Desk.objects.create(agenda=agenda, slug='_exceptions_holder') Event.objects.create( slug='event-wednesday', start_datetime=make_aware(datetime.datetime(year=2022, month=3, day=9, hour=14, minute=0)), places=5, agenda=agenda, ) Event.objects.create( slug='event-thursday', start_datetime=make_aware(datetime.datetime(year=2022, month=3, day=10, hour=14, minute=0)), places=5, agenda=agenda, ) Subscription.objects.create( agenda=agenda, user_external_id='child_id', date_start=now(), date_end=now() + datetime.timedelta(days=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='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, days=list(range(7))) agenda2 = SharedCustodyAgenda.objects.create( first_guardian=father, second_guardian=mother, child=child, date_start=datetime.date(year=2022, month=3, day=10), ) SharedCustodyRule.objects.create(agenda=agenda2, guardian=mother, days=list(range(7))) app.authorization = ('Basic', ('john.doe', 'password')) params = {'user_external_id': 'child_id', 'slots': 'first-agenda@event-wednesday'} resp = app.post_json( '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=father_id', params=params ) assert resp.json['booking_count'] == 1 resp = app.post_json( '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=mother_id', params=params, status=400, ) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'Some events are outside guardian custody: first-agenda@event-wednesday' params['slots'] = 'first-agenda@event-thursday' resp = app.post_json( '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=mother_id', params=params ) assert resp.json['booking_count'] == 1 params['slots'] = 'first-agenda@event-thursday' resp = app.post_json( '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=father_id', params=params, status=400, ) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'Some events are outside guardian custody: first-agenda@event-thursday' # check date_start/date_end params Booking.objects.all().delete() resp = app.post_json( '/api/agendas/events/fillslots/?subscribed=all&guardian_external_id=mother_id', params={'date_start': '2022-03-09', 'date_end': '2022-03-20', **params}, ) assert resp.json['booking_count'] == 1 @pytest.mark.freeze_time('2021-09-06 12:00') def test_api_events_fillslots_multiple_agendas_overlapping_events(app, user, freezer): agenda = Agenda.objects.create(label='Foo bar', kind='events') Event.objects.create( label='Event', start_datetime=now() + datetime.timedelta(days=5), duration=120, places=5, agenda=agenda, ) second_agenda = Agenda.objects.create(label='Foo bar 2', kind='events') Event.objects.create( label='Event 2', start_datetime=now() + datetime.timedelta(days=5, hours=1), duration=120, places=5, agenda=second_agenda, ) app.authorization = ('Basic', ('john.doe', 'password')) fillslots_url = '/api/agendas/events/fillslots/?agendas=%s' resp = app.post_json( fillslots_url % ','.join((agenda.slug, second_agenda.slug)), params={ 'user_external_id': 'user_id', 'check_overlaps': True, 'slots': 'foo-bar@event,foo-bar-2@event-2', }, ) assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'Some events occur at the same time: Event, Event 2' # events can be booked separately resp = app.post_json( fillslots_url % agenda.slug, params={'user_external_id': 'user_id', 'check_overlaps': True, 'slots': 'foo-bar@event'}, ) assert resp.json['booking_count'] == 1 resp = app.post_json( fillslots_url % second_agenda.slug, params={'user_external_id': 'user_id', 'check_overlaps': True, 'slots': 'foo-bar-2@event-2'}, ) assert resp.json['booking_count'] == 1