1.1 --- a/docs/wiki/Resources Fri May 13 15:41:38 2016 +0200
1.2 +++ b/docs/wiki/Resources Fri May 13 16:20:29 2016 +0200
1.3 @@ -70,6 +70,26 @@
1.4 periods of the resource's schedule. However, no attempt is made to reject the
1.5 booking of the resource according to the identity of the organiser.
1.6
1.7 +=== Concurrent Reservations ===
1.8 +
1.9 +The `schedule_in_freebusy` function causes a resource to attempt to schedule
1.10 +an event, and by default it rejects requests that involve periods for which
1.11 +the resource is otherwise committed. However, a resource can be allowed to
1.12 +attend (or commit to) multiple concurrent events.
1.13 +
1.14 +By indicating a value as an argument to the function, a kind of capacity or
1.15 +commitment level can be assigned to a resource. For example:
1.16 +
1.17 +{{{
1.18 +schedule_in_freebusy 5
1.19 +}}}
1.20 +
1.21 +This example indicates that a resource can support five different events
1.22 +occupying the same point in time. Applications of such concurrent reservations
1.23 +include things like rooms or resources that can be shared and which have a
1.24 +notion of a capacity that is not immediately exhausted as soon as one event
1.25 +seeks to reserve such a room or resource.
1.26 +
1.27 === Identity Controls ===
1.28
1.29 Although identity controls may be implemented in the e-mail system,
2.1 --- a/imiptools/handlers/scheduling/common.py Fri May 13 15:41:38 2016 +0200
2.2 +++ b/imiptools/handlers/scheduling/common.py Fri May 13 16:20:29 2016 +0200
2.3 @@ -37,4 +37,35 @@
2.4 else:
2.5 return response, _("The recipient is unavailable in the requested period.")
2.6
2.7 +def get_scheduling_conflicts(handler, freebusy, users, attendee=False):
2.8 +
2.9 + """
2.10 + Use the 'handler' to obtain scheduling conflicts within the given 'freebusy'
2.11 + collection involving the given 'users', with the organiser of each period
2.12 + being tested against the users unless 'attendee' is set to a true value
2.13 + (thus testing the attendee of each period against the users instead).
2.14 +
2.15 + Return a dictionary mapping users to the number of conflicts (or concurrent
2.16 + scheduling instances) each user experiences for the current object of the
2.17 + 'handler'.
2.18 + """
2.19 +
2.20 + conflicts = {}
2.21 +
2.22 + for user in users:
2.23 + conflicts[user] = 0
2.24 +
2.25 + for period in handler.get_periods(handler.obj):
2.26 + overlapping = freebusy.get_overlapping(period)
2.27 +
2.28 + # Where scheduling cannot occur, find the busy potential users.
2.29 +
2.30 + if overlapping:
2.31 + for p in overlapping:
2.32 + involved = attendee and p.attendee or p.organiser
2.33 + if conflicts.has_key(involved):
2.34 + conflicts[involved] += 1
2.35 +
2.36 + return conflicts
2.37 +
2.38 # vim: tabstop=4 expandtab shiftwidth=4
3.1 --- a/imiptools/handlers/scheduling/freebusy.py Fri May 13 15:41:38 2016 +0200
3.2 +++ b/imiptools/handlers/scheduling/freebusy.py Fri May 13 16:20:29 2016 +0200
3.3 @@ -30,11 +30,22 @@
3.4 free/busy schedule of a resource, returning an indication of the kind of
3.5 response to be returned.
3.6
3.7 + The 'args' are used to configure the behaviour of the function. If a number
3.8 + is indicated, it is taken to mean the number of concurrent events that can
3.9 + be scheduled at the same point in time, with the default being 1.
3.10 +
3.11 If 'freebusy' is specified, the given collection of busy periods will be
3.12 used to determine whether any conflicts occur. Otherwise, the current user's
3.13 free/busy records will be used.
3.14 """
3.15
3.16 + # Permit the indication that concurrent events may be scheduled.
3.17 +
3.18 + try:
3.19 + max_concurrent = int((args[1:] or ["1"])[0])
3.20 + except ValueError:
3.21 + max_concurrent = 1
3.22 +
3.23 # If newer than any old version, discard old details from the
3.24 # free/busy record and check for suitability.
3.25
3.26 @@ -46,8 +57,14 @@
3.27 # Check the periods against any scheduled events and against
3.28 # any outstanding offers.
3.29
3.30 - scheduled = handler.can_schedule(freebusy, periods)
3.31 - scheduled = scheduled and handler.can_schedule(offers, periods)
3.32 + if max_concurrent == 1:
3.33 + scheduled = handler.can_schedule(freebusy, periods)
3.34 + scheduled = scheduled and handler.can_schedule(offers, periods)
3.35 + else:
3.36 + conflicts = get_scheduling_conflicts(handler, freebusy, [handler.user])
3.37 + scheduled = conflicts[handler.user] < max_concurrent
3.38 + conflicts = get_scheduling_conflicts(handler, offers, [handler.user])
3.39 + scheduled = scheduled and conflicts[handler.user] < max_concurrent
3.40
3.41 return standard_responses(handler, scheduled and "ACCEPTED" or "DECLINED")
3.42
3.43 @@ -57,6 +74,10 @@
3.44 Attempt to schedule the current object of the given 'handler', correcting
3.45 specified datetimes according to the configuration of a resource,
3.46 returning an indication of the kind of response to be returned.
3.47 +
3.48 + The 'args' are used to configure the behaviour of the function. If a number
3.49 + is indicated, it is taken to mean the number of concurrent events that can
3.50 + be scheduled at the same point in time, with the default being 1.
3.51 """
3.52
3.53 obj = handler.obj.copy()
3.54 @@ -96,6 +117,10 @@
3.55 suggesting the next available period in the free/busy records if scheduling
3.56 cannot occur for the requested period, returning an indication of the kind
3.57 of response to be returned.
3.58 +
3.59 + The 'args' are used to configure the behaviour of the function. If a number
3.60 + is indicated, it is taken to mean the number of concurrent events that can
3.61 + be scheduled at the same point in time, with the default being 1.
3.62 """
3.63
3.64 _ = handler.get_translator()