# HG changeset patch # User Paul Boddie # Date 1463149229 -7200 # Node ID e8de5c7483076d00668775fccc08b4436cd9c4bf # Parent b432c9a47d8a7163cf62ccf28b7997102e1bdec2 Added support for scheduling multiple concurrent reservations. diff -r b432c9a47d8a -r e8de5c748307 docs/wiki/Resources --- a/docs/wiki/Resources Fri May 13 15:41:38 2016 +0200 +++ b/docs/wiki/Resources Fri May 13 16:20:29 2016 +0200 @@ -70,6 +70,26 @@ periods of the resource's schedule. However, no attempt is made to reject the booking of the resource according to the identity of the organiser. +=== Concurrent Reservations === + +The `schedule_in_freebusy` function causes a resource to attempt to schedule +an event, and by default it rejects requests that involve periods for which +the resource is otherwise committed. However, a resource can be allowed to +attend (or commit to) multiple concurrent events. + +By indicating a value as an argument to the function, a kind of capacity or +commitment level can be assigned to a resource. For example: + +{{{ +schedule_in_freebusy 5 +}}} + +This example indicates that a resource can support five different events +occupying the same point in time. Applications of such concurrent reservations +include things like rooms or resources that can be shared and which have a +notion of a capacity that is not immediately exhausted as soon as one event +seeks to reserve such a room or resource. + === Identity Controls === Although identity controls may be implemented in the e-mail system, diff -r b432c9a47d8a -r e8de5c748307 imiptools/handlers/scheduling/common.py --- a/imiptools/handlers/scheduling/common.py Fri May 13 15:41:38 2016 +0200 +++ b/imiptools/handlers/scheduling/common.py Fri May 13 16:20:29 2016 +0200 @@ -37,4 +37,35 @@ else: return response, _("The recipient is unavailable in the requested period.") +def get_scheduling_conflicts(handler, freebusy, users, attendee=False): + + """ + Use the 'handler' to obtain scheduling conflicts within the given 'freebusy' + collection involving the given 'users', with the organiser of each period + being tested against the users unless 'attendee' is set to a true value + (thus testing the attendee of each period against the users instead). + + Return a dictionary mapping users to the number of conflicts (or concurrent + scheduling instances) each user experiences for the current object of the + 'handler'. + """ + + conflicts = {} + + for user in users: + conflicts[user] = 0 + + for period in handler.get_periods(handler.obj): + overlapping = freebusy.get_overlapping(period) + + # Where scheduling cannot occur, find the busy potential users. + + if overlapping: + for p in overlapping: + involved = attendee and p.attendee or p.organiser + if conflicts.has_key(involved): + conflicts[involved] += 1 + + return conflicts + # vim: tabstop=4 expandtab shiftwidth=4 diff -r b432c9a47d8a -r e8de5c748307 imiptools/handlers/scheduling/freebusy.py --- a/imiptools/handlers/scheduling/freebusy.py Fri May 13 15:41:38 2016 +0200 +++ b/imiptools/handlers/scheduling/freebusy.py Fri May 13 16:20:29 2016 +0200 @@ -30,11 +30,22 @@ free/busy schedule of a resource, returning an indication of the kind of response to be returned. + The 'args' are used to configure the behaviour of the function. If a number + is indicated, it is taken to mean the number of concurrent events that can + be scheduled at the same point in time, with the default being 1. + If 'freebusy' is specified, the given collection of busy periods will be used to determine whether any conflicts occur. Otherwise, the current user's free/busy records will be used. """ + # Permit the indication that concurrent events may be scheduled. + + try: + max_concurrent = int((args[1:] or ["1"])[0]) + except ValueError: + max_concurrent = 1 + # If newer than any old version, discard old details from the # free/busy record and check for suitability. @@ -46,8 +57,14 @@ # Check the periods against any scheduled events and against # any outstanding offers. - scheduled = handler.can_schedule(freebusy, periods) - scheduled = scheduled and handler.can_schedule(offers, periods) + if max_concurrent == 1: + scheduled = handler.can_schedule(freebusy, periods) + scheduled = scheduled and handler.can_schedule(offers, periods) + else: + conflicts = get_scheduling_conflicts(handler, freebusy, [handler.user]) + scheduled = conflicts[handler.user] < max_concurrent + conflicts = get_scheduling_conflicts(handler, offers, [handler.user]) + scheduled = scheduled and conflicts[handler.user] < max_concurrent return standard_responses(handler, scheduled and "ACCEPTED" or "DECLINED") @@ -57,6 +74,10 @@ Attempt to schedule the current object of the given 'handler', correcting specified datetimes according to the configuration of a resource, returning an indication of the kind of response to be returned. + + The 'args' are used to configure the behaviour of the function. If a number + is indicated, it is taken to mean the number of concurrent events that can + be scheduled at the same point in time, with the default being 1. """ obj = handler.obj.copy() @@ -96,6 +117,10 @@ suggesting the next available period in the free/busy records if scheduling cannot occur for the requested period, returning an indication of the kind of response to be returned. + + The 'args' are used to configure the behaviour of the function. If a number + is indicated, it is taken to mean the number of concurrent events that can + be scheduled at the same point in time, with the default being 1. """ _ = handler.get_translator()