api: exclude slots already booked by user - meetings (#51341)
This commit is contained in:
parent
e15439bd3c
commit
6d57af612e
|
@ -72,7 +72,13 @@ TimeSlot = collections.namedtuple('TimeSlot', ['start_datetime', 'end_datetime',
|
|||
|
||||
|
||||
def get_all_slots(
|
||||
base_agenda, meeting_type, resources=None, unique=False, start_datetime=None, end_datetime=None
|
||||
base_agenda,
|
||||
meeting_type,
|
||||
resources=None,
|
||||
unique=False,
|
||||
start_datetime=None,
|
||||
end_datetime=None,
|
||||
excluded_user_external_id=None,
|
||||
):
|
||||
"""Get all occupation state of all possible slots for the given agenda (of
|
||||
its real agendas for a virtual agenda) and the given meeting_type.
|
||||
|
@ -225,6 +231,32 @@ def get_all_slots(
|
|||
for event_start_datetime, event_duration in booked_events
|
||||
)
|
||||
|
||||
# aggregate already booked time intervals by excluded_user_external_id
|
||||
user_bookings = IntervalSet()
|
||||
if excluded_user_external_id:
|
||||
used_min_datetime, used_max_datetime = (
|
||||
min([v[0] for v in agenda_id_min_max_datetime.values()]),
|
||||
max([v[1] for v in agenda_id_min_max_datetime.values()]),
|
||||
)
|
||||
booked_events = (
|
||||
Event.objects.filter(
|
||||
agenda__in=agenda_ids,
|
||||
start_datetime__gte=used_min_datetime,
|
||||
start_datetime__lte=used_max_datetime + meeting_duration_td,
|
||||
booking__user_external_id=excluded_user_external_id,
|
||||
)
|
||||
.exclude(booking__cancellation_datetime__isnull=False)
|
||||
# ordering is important for the later groupby, it works like sort | uniq
|
||||
.order_by('start_datetime', 'meeting_type__duration')
|
||||
.values_list('start_datetime', 'meeting_type__duration')
|
||||
)
|
||||
# compute exclusion set by desk from all bookings, using
|
||||
# itertools.groupby() to group them by desk_id
|
||||
user_bookings = IntervalSet.from_ordered(
|
||||
(event_start_datetime, event_start_datetime + datetime.timedelta(minutes=event_duration))
|
||||
for event_start_datetime, event_duration in booked_events
|
||||
)
|
||||
|
||||
unique_booked = {}
|
||||
for time_period in base_agenda.get_effective_time_periods():
|
||||
duration = (
|
||||
|
@ -272,8 +304,11 @@ def get_all_slots(
|
|||
# slot is full if an already booked event overlaps it
|
||||
# check resources first
|
||||
booked = resources_bookings.overlaps(start_datetime, end_datetime)
|
||||
# then check user boookings
|
||||
if not booked:
|
||||
booked = user_bookings.overlaps(start_datetime, end_datetime)
|
||||
# then bookings if resources are free
|
||||
if not booked:
|
||||
# then bookings if resources are free
|
||||
booked = desk.id in bookings and bookings[desk.id].overlaps(
|
||||
start_datetime, end_datetime
|
||||
)
|
||||
|
@ -629,6 +664,8 @@ class MeetingDatetimes(APIView):
|
|||
http_status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
user_external_id = request.GET.get('exclude_user_external_id') or None
|
||||
|
||||
# Generate an unique slot for each possible meeting [start_datetime,
|
||||
# end_datetime] range.
|
||||
# First use get_all_slots() to get each possible meeting by desk and
|
||||
|
@ -650,6 +687,7 @@ class MeetingDatetimes(APIView):
|
|||
unique=True,
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
excluded_user_external_id=user_external_id,
|
||||
)
|
||||
)
|
||||
for slot in sorted(all_slots, key=lambda slot: slot[:3]):
|
||||
|
|
|
@ -1007,6 +1007,66 @@ def test_datetimes_api_meetings_agenda_short_time_periods(app, meetings_agenda,
|
|||
assert resp.json['err_desc'] == 'no more desk available'
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2021-02-25')
|
||||
def test_datetimes_api_meetings_agenda_exclude_slots(app):
|
||||
tomorrow = now() + datetime.timedelta(days=1)
|
||||
agenda = Agenda.objects.create(
|
||||
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
|
||||
)
|
||||
desk = Desk.objects.create(agenda=agenda, slug='desk')
|
||||
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
|
||||
TimePeriod.objects.create(
|
||||
weekday=tomorrow.date().weekday(),
|
||||
start_time=datetime.time(9, 0),
|
||||
end_time=datetime.time(17, 00),
|
||||
desk=desk,
|
||||
)
|
||||
desk.duplicate()
|
||||
event = Event.objects.create(
|
||||
agenda=agenda,
|
||||
meeting_type=meeting_type,
|
||||
places=1,
|
||||
start_datetime=localtime(tomorrow).replace(hour=9, minute=0),
|
||||
desk=desk,
|
||||
)
|
||||
Booking.objects.create(event=event, user_external_id='42')
|
||||
event2 = Event.objects.create(
|
||||
agenda=agenda,
|
||||
meeting_type=meeting_type,
|
||||
places=1,
|
||||
start_datetime=localtime(tomorrow).replace(hour=10, minute=0),
|
||||
desk=desk,
|
||||
)
|
||||
cancelled = Booking.objects.create(event=event2, user_external_id='35')
|
||||
cancelled.cancel()
|
||||
|
||||
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug))
|
||||
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
|
||||
assert resp.json['data'][0]['disabled'] is False
|
||||
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
|
||||
assert resp.json['data'][2]['disabled'] is False
|
||||
|
||||
resp = app.get(
|
||||
'/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug),
|
||||
params={'exclude_user_external_id': '35'},
|
||||
)
|
||||
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
|
||||
assert resp.json['data'][0]['disabled'] is False
|
||||
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
|
||||
assert resp.json['data'][2]['disabled'] is False
|
||||
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.get(
|
||||
'/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug),
|
||||
params={'exclude_user_external_id': '42'},
|
||||
)
|
||||
assert len(ctx.captured_queries) == 9
|
||||
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
|
||||
assert resp.json['data'][0]['disabled'] is True
|
||||
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
|
||||
assert resp.json['data'][2]['disabled'] is False
|
||||
|
||||
|
||||
def test_booking_api(app, some_data, user):
|
||||
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
|
||||
event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0]
|
||||
|
@ -4831,6 +4891,84 @@ def test_virtual_agendas_meetings_datetimes_multiple_agendas(app, mock_now):
|
|||
assert len(resp.json['data']) == 20
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2021-02-25')
|
||||
def test_virtual_agendas_meetings_datetimes_exclude_slots(app):
|
||||
tomorrow = now() + datetime.timedelta(days=1)
|
||||
agenda = Agenda.objects.create(
|
||||
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
|
||||
)
|
||||
desk = Desk.objects.create(agenda=agenda, slug='desk')
|
||||
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
|
||||
TimePeriod.objects.create(
|
||||
weekday=tomorrow.date().weekday(),
|
||||
start_time=datetime.time(9, 0),
|
||||
end_time=datetime.time(17, 00),
|
||||
desk=desk,
|
||||
)
|
||||
agenda2 = agenda.duplicate()
|
||||
virt_agenda = Agenda.objects.create(
|
||||
label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=10
|
||||
)
|
||||
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda)
|
||||
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda2)
|
||||
|
||||
event = Event.objects.create(
|
||||
agenda=agenda,
|
||||
meeting_type=meeting_type,
|
||||
places=1,
|
||||
start_datetime=localtime(tomorrow).replace(hour=9, minute=0),
|
||||
desk=desk,
|
||||
)
|
||||
Booking.objects.create(event=event, user_external_id='42')
|
||||
event2 = Event.objects.create(
|
||||
agenda=agenda,
|
||||
meeting_type=meeting_type,
|
||||
places=1,
|
||||
start_datetime=localtime(tomorrow).replace(hour=10, minute=0),
|
||||
desk=desk,
|
||||
)
|
||||
cancelled = Booking.objects.create(event=event2, user_external_id='35')
|
||||
cancelled.cancel()
|
||||
|
||||
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug))
|
||||
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
|
||||
assert resp.json['data'][0]['disabled'] is False
|
||||
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
|
||||
assert resp.json['data'][2]['disabled'] is False
|
||||
|
||||
resp = app.get(
|
||||
'/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug),
|
||||
params={'exclude_user_external_id': '35'},
|
||||
)
|
||||
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
|
||||
assert resp.json['data'][0]['disabled'] is False
|
||||
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
|
||||
assert resp.json['data'][2]['disabled'] is False
|
||||
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.get(
|
||||
'/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug),
|
||||
params={'exclude_user_external_id': '42'},
|
||||
)
|
||||
assert len(ctx.captured_queries) == 13
|
||||
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
|
||||
assert resp.json['data'][0]['disabled'] is True
|
||||
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
|
||||
assert resp.json['data'][2]['disabled'] is False
|
||||
|
||||
virt_agenda.minimal_booking_delay = None
|
||||
virt_agenda.maximal_booking_delay = None
|
||||
virt_agenda.save()
|
||||
resp = app.get(
|
||||
'/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug),
|
||||
params={'exclude_user_external_id': '42'},
|
||||
)
|
||||
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
|
||||
assert resp.json['data'][0]['disabled'] is True
|
||||
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
|
||||
assert resp.json['data'][2]['disabled'] is False
|
||||
|
||||
|
||||
def test_virtual_agendas_meetings_booking(app, mock_now, user):
|
||||
foo_agenda = Agenda.objects.create(
|
||||
label='Foo Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5
|
||||
|
|
Loading…
Reference in New Issue