api: restrict slots with exluded timeperiods (#40058)
This commit is contained in:
parent
aec5b30fd9
commit
ec474b46d2
|
@ -361,6 +361,47 @@ class TimePeriod(models.Model):
|
|||
'end_time': self.end_time.strftime('%H:%M'),
|
||||
}
|
||||
|
||||
def get_effective_timeperiods(self, excluded_timeperiods):
|
||||
effective_timeperiods = [self]
|
||||
for excluded_timeperiod in excluded_timeperiods:
|
||||
res = []
|
||||
for effective_timeperiod in effective_timeperiods:
|
||||
if (
|
||||
excluded_timeperiod.weekday != effective_timeperiod.weekday
|
||||
or excluded_timeperiod.start_time >= effective_timeperiod.end_time
|
||||
or excluded_timeperiod.end_time <= effective_timeperiod.start_time
|
||||
):
|
||||
res.append(effective_timeperiod)
|
||||
continue
|
||||
if (
|
||||
excluded_timeperiod.start_time <= effective_timeperiod.start_time
|
||||
and excluded_timeperiod.end_time >= effective_timeperiod.end_time
|
||||
):
|
||||
# completely exclude
|
||||
continue
|
||||
if excluded_timeperiod.start_time > effective_timeperiod.start_time:
|
||||
res.append(
|
||||
TimePeriod(
|
||||
weekday=effective_timeperiod.weekday,
|
||||
start_time=effective_timeperiod.start_time,
|
||||
end_time=excluded_timeperiod.start_time,
|
||||
desk=effective_timeperiod.desk,
|
||||
)
|
||||
)
|
||||
if excluded_timeperiod.end_time < effective_timeperiod.end_time:
|
||||
res.append(
|
||||
TimePeriod(
|
||||
weekday=effective_timeperiod.weekday,
|
||||
start_time=excluded_timeperiod.end_time,
|
||||
end_time=effective_timeperiod.end_time,
|
||||
desk=effective_timeperiod.desk,
|
||||
)
|
||||
)
|
||||
|
||||
effective_timeperiods = res
|
||||
|
||||
return effective_timeperiods
|
||||
|
||||
def get_time_slots(self, min_datetime, max_datetime, meeting_type):
|
||||
meeting_duration = datetime.timedelta(minutes=meeting_type.duration)
|
||||
duration = datetime.timedelta(minutes=self.desk.agenda.get_base_meeting_duration())
|
||||
|
|
|
@ -76,6 +76,7 @@ def get_all_slots(agenda, meeting_type):
|
|||
base_date = now().date()
|
||||
|
||||
agendas = agenda.get_real_agendas()
|
||||
base_agenda = agenda
|
||||
|
||||
open_slots = {}
|
||||
for agenda in agendas:
|
||||
|
@ -88,17 +89,20 @@ def get_all_slots(agenda, meeting_type):
|
|||
if used_time_period_filters['max_datetime'] is None:
|
||||
used_time_period_filters['max_datetime'] = get_max_datetime(agenda)
|
||||
|
||||
for time_period in TimePeriod.objects.filter(desk__agenda=agenda):
|
||||
duration = (
|
||||
datetime.datetime.combine(base_date, time_period.end_time)
|
||||
- datetime.datetime.combine(base_date, time_period.start_time)
|
||||
).seconds / 60
|
||||
if duration < meeting_type.duration:
|
||||
# skip time period that can't even hold a single meeting
|
||||
continue
|
||||
for slot in time_period.get_time_slots(**used_time_period_filters):
|
||||
slot.full = False
|
||||
open_slots[agenda][time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot)
|
||||
for raw_time_period in TimePeriod.objects.filter(desk__agenda=agenda):
|
||||
for time_period in raw_time_period.get_effective_timeperiods(
|
||||
base_agenda.excluded_timeperiods.all()
|
||||
):
|
||||
duration = (
|
||||
datetime.datetime.combine(base_date, time_period.end_time)
|
||||
- datetime.datetime.combine(base_date, time_period.start_time)
|
||||
).seconds / 60
|
||||
if duration < meeting_type.duration:
|
||||
# skip time period that can't even hold a single meeting
|
||||
continue
|
||||
for slot in time_period.get_time_slots(**used_time_period_filters):
|
||||
slot.full = False
|
||||
open_slots[agenda][time_period.desk_id].add(slot.start_datetime, slot.end_datetime, slot)
|
||||
|
||||
# remove excluded slot
|
||||
for agenda in agendas:
|
||||
|
|
|
@ -16,6 +16,7 @@ from chrono.agendas.models import (
|
|||
Event,
|
||||
ICSError,
|
||||
MeetingType,
|
||||
TimePeriod,
|
||||
TimePeriodException,
|
||||
TimePeriodExceptionSource,
|
||||
VirtualMember,
|
||||
|
@ -594,3 +595,92 @@ def test_virtual_agenda_base_meeting_duration():
|
|||
meeting_type = MeetingType(agenda=agenda2, label='Bar', duration=60)
|
||||
meeting_type.save()
|
||||
assert virt_agenda.get_base_meeting_duration() == 60
|
||||
|
||||
|
||||
def test_get_effective_timeperiods():
|
||||
time_period = TimePeriod(weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0))
|
||||
# empty exclusion set
|
||||
effective_timeperiods = time_period.get_effective_timeperiods(TimePeriod.objects.none())
|
||||
assert len(effective_timeperiods) == 1
|
||||
effective_timeperiod = effective_timeperiods[0]
|
||||
assert effective_timeperiod.weekday == time_period.weekday
|
||||
assert effective_timeperiod.start_time == time_period.start_time
|
||||
assert effective_timeperiod.end_time == time_period.end_time
|
||||
|
||||
# exclusions are on a different day
|
||||
excluded_timeperiods = [
|
||||
TimePeriod(weekday=1, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)),
|
||||
TimePeriod(weekday=2, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0)),
|
||||
]
|
||||
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
|
||||
assert len(effective_timeperiods) == 1
|
||||
effective_timeperiod = effective_timeperiods[0]
|
||||
assert effective_timeperiod.weekday == time_period.weekday
|
||||
assert effective_timeperiod.start_time == time_period.start_time
|
||||
assert effective_timeperiod.end_time == time_period.end_time
|
||||
|
||||
# one exclusion, end_time should be earlier
|
||||
excluded_timeperiods = [
|
||||
TimePeriod(weekday=0, start_time=datetime.time(17, 0), end_time=datetime.time(18, 0))
|
||||
]
|
||||
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
|
||||
assert len(effective_timeperiods) == 1
|
||||
effective_timeperiod = effective_timeperiods[0]
|
||||
assert effective_timeperiod.weekday == time_period.weekday
|
||||
assert effective_timeperiod.start_time == datetime.time(10, 0)
|
||||
assert effective_timeperiod.end_time == datetime.time(17, 0)
|
||||
|
||||
# one exclusion, start_time should be later
|
||||
excluded_timeperiods = [
|
||||
TimePeriod(weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(16, 0))
|
||||
]
|
||||
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
|
||||
assert len(effective_timeperiods) == 1
|
||||
effective_timeperiod = effective_timeperiods[0]
|
||||
assert effective_timeperiod.weekday == time_period.weekday
|
||||
assert effective_timeperiod.start_time == datetime.time(16, 0)
|
||||
assert effective_timeperiod.end_time == datetime.time(18, 0)
|
||||
|
||||
# one exclusion, splits effective timeperiod in two
|
||||
excluded_timeperiods = [
|
||||
TimePeriod(weekday=0, start_time=datetime.time(12, 0), end_time=datetime.time(16, 0))
|
||||
]
|
||||
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
|
||||
assert len(effective_timeperiods) == 2
|
||||
effective_timeperiod = effective_timeperiods[0]
|
||||
assert effective_timeperiod.weekday == time_period.weekday
|
||||
assert effective_timeperiod.start_time == datetime.time(10, 0)
|
||||
assert effective_timeperiod.end_time == datetime.time(12, 0)
|
||||
effective_timeperiod = effective_timeperiods[1]
|
||||
assert effective_timeperiod.weekday == time_period.weekday
|
||||
assert effective_timeperiod.start_time == datetime.time(16, 0)
|
||||
assert effective_timeperiod.end_time == datetime.time(18, 0)
|
||||
|
||||
# several exclusion, splits effective timeperiod into pieces
|
||||
excluded_timeperiods = [
|
||||
TimePeriod(weekday=0, start_time=datetime.time(12, 0), end_time=datetime.time(13, 0)),
|
||||
TimePeriod(weekday=0, start_time=datetime.time(10, 30), end_time=datetime.time(11, 30)),
|
||||
TimePeriod(weekday=0, start_time=datetime.time(16, 30), end_time=datetime.time(17, 00)),
|
||||
]
|
||||
effective_timeperiods = time_period.get_effective_timeperiods(excluded_timeperiods)
|
||||
assert len(effective_timeperiods) == 4
|
||||
|
||||
effective_timeperiod = effective_timeperiods[0]
|
||||
assert effective_timeperiod.weekday == time_period.weekday
|
||||
assert effective_timeperiod.start_time == datetime.time(10, 0)
|
||||
assert effective_timeperiod.end_time == datetime.time(10, 30)
|
||||
|
||||
effective_timeperiod = effective_timeperiods[1]
|
||||
assert effective_timeperiod.weekday == time_period.weekday
|
||||
assert effective_timeperiod.start_time == datetime.time(11, 30)
|
||||
assert effective_timeperiod.end_time == datetime.time(12, 0)
|
||||
|
||||
effective_timeperiod = effective_timeperiods[2]
|
||||
assert effective_timeperiod.weekday == time_period.weekday
|
||||
assert effective_timeperiod.start_time == datetime.time(13, 0)
|
||||
assert effective_timeperiod.end_time == datetime.time(16, 30)
|
||||
|
||||
effective_timeperiod = effective_timeperiods[3]
|
||||
assert effective_timeperiod.weekday == time_period.weekday
|
||||
assert effective_timeperiod.start_time == datetime.time(17, 0)
|
||||
assert effective_timeperiod.end_time == datetime.time(18, 0)
|
||||
|
|
|
@ -2676,6 +2676,123 @@ def test_virtual_agendas_meetings_datetimes_delays_api(app, mock_now):
|
|||
assert len(resp.json['data']) == 12
|
||||
|
||||
|
||||
def test_virtual_agendas_meetings_datetimes_exluded_periods(app, mock_now):
|
||||
foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7)
|
||||
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
|
||||
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
|
||||
TimePeriod.objects.create(
|
||||
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
|
||||
)
|
||||
TimePeriod.objects.create(
|
||||
weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_1,
|
||||
)
|
||||
virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
|
||||
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
|
||||
|
||||
api_url = '/api/agenda/%s/meetings/meeting-type/datetimes/' % (virt_agenda.slug)
|
||||
resp = app.get(api_url)
|
||||
# 8 slots
|
||||
data = resp.json['data']
|
||||
assert len(data) == 8
|
||||
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
||||
assert data[1]['datetime'] == '2017-05-22 10:30:00'
|
||||
assert data[2]['datetime'] == '2017-05-22 11:00:00'
|
||||
|
||||
# exclude one hour the first day
|
||||
tp1 = TimePeriod.objects.create(
|
||||
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
|
||||
)
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 6
|
||||
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
||||
assert data[1]['datetime'] == '2017-05-22 10:30:00'
|
||||
# no more slots the 22 thanks to the exclusion period
|
||||
assert data[2]['datetime'] == '2017-05-23 10:00:00'
|
||||
|
||||
# exclude the second day
|
||||
tp2 = TimePeriod.objects.create(
|
||||
weekday=1, start_time=datetime.time(9, 0), end_time=datetime.time(18, 0), agenda=virt_agenda
|
||||
)
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 2
|
||||
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
||||
assert data[1]['datetime'] == '2017-05-22 10:30:00'
|
||||
|
||||
# go back to no restriction
|
||||
tp1.delete()
|
||||
tp2.delete()
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 8
|
||||
|
||||
# excluded period applies to every desk
|
||||
foo_desk_2 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 2')
|
||||
TimePeriod.objects.create(
|
||||
weekday=3, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_2,
|
||||
)
|
||||
TimePeriod.objects.create(
|
||||
weekday=4, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk_2,
|
||||
)
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 16
|
||||
|
||||
# exclude one hour the first day
|
||||
tp1 = TimePeriod.objects.create(
|
||||
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
|
||||
)
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 14
|
||||
|
||||
# exclude one hour the last day
|
||||
tp2 = TimePeriod.objects.create(
|
||||
weekday=4, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
|
||||
)
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 12
|
||||
|
||||
# go back to no restriction
|
||||
tp1.delete()
|
||||
tp2.delete()
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 16
|
||||
|
||||
# add a second real agenda
|
||||
bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7)
|
||||
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
|
||||
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
|
||||
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
|
||||
bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2')
|
||||
TimePeriod.objects.create(
|
||||
weekday=0, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_1,
|
||||
)
|
||||
TimePeriod.objects.create(
|
||||
weekday=1, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_1,
|
||||
)
|
||||
TimePeriod.objects.create(
|
||||
weekday=2, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_2,
|
||||
)
|
||||
TimePeriod.objects.create(
|
||||
weekday=3, start_time=datetime.time(14, 0), end_time=datetime.time(16, 0), desk=bar_desk_2,
|
||||
)
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 32
|
||||
|
||||
# exclude the first day, 11 to 15 : 4 slots
|
||||
tp1 = TimePeriod.objects.create(
|
||||
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(15, 0), agenda=virt_agenda
|
||||
)
|
||||
resp = app.get(api_url)
|
||||
data = resp.json['data']
|
||||
assert len(data) == 28
|
||||
|
||||
|
||||
def test_virtual_agendas_meetings_exception(app, user, virtual_meetings_agenda):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
real_agenda = virtual_meetings_agenda.real_agendas.first()
|
||||
|
|
Loading…
Reference in New Issue