general: exhaustively list available meeting datetimes (#19150)

This commit is contained in:
Frédéric Péters 2017-11-20 01:10:44 +04:00
parent b93098d4e6
commit add27d0933
4 changed files with 103 additions and 11 deletions

View File

@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import fractions
import requests
import vobject
@ -96,6 +97,15 @@ class Agenda(models.Model):
group_ids = [x.id for x in user.groups.all()]
return bool(self.view_role_id in group_ids)
def get_base_meeting_duration(self):
durations = [x.duration for x in MeetingType.objects.filter(agenda=self)]
if not durations:
raise ValueError()
gcd = durations[0]
for duration in durations[1:]:
gcd = fractions.gcd(duration, gcd)
return gcd
def export_json(self):
agenda = {
'label': self.label,
@ -188,7 +198,8 @@ class TimePeriod(models.Model):
}
def get_time_slots(self, min_datetime, max_datetime, meeting_type):
duration = datetime.timedelta(minutes=meeting_type.duration)
meeting_duration = datetime.timedelta(minutes=meeting_type.duration)
duration = datetime.timedelta(minutes=self.desk.agenda.get_base_meeting_duration())
min_datetime = make_naive(min_datetime)
max_datetime = make_naive(max_datetime)
@ -200,19 +211,20 @@ class TimePeriod(models.Model):
event_datetime = real_min_datetime.replace(hour=self.start_time.hour,
minute=self.start_time.minute, second=0, microsecond=0)
while event_datetime < max_datetime:
end_time = event_datetime + duration
end_time = event_datetime + meeting_duration
next_time = event_datetime + duration
if end_time.time() > self.end_time:
# back to morning
event_datetime = event_datetime.replace(hour=self.start_time.hour, minute=self.start_time.minute)
# but next week
event_datetime += datetime.timedelta(days=7)
end_time = event_datetime + duration
next_time = event_datetime + duration
if event_datetime > max_datetime:
break
yield TimeSlot(start_datetime=make_aware(event_datetime), meeting_type=meeting_type, desk=self.desk)
event_datetime = end_time
event_datetime = next_time
class MeetingType(models.Model):

View File

@ -10,7 +10,7 @@ from django.core.management import call_command
from django.core.management.base import CommandError
from chrono.agendas.models import (Agenda, Event, Booking, MeetingType,
Desk, TimePeriodException, ICSError)
Desk, TimePeriod, TimePeriodException, ICSError)
pytestmark = pytest.mark.django_db
@ -330,3 +330,22 @@ END:VCALENDAR"""
mocked_get.return_value = mocked_response
call_command('sync_desks_timeperiod_exceptions')
assert not TimePeriodException.objects.filter(desk=desk).exists()
def test_base_meeting_duration():
agenda = Agenda(label='Meeting', kind='meetings')
agenda.save()
with pytest.raises(ValueError):
agenda.get_base_meeting_duration()
meeting_type = MeetingType(agenda=agenda, label='Foo', duration=30)
meeting_type.save()
assert agenda.get_base_meeting_duration() == 30
meeting_type = MeetingType(agenda=agenda, label='Bar', duration=60)
meeting_type.save()
assert agenda.get_base_meeting_duration() == 30
meeting_type = MeetingType(agenda=agenda, label='Bar', duration=45)
meeting_type.save()
assert agenda.get_base_meeting_duration() == 15

View File

@ -1035,3 +1035,60 @@ def test_agenda_meeting_api_desk_info(app, meetings_agenda, user):
resp = app.post(booking_url2)
assert resp.json['desk']['label'] == desk.label
assert resp.json['desk']['slug'] == desk.slug
def test_agenda_meeting_gcd_durations(app, meetings_agenda, user):
meetings_agenda.maximal_booking_delay = 8
meetings_agenda.save()
time_period = TimePeriod.objects.get(end_time=datetime.time(12, 0))
time_period.end_time = datetime.time(13, 0)
time_period.save()
meeting_type_30 = MeetingType.objects.get(duration=30)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
assert len(resp.json['data']) == 20
meeting_type_20 = MeetingType(agenda=meetings_agenda, label='Lorem', duration=20)
meeting_type_20.save()
assert meetings_agenda.get_base_meeting_duration() == 10
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
assert len(resp.json['data']) == 56
# 16:30 is time period end time (17:00) minus meeting type duration
assert resp.json['data'][-1]['datetime'] == '2017-05-23 16:30:00'
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id)
assert len(resp.json['data']) == 58
assert resp.json['data'][-1]['datetime'] == '2017-05-23 16:40:00'
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
event_id = resp.json['data'][0]['id']
app.authorization = ('Basic', ('john.doe', 'password'))
app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.id, event_id))
assert Booking.objects.count() == 1
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id)
assert len([x for x in resp.json['data'] if not x.get('disabled')]) == 55
event_id = [x for x in resp.json['data'] if not x.get('disabled')][0]['id']
resp = app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.id, event_id))
assert resp.json['datetime'].startswith('2017-05-22T10:30:00')
assert Booking.objects.count() == 2
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
event_id = [x for x in resp.json['data'] if not x.get('disabled')][0]['id']
resp = app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.id, event_id))
assert resp.json['datetime'].startswith('2017-05-22T10:50:00')
assert Booking.objects.count() == 3
# create a gap
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
event_id = [x for x in resp.json['data'] if not x.get('disabled')][1]['id']
resp = app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.id, event_id))
assert resp.json['datetime'].startswith('2017-05-22T11:30:00')
assert Booking.objects.count() == 4
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id)
assert [x for x in resp.json['data'] if not x.get('disabled')][0]['datetime'].startswith('2017-05-22 12:00:00')
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
assert [x for x in resp.json['data'] if not x.get('disabled')][0]['datetime'].startswith('2017-05-22 12:00:00')

View File

@ -12,13 +12,15 @@ def test_timeperiod_time_slots():
agenda = Agenda(label=u'Foo bar', slug='bar')
agenda.save()
desk = Desk.objects.create(label='Desk 1', agenda=agenda)
meeting_type = MeetingType(duration=60, agenda=agenda)
meeting_type.save()
timeperiod = TimePeriod(desk=desk, weekday=0,
start_time=datetime.time(9, 0),
end_time=datetime.time(12, 0))
events = timeperiod.get_time_slots(
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=MeetingType(duration=60))
meeting_type=meeting_type)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 5, 9, 0)
assert events[1].start_datetime.timetuple()[:5] == (2016, 9, 5, 10, 0)
@ -35,7 +37,7 @@ def test_timeperiod_time_slots():
events = timeperiod.get_time_slots(
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=MeetingType(duration=60))
meeting_type=meeting_type)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 6, 9, 0)
assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 27, 11, 0)
@ -48,7 +50,7 @@ def test_timeperiod_time_slots():
events = timeperiod.get_time_slots(
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=MeetingType(duration=60))
meeting_type=meeting_type)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 1, 9, 0)
assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 29, 11, 0)
@ -61,7 +63,7 @@ def test_timeperiod_time_slots():
events = timeperiod.get_time_slots(
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=MeetingType(duration=60))
meeting_type=meeting_type)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 2, 9, 0)
assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 30, 11, 0)
@ -74,20 +76,22 @@ def test_timeperiod_time_slots():
events = timeperiod.get_time_slots(
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=MeetingType(duration=60))
meeting_type=meeting_type)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 3, 9, 0)
assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 24, 11, 0)
assert len(events) == 12
# shorter duration -> double the events
meeting_type.duration = 30
meeting_type.save()
timeperiod = TimePeriod(desk=desk, weekday=5,
start_time=datetime.time(9, 0),
end_time=datetime.time(12, 0))
events = timeperiod.get_time_slots(
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
meeting_type=MeetingType(duration=30))
meeting_type=meeting_type)
events = list(sorted(events, key=lambda x: x.start_datetime))
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 3, 9, 0)
assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 24, 11, 30)