1
|
|
2
|
from datetime import datetime, timedelta, date, time
|
3
|
from interval import IntervalSet
|
4
|
|
5
|
from django.db.models import Q
|
6
|
from model_utils.managers import InheritanceManager, PassThroughManager, InheritanceQuerySet
|
7
|
|
8
|
from calebasse.agenda.conf import default
|
9
|
from calebasse.utils import weeks_since_epoch, weekday_ranks
|
10
|
from calebasse import agenda
|
11
|
|
12
|
__all__ = (
|
13
|
'EventManager',
|
14
|
)
|
15
|
|
16
|
|
17
|
class EventQuerySet(InheritanceQuerySet):
|
18
|
def for_today(self, today=None):
|
19
|
today = today or date.today()
|
20
|
excluded = self.filter(exceptions__exception_date=today).values_list('id', flat=True)
|
21
|
weeks = weeks_since_epoch(today)
|
22
|
filters = [Q(start_datetime__gte=datetime.combine(today, time()),
|
23
|
start_datetime__lte=datetime.combine(today, time(23,59,59)),
|
24
|
recurrence_periodicity__isnull=True,
|
25
|
canceled=False) ]
|
26
|
base_q = Q(start_datetime__lte=datetime.combine(today, time(23,59,59)),
|
27
|
canceled=False,
|
28
|
recurrence_week_day=today.weekday(),
|
29
|
recurrence_periodicity__isnull=False) & \
|
30
|
(Q(recurrence_end_date__gte=today) |
|
31
|
Q(recurrence_end_date__isnull=True)) & \
|
32
|
~ Q(id__in=excluded)
|
33
|
# week periods
|
34
|
for period in range(1, 6):
|
35
|
filters.append(base_q & Q(recurrence_week_offset=weeks % period,
|
36
|
recurrence_week_period=period))
|
37
|
# week parity
|
38
|
parity = today.isocalendar()[1] % 2
|
39
|
filters.append(base_q & Q(recurrence_week_parity=parity))
|
40
|
# week ranks
|
41
|
filters.append(base_q & Q(recurrence_week_rank__in=weekday_ranks(today)))
|
42
|
qs = self.filter(reduce(Q.__or__, filters))
|
43
|
qs = qs.distinct()
|
44
|
return qs
|
45
|
|
46
|
def today_occurrences(self, today=None):
|
47
|
today = today or date.today()
|
48
|
self = self.for_today(today)
|
49
|
occurences = ( e.today_occurrence(today) for e in self )
|
50
|
return sorted(occurences, key=lambda e: e.start_datetime)
|
51
|
|
52
|
def daily_disponibilities(self, date, events, participant, time_tables,
|
53
|
holidays):
|
54
|
'''Slice the day into quarters between 8:00 and 19:00, and returns the
|
55
|
list of particpants with their status amon free, busy and away for each
|
56
|
hour and quarters.
|
57
|
|
58
|
date - the date of day we slice
|
59
|
occurences - a dictionnary of iterable of events indexed by participants
|
60
|
participants - an iterable of participants
|
61
|
time_tables - a dictionnaty of timetable applying for this day indexed by participants
|
62
|
holidays - a dictionnary of holidays applying for this day indexed by participants
|
63
|
'''
|
64
|
result = dict()
|
65
|
quarter = 0
|
66
|
|
67
|
events_intervals = IntervalSet((o.to_interval() for o in events if not o.is_event_absence()))
|
68
|
|
69
|
timetables_intervals = IntervalSet((t.to_interval(date) for t in time_tables))
|
70
|
holidays_intervals = IntervalSet((h.to_interval(date) for h in holidays))
|
71
|
|
72
|
start_datetime = datetime(date.year, date.month, date.day, 8, 0)
|
73
|
end_datetime = datetime(date.year, date.month, date.day, 8, 15)
|
74
|
while (start_datetime.hour <= 19):
|
75
|
|
76
|
if not result.has_key(start_datetime.hour):
|
77
|
result[start_datetime.hour] = [[], [], [], []]
|
78
|
quarter = 0
|
79
|
interval = IntervalSet.between(start_datetime, end_datetime, False)
|
80
|
mins = quarter * 15
|
81
|
crossed_events = self.overlap_occurences(start_datetime, events)
|
82
|
if len(crossed_events) > 1:
|
83
|
result[start_datetime.hour][quarter].append((mins, {'id': participant.id, 'dispo': 'overlap'}))
|
84
|
elif interval.intersection(events_intervals):
|
85
|
result[start_datetime.hour][quarter].append((mins, {'id': participant.id, 'dispo': 'busy'}))
|
86
|
elif interval.intersection(holidays_intervals):
|
87
|
result[start_datetime.hour][quarter].append((mins, {'id': participant.id, 'dispo': 'busy'}))
|
88
|
elif not interval.intersection(timetables_intervals):
|
89
|
result[start_datetime.hour][quarter].append((mins, {'id': participant.id, 'dispo': 'away'}))
|
90
|
else:
|
91
|
result[start_datetime.hour][quarter].append((mins, {'id': participant.id, 'dispo': 'free'}))
|
92
|
quarter += 1
|
93
|
start_datetime += timedelta(minutes=15)
|
94
|
end_datetime += timedelta(minutes=15)
|
95
|
return result
|
96
|
|
97
|
def overlap_occurences(self, date_time=None, events=None):
|
98
|
"""
|
99
|
returns the list of overlapping event occurences which do not begin and end
|
100
|
at the same time and have the same act type
|
101
|
"""
|
102
|
date_time = date_time or datetime.now()
|
103
|
if events is None:
|
104
|
events = self.today_occurrences(date_time.date())
|
105
|
overlap = filter(lambda e: e.start_datetime <= date_time and e.end_datetime > date_time \
|
106
|
and not e.is_absent(), events)
|
107
|
same_type_events = []
|
108
|
different_overlap = []
|
109
|
for event in overlap:
|
110
|
if different_overlap:
|
111
|
for e in different_overlap:
|
112
|
try:
|
113
|
if event.start_datetime == e.start_datetime and \
|
114
|
event.end_datetime == e.end_datetime and \
|
115
|
event.act_type == e.act_type:
|
116
|
continue
|
117
|
different_overlap.append(event)
|
118
|
except AttributeError:
|
119
|
continue
|
120
|
else:
|
121
|
different_overlap.append(event)
|
122
|
return different_overlap
|
123
|
|
124
|
|
125
|
class EventManager(PassThroughManager.for_queryset_class(EventQuerySet),
|
126
|
InheritanceManager):
|
127
|
""" This class allows you to manage events, appointment, ...
|
128
|
"""
|
129
|
|
130
|
def create_event(self, creator, title, event_type, participants=[], description='',
|
131
|
services=[], start_datetime=None, end_datetime=None, ressource=None, note=None, periodicity=1, until=False, **kwargs):
|
132
|
"""
|
133
|
Convenience function to create an ``Event``, optionally create an
|
134
|
``EventType``.
|
135
|
|
136
|
Args:
|
137
|
event_type: can be either an ``EventType`` object or the label
|
138
|
is either created or retrieved.
|
139
|
participants: List of CalebasseUser
|
140
|
start_datetime: will default to the current hour if ``None``
|
141
|
end_datetime: will default to ``start_datetime`` plus
|
142
|
default.DEFAULT_EVENT_DURATION hour if ``None``
|
143
|
Returns:
|
144
|
Event object
|
145
|
"""
|
146
|
if isinstance(event_type, str):
|
147
|
event_type, created = agenda.models.EventType.objects.get_or_create(
|
148
|
label=event_type
|
149
|
)
|
150
|
if not start_datetime:
|
151
|
now = datetime.now()
|
152
|
start_datetime = datetime.combine(now.date, time(now.hour))
|
153
|
if not end_datetime:
|
154
|
end_datetime = start_datetime + default.DEFAULT_EVENT_DURATION
|
155
|
if until is False:
|
156
|
until = start_datetime.date()
|
157
|
event = self.create(creator=creator,
|
158
|
title=title, start_datetime=start_datetime,
|
159
|
end_datetime=end_datetime, event_type=event_type,
|
160
|
ressource=ressource, recurrence_periodicity=periodicity,
|
161
|
recurrence_end_date=until, **kwargs)
|
162
|
event.services = services
|
163
|
event.participants = participants
|
164
|
return event
|
165
|
|
166
|
def next_appointment(self, patient_record):
|
167
|
qs = self.next_appointments(patient_record)
|
168
|
if qs:
|
169
|
return qs[0]
|
170
|
else:
|
171
|
return None
|
172
|
|
173
|
def next_appointments(self, patient_record, today=None):
|
174
|
from calebasse.actes.models import Act
|
175
|
acts = Act.objects.next_acts(patient_record, today=today) \
|
176
|
.filter(parent_event__isnull=False) \
|
177
|
.select_related()
|
178
|
return [ a.parent_event.today_occurrence(a.date) for a in acts ]
|
179
|
|
180
|
def last_appointment(self, patient_record):
|
181
|
qs = self.last_appointments(patient_record)
|
182
|
if qs:
|
183
|
return qs[0]
|
184
|
else:
|
185
|
return None
|
186
|
|
187
|
def last_appointments(self, patient_record, today=None):
|
188
|
from calebasse.actes.models import Act
|
189
|
acts = Act.objects.last_acts(patient_record, today=today) \
|
190
|
.filter(parent_event__isnull=False) \
|
191
|
.select_related()
|
192
|
return [ a.parent_event.today_occurrence(a.date) for a in acts ]
|