1 |
96f613c6
|
Benjamin Dauvergne
|
# -*- coding: utf-8 -*-
|
2 |
ba34dc24
|
Jérôme Schneider
|
|
3 |
76974b6f
|
Benjamin Dauvergne
|
from datetime import datetime, date, timedelta
|
4 |
|
|
from copy import copy
|
5 |
ba34dc24
|
Jérôme Schneider
|
|
6 |
|
|
from django.utils.translation import ugettext_lazy as _
|
7 |
76974b6f
|
Benjamin Dauvergne
|
from django.contrib.auth.models import User
|
8 |
ba34dc24
|
Jérôme Schneider
|
from django.db import models
|
9 |
4ce756c8
|
Benjamin Dauvergne
|
from django import forms
|
10 |
ba34dc24
|
Jérôme Schneider
|
|
11 |
11984ab3
|
Jérôme Schneider
|
from calebasse.agenda import managers
|
12 |
4ce756c8
|
Benjamin Dauvergne
|
from calebasse.utils import weeks_since_epoch, weekday_ranks
|
13 |
e6b125bd
|
Mikaël Ates
|
from calebasse.personnes.models import Holiday
|
14 |
7a0a30d7
|
Benjamin Dauvergne
|
from interval import Interval
|
15 |
ba34dc24
|
Jérôme Schneider
|
|
16 |
|
|
__all__ = (
|
17 |
|
|
'EventType',
|
18 |
|
|
'Event',
|
19 |
76974b6f
|
Benjamin Dauvergne
|
'EventWithAct',
|
20 |
ba34dc24
|
Jérôme Schneider
|
)
|
21 |
|
|
|
22 |
|
|
class EventType(models.Model):
|
23 |
|
|
'''
|
24 |
|
|
Simple ``Event`` classifcation.
|
25 |
|
|
'''
|
26 |
|
|
class Meta:
|
27 |
e39efb4a
|
Jérôme Schneider
|
verbose_name = u'Type d\'événement'
|
28 |
|
|
verbose_name_plural = u'Types d\'événement'
|
29 |
ba34dc24
|
Jérôme Schneider
|
|
30 |
|
|
def __unicode__(self):
|
31 |
|
|
return self.label
|
32 |
|
|
|
33 |
e39efb4a
|
Jérôme Schneider
|
label = models.CharField(_('label'), max_length=50)
|
34 |
68e9f8c1
|
Frédéric Péters
|
rank = models.IntegerField(_('Sorting Rank'), null=True, blank=True, default=0)
|
35 |
e39efb4a
|
Jérôme Schneider
|
|
36 |
84091f2c
|
Jérôme Schneider
|
class Event(models.Model):
|
37 |
ba34dc24
|
Jérôme Schneider
|
'''
|
38 |
76974b6f
|
Benjamin Dauvergne
|
Container model for general agenda events
|
39 |
ba34dc24
|
Jérôme Schneider
|
'''
|
40 |
e39efb4a
|
Jérôme Schneider
|
objects = managers.EventManager()
|
41 |
ba34dc24
|
Jérôme Schneider
|
|
42 |
9ec8b7a2
|
Jérôme Schneider
|
title = models.CharField(_('Title'), max_length=60, blank=True, default="")
|
43 |
a334f1e6
|
Mikaël Ates
|
description = models.TextField(_('Description'), max_length=100, blank=True, null=True)
|
44 |
69a9009e
|
Jérôme Schneider
|
event_type = models.ForeignKey(EventType, verbose_name=u"Type d'événement")
|
45 |
76974b6f
|
Benjamin Dauvergne
|
creator = models.ForeignKey(User, verbose_name=_(u'Créateur'), blank=True, null=True)
|
46 |
|
|
create_date = models.DateTimeField(_(u'Date de création'), auto_now_add=True)
|
47 |
ba34dc24
|
Jérôme Schneider
|
|
48 |
84091f2c
|
Jérôme Schneider
|
services = models.ManyToManyField('ressources.Service',
|
49 |
|
|
null=True, blank=True, default=None)
|
50 |
e39efb4a
|
Jérôme Schneider
|
participants = models.ManyToManyField('personnes.People',
|
51 |
|
|
null=True, blank=True, default=None)
|
52 |
1da8cee7
|
Benjamin Dauvergne
|
room = models.ForeignKey('ressources.Room', blank=True, null=True,
|
53 |
|
|
verbose_name=u'Salle')
|
54 |
e39efb4a
|
Jérôme Schneider
|
|
55 |
5c93596e
|
Benjamin Dauvergne
|
start_datetime = models.DateTimeField(_('Début'), db_index=True)
|
56 |
76974b6f
|
Benjamin Dauvergne
|
end_datetime = models.DateTimeField(_('Fin'), blank=True, null=True)
|
57 |
74102956
|
Benjamin Dauvergne
|
old_ev_id = models.CharField(max_length=8, blank=True, null=True)
|
58 |
|
|
old_rr_id = models.CharField(max_length=8, blank=True, null=True)
|
59 |
44482a62
|
Benjamin Dauvergne
|
# only used when there is no rr id
|
60 |
74102956
|
Benjamin Dauvergne
|
old_rs_id = models.CharField(max_length=8, blank=True, null=True)
|
61 |
a04356e2
|
Benjamin Dauvergne
|
# exception to is mutually exclusive with recurrence_periodicity
|
62 |
|
|
# an exception cannot be periodic
|
63 |
|
|
exception_to = models.ForeignKey('self', related_name='exceptions',
|
64 |
|
|
blank=True, null=True,
|
65 |
|
|
verbose_name=u'Exception à')
|
66 |
|
|
exception_date = models.DateField(blank=True, null=True,
|
67 |
e9fb7b88
|
Benjamin Dauvergne
|
verbose_name=u'Reporté du', db_index=True)
|
68 |
a04356e2
|
Benjamin Dauvergne
|
# canceled can only be used with exception to
|
69 |
e9fb7b88
|
Benjamin Dauvergne
|
canceled = models.BooleanField(_('Annulé'), db_index=True)
|
70 |
76974b6f
|
Benjamin Dauvergne
|
|
71 |
|
|
PERIODS = (
|
72 |
|
|
(1, u'Toutes les semaines'),
|
73 |
|
|
(2, u'Une semaine sur deux'),
|
74 |
|
|
(3, u'Une semaine sur trois'),
|
75 |
|
|
(4, 'Une semaine sur quatre'),
|
76 |
|
|
(5, 'Une semaine sur cinq')
|
77 |
|
|
)
|
78 |
3c5df84d
|
Jérôme Schneider
|
OFFSET = range(0, 4)
|
79 |
4ce756c8
|
Benjamin Dauvergne
|
PERIODICITIES = (
|
80 |
|
|
(1, u'Toutes les semaines'),
|
81 |
|
|
(2, u'Une semaine sur deux'),
|
82 |
|
|
(3, u'Une semaine sur trois'),
|
83 |
|
|
(4, u'Une semaine sur quatre'),
|
84 |
|
|
(5, u'Une semaine sur cinq'),
|
85 |
|
|
(6, u'La première semaine du mois'),
|
86 |
|
|
(7, u'La deuxième semaine du mois'),
|
87 |
|
|
(8, u'La troisième semaine du mois'),
|
88 |
|
|
(9, u'La quatrième semaine du mois'),
|
89 |
|
|
(10, u'La dernière semaine du mois'),
|
90 |
|
|
(11, u'Les semaines paires'),
|
91 |
|
|
(12, u'Les semaines impaires')
|
92 |
|
|
)
|
93 |
|
|
WEEK_RANKS = (
|
94 |
|
|
(0, u'La première semaine du mois'),
|
95 |
|
|
(1, u'La deuxième semaine du mois'),
|
96 |
|
|
(2, u'La troisième semaine du mois'),
|
97 |
|
|
(3, u'La quatrième semaine du mois'),
|
98 |
|
|
(4, u'La dernière semaine du mois')
|
99 |
|
|
)
|
100 |
|
|
PARITIES = (
|
101 |
|
|
(0, u'Les semaines paires'),
|
102 |
|
|
(1, u'Les semaines impaires')
|
103 |
|
|
)
|
104 |
|
|
recurrence_periodicity = models.PositiveIntegerField(
|
105 |
|
|
choices=PERIODICITIES,
|
106 |
|
|
verbose_name=u"Périodicité",
|
107 |
|
|
default=None,
|
108 |
|
|
blank=True,
|
109 |
e9fb7b88
|
Benjamin Dauvergne
|
null=True,
|
110 |
|
|
db_index=True)
|
111 |
|
|
recurrence_week_day = models.PositiveIntegerField(default=0, db_index=True)
|
112 |
76974b6f
|
Benjamin Dauvergne
|
recurrence_week_offset = models.PositiveIntegerField(
|
113 |
|
|
choices=zip(OFFSET, OFFSET),
|
114 |
|
|
verbose_name=u"Décalage en semaines par rapport au 1/1/1970 pour le calcul de période",
|
115 |
|
|
default=0,
|
116 |
|
|
db_index=True)
|
117 |
|
|
recurrence_week_period = models.PositiveIntegerField(
|
118 |
|
|
choices=PERIODS,
|
119 |
|
|
verbose_name=u"Période en semaines",
|
120 |
|
|
default=None,
|
121 |
|
|
blank=True,
|
122 |
|
|
null=True,
|
123 |
|
|
db_index=True)
|
124 |
4ce756c8
|
Benjamin Dauvergne
|
recurrence_week_rank = models.PositiveIntegerField(
|
125 |
|
|
verbose_name=u"Rang de la semaine dans le mois",
|
126 |
|
|
choices=WEEK_RANKS,
|
127 |
e9fb7b88
|
Benjamin Dauvergne
|
blank=True, null=True, db_index=True)
|
128 |
4ce756c8
|
Benjamin Dauvergne
|
recurrence_week_parity = models.PositiveIntegerField(
|
129 |
|
|
choices=PARITIES,
|
130 |
|
|
verbose_name=u"Parité des semaines",
|
131 |
|
|
blank=True,
|
132 |
e9fb7b88
|
Benjamin Dauvergne
|
null=True,
|
133 |
|
|
db_index=True)
|
134 |
76974b6f
|
Benjamin Dauvergne
|
recurrence_end_date = models.DateField(
|
135 |
|
|
verbose_name=_(u'Fin de la récurrence'),
|
136 |
|
|
blank=True, null=True,
|
137 |
|
|
db_index=True)
|
138 |
|
|
|
139 |
4ce756c8
|
Benjamin Dauvergne
|
PERIOD_LIST_TO_FIELDS = [(1, None, None),
|
140 |
|
|
(2, None, None),
|
141 |
|
|
(3, None, None),
|
142 |
|
|
(4, None, None),
|
143 |
|
|
(5, None, None),
|
144 |
|
|
(None, 0, None),
|
145 |
|
|
(None, 1, None),
|
146 |
|
|
(None, 2, None),
|
147 |
|
|
(None, 3, None),
|
148 |
|
|
(None, 4, None),
|
149 |
|
|
(None, None, 0),
|
150 |
|
|
(None, None, 1)
|
151 |
|
|
]
|
152 |
|
|
|
153 |
ba34dc24
|
Jérôme Schneider
|
class Meta:
|
154 |
e39efb4a
|
Jérôme Schneider
|
verbose_name = u'Evénement'
|
155 |
|
|
verbose_name_plural = u'Evénements'
|
156 |
76974b6f
|
Benjamin Dauvergne
|
ordering = ('start_datetime', 'end_datetime', 'title')
|
157 |
a04356e2
|
Benjamin Dauvergne
|
unique_together = (('exception_to', 'exception_date'),)
|
158 |
76974b6f
|
Benjamin Dauvergne
|
|
159 |
|
|
def __init__(self, *args, **kwargs):
|
160 |
|
|
if kwargs.get('start_datetime') and not kwargs.has_key('recurrence_end_date'):
|
161 |
|
|
kwargs['recurrence_end_date'] = kwargs.get('start_datetime').date()
|
162 |
|
|
super(Event, self).__init__(*args, **kwargs)
|
163 |
|
|
|
164 |
|
|
def clean(self):
|
165 |
|
|
'''Initialize recurrence fields if they are not.'''
|
166 |
f16de572
|
Benjamin Dauvergne
|
self.sanitize()
|
167 |
4ce756c8
|
Benjamin Dauvergne
|
if self.recurrence_periodicity:
|
168 |
f16de572
|
Benjamin Dauvergne
|
if self.recurrence_end_date and self.start_datetime and self.recurrence_end_date < self.start_datetime.date():
|
169 |
4ce756c8
|
Benjamin Dauvergne
|
raise forms.ValidationError(u'La date de fin de périodicité doit être postérieure à la date de début.')
|
170 |
|
|
if self.recurrence_week_parity is not None:
|
171 |
|
|
if self.start_datetime:
|
172 |
|
|
week = self.start_datetime.date().isocalendar()[1]
|
173 |
|
|
start_week_parity = week % 2
|
174 |
|
|
if start_week_parity != self.recurrence_week_parity:
|
175 |
|
|
raise forms.ValidationError(u'Le date de départ de la périodicité est en semaine {week}.'.format(week=week))
|
176 |
|
|
if self.recurrence_week_rank is not None and self.start_datetime:
|
177 |
|
|
start_week_ranks = weekday_ranks(self.start_datetime.date())
|
178 |
|
|
if self.recurrence_week_rank not in start_week_ranks:
|
179 |
|
|
raise forms.ValidationError('La date de début de périodicité doit faire partie de la bonne semaine dans le mois.')
|
180 |
76974b6f
|
Benjamin Dauvergne
|
|
181 |
f16de572
|
Benjamin Dauvergne
|
def sanitize(self):
|
182 |
|
|
if self.recurrence_periodicity:
|
183 |
|
|
l = self.PERIOD_LIST_TO_FIELDS[self.recurrence_periodicity-1]
|
184 |
|
|
else:
|
185 |
|
|
l = None, None, None
|
186 |
|
|
self.recurrence_week_period = l[0]
|
187 |
|
|
self.recurrence_week_rank = l[1]
|
188 |
|
|
self.recurrence_week_parity = l[2]
|
189 |
|
|
if self.start_datetime:
|
190 |
|
|
if self.recurrence_periodicity:
|
191 |
|
|
self.recurrence_week_day = self.start_datetime.weekday()
|
192 |
|
|
if self.recurrence_week_period is not None:
|
193 |
|
|
self.recurrence_week_offset = weeks_since_epoch(self.start_datetime) % self.recurrence_week_period
|
194 |
|
|
|
195 |
76974b6f
|
Benjamin Dauvergne
|
def timedelta(self):
|
196 |
|
|
'''Distance between start and end of the event'''
|
197 |
|
|
return self.end_datetime - self.start_datetime
|
198 |
|
|
|
199 |
a04356e2
|
Benjamin Dauvergne
|
def match_date(self, date):
|
200 |
|
|
if self.is_recurring():
|
201 |
|
|
# consider exceptions
|
202 |
|
|
exception = self.get_exceptions_dict().get(date)
|
203 |
|
|
if exception is not None:
|
204 |
|
|
return exception if exception.match_date(date) else None
|
205 |
|
|
if self.canceled:
|
206 |
|
|
return None
|
207 |
|
|
if date.weekday() != self.recurrence_week_day:
|
208 |
|
|
return None
|
209 |
|
|
if self.start_datetime.date() > date:
|
210 |
|
|
return None
|
211 |
|
|
if self.recurrence_end_date and self.recurrence_end_date < date:
|
212 |
|
|
return None
|
213 |
|
|
if self.recurrence_week_period is not None:
|
214 |
|
|
if weeks_since_epoch(date) % self.recurrence_week_period != self.recurrence_week_offset:
|
215 |
|
|
return None
|
216 |
|
|
elif self.recurrence_week_parity is not None:
|
217 |
|
|
if date.isocalendar()[1] % 2 != self.recurrence_week_parity:
|
218 |
|
|
return None
|
219 |
|
|
elif self.recurrence_week_rank is not None:
|
220 |
|
|
if self.recurrence_week_rank not in weekday_ranks(date):
|
221 |
|
|
return None
|
222 |
|
|
else:
|
223 |
|
|
raise NotImplemented
|
224 |
|
|
return self
|
225 |
|
|
else:
|
226 |
ced57915
|
Benjamin Dauvergne
|
return self if date == self.start_datetime.date() else None
|
227 |
a04356e2
|
Benjamin Dauvergne
|
|
228 |
|
|
|
229 |
b8a9f824
|
Benjamin Dauvergne
|
def today_occurrence(self, today=None, match=False, upgrade=True):
|
230 |
76974b6f
|
Benjamin Dauvergne
|
'''For a recurring event compute the today 'Event'.
|
231 |
|
|
|
232 |
|
|
The computed event is the fake one that you cannot save to the database.
|
233 |
ba34dc24
|
Jérôme Schneider
|
'''
|
234 |
76974b6f
|
Benjamin Dauvergne
|
today = today or date.today()
|
235 |
a04356e2
|
Benjamin Dauvergne
|
if self.canceled:
|
236 |
76974b6f
|
Benjamin Dauvergne
|
return None
|
237 |
a04356e2
|
Benjamin Dauvergne
|
if match:
|
238 |
|
|
exception = self.get_exceptions_dict().get(today)
|
239 |
b16fae0e
|
Benjamin Dauvergne
|
if exception:
|
240 |
|
|
if exception.start_datetime.date() == today:
|
241 |
|
|
return exception.today_occurrence(today)
|
242 |
|
|
else:
|
243 |
|
|
return None
|
244 |
4ce756c8
|
Benjamin Dauvergne
|
else:
|
245 |
a04356e2
|
Benjamin Dauvergne
|
exception_or_self = self.match_date(today)
|
246 |
|
|
if exception_or_self is None:
|
247 |
|
|
return None
|
248 |
|
|
if exception_or_self != self:
|
249 |
5c2d0ea2
|
Benjamin Dauvergne
|
return exception_or_self.today_occurrence(today)
|
250 |
b8a9f824
|
Benjamin Dauvergne
|
if self.event_type_id == 1 and type(self) != EventWithAct and upgrade:
|
251 |
|
|
self = self.eventwithact
|
252 |
59136255
|
Benjamin Dauvergne
|
if self.recurrence_periodicity is None:
|
253 |
|
|
return self
|
254 |
76974b6f
|
Benjamin Dauvergne
|
start_datetime = datetime.combine(today, self.start_datetime.timetz())
|
255 |
|
|
end_datetime = start_datetime + self.timedelta()
|
256 |
|
|
event = copy(self)
|
257 |
a04356e2
|
Benjamin Dauvergne
|
event.exception_to = self
|
258 |
|
|
event.exception_date = today
|
259 |
76974b6f
|
Benjamin Dauvergne
|
event.start_datetime = start_datetime
|
260 |
|
|
event.end_datetime = end_datetime
|
261 |
a04356e2
|
Benjamin Dauvergne
|
event.recurrence_periodicity = None
|
262 |
|
|
event.recurrence_week_offset = 0
|
263 |
76974b6f
|
Benjamin Dauvergne
|
event.recurrence_week_period = None
|
264 |
a04356e2
|
Benjamin Dauvergne
|
event.recurrence_week_parity = None
|
265 |
|
|
event.recurrence_week_rank = None
|
266 |
|
|
event.recurrence_end_date = None
|
267 |
76974b6f
|
Benjamin Dauvergne
|
event.parent = self
|
268 |
4ce756c8
|
Benjamin Dauvergne
|
# the returned event is "virtual", it must not be saved
|
269 |
a04356e2
|
Benjamin Dauvergne
|
old_save = event.save
|
270 |
|
|
old_participants = list(self.participants.all())
|
271 |
33522bec
|
Benjamin Dauvergne
|
old_services = list(self.services.all())
|
272 |
a334f1e6
|
Mikaël Ates
|
def save(*args, **kwargs):
|
273 |
a04356e2
|
Benjamin Dauvergne
|
event.id = None
|
274 |
86d19877
|
Benjamin Dauvergne
|
event.event_ptr_id = None
|
275 |
a04356e2
|
Benjamin Dauvergne
|
old_save(*args, **kwargs)
|
276 |
dbe774de
|
Benjamin Dauvergne
|
if hasattr(self, 'exceptions_dict'):
|
277 |
|
|
self.exceptions_dict[event.start_datetime.date()] = event
|
278 |
33522bec
|
Benjamin Dauvergne
|
event.services = old_services
|
279 |
a04356e2
|
Benjamin Dauvergne
|
event.participants = old_participants
|
280 |
083e9b50
|
Benjamin Dauvergne
|
event.save = old_save
|
281 |
76974b6f
|
Benjamin Dauvergne
|
event.save = save
|
282 |
|
|
return event
|
283 |
|
|
|
284 |
|
|
def next_occurence(self, today=None):
|
285 |
|
|
'''Returns the next occurence after today.'''
|
286 |
|
|
today = today or date.today()
|
287 |
|
|
for occurence in self.all_occurences():
|
288 |
|
|
if occurence.start_datetime.date() > today:
|
289 |
|
|
return occurence
|
290 |
|
|
|
291 |
|
|
def is_recurring(self):
|
292 |
|
|
'''Is this event multiple ?'''
|
293 |
4ce756c8
|
Benjamin Dauvergne
|
return self.recurrence_periodicity is not None
|
294 |
76974b6f
|
Benjamin Dauvergne
|
|
295 |
a04356e2
|
Benjamin Dauvergne
|
def get_exceptions_dict(self):
|
296 |
|
|
if not hasattr(self, 'exceptions_dict'):
|
297 |
|
|
self.exceptions_dict = dict()
|
298 |
508c3726
|
Benjamin Dauvergne
|
if self.exception_to_id is None:
|
299 |
b8a9f824
|
Benjamin Dauvergne
|
for exception in self.exceptions.all():
|
300 |
508c3726
|
Benjamin Dauvergne
|
self.exceptions_dict[exception.exception_date] = exception
|
301 |
a04356e2
|
Benjamin Dauvergne
|
return self.exceptions_dict
|
302 |
|
|
|
303 |
76974b6f
|
Benjamin Dauvergne
|
def all_occurences(self, limit=90):
|
304 |
4ce756c8
|
Benjamin Dauvergne
|
'''Returns all occurences of this event as virtual Event objects
|
305 |
76974b6f
|
Benjamin Dauvergne
|
|
306 |
|
|
limit - compute occurrences until limit days in the future
|
307 |
|
|
|
308 |
|
|
Default is to limit to 90 days.
|
309 |
ba34dc24
|
Jérôme Schneider
|
'''
|
310 |
4ce756c8
|
Benjamin Dauvergne
|
if self.recurrence_periodicity is not None:
|
311 |
76974b6f
|
Benjamin Dauvergne
|
day = self.start_datetime.date()
|
312 |
|
|
max_end_date = max(date.today(), self.start_datetime.date()) + timedelta(days=limit)
|
313 |
|
|
end_date = min(self.recurrence_end_date or max_end_date, max_end_date)
|
314 |
a04356e2
|
Benjamin Dauvergne
|
occurrences = []
|
315 |
4ce756c8
|
Benjamin Dauvergne
|
if self.recurrence_week_period is not None:
|
316 |
|
|
delta = timedelta(days=self.recurrence_week_period*7)
|
317 |
|
|
while day <= end_date:
|
318 |
790fd669
|
Benjamin Dauvergne
|
occurrence = self.today_occurrence(day, True)
|
319 |
a04356e2
|
Benjamin Dauvergne
|
if occurrence is not None:
|
320 |
|
|
occurrences.append(occurrence)
|
321 |
4ce756c8
|
Benjamin Dauvergne
|
day += delta
|
322 |
|
|
elif self.recurrence_week_parity is not None:
|
323 |
|
|
delta = timedelta(days=7)
|
324 |
|
|
while day <= end_date:
|
325 |
|
|
if day.isocalendar()[1] % 2 == self.recurrence_week_parity:
|
326 |
790fd669
|
Benjamin Dauvergne
|
occurrence = self.today_occurrence(day, True)
|
327 |
a04356e2
|
Benjamin Dauvergne
|
if occurrence is not None:
|
328 |
|
|
occurrences.append(occurrence)
|
329 |
4ce756c8
|
Benjamin Dauvergne
|
day += delta
|
330 |
|
|
elif self.recurrence_week_rank is not None:
|
331 |
|
|
delta = timedelta(days=7)
|
332 |
|
|
while day <= end_date:
|
333 |
|
|
if self.recurrence_week_rank in weekday_ranks(day):
|
334 |
790fd669
|
Benjamin Dauvergne
|
occurrence = self.today_occurrence(day, True)
|
335 |
a04356e2
|
Benjamin Dauvergne
|
if occurrence is not None:
|
336 |
|
|
occurrences.append(occurrence)
|
337 |
4ce756c8
|
Benjamin Dauvergne
|
day += delta
|
338 |
a04356e2
|
Benjamin Dauvergne
|
for exception in self.exceptions.all():
|
339 |
fcc58fae
|
Jérôme Schneider
|
if not exception.canceled:
|
340 |
|
|
if exception.exception_date != exception.start_datetime.date() or exception.exception_date > end_date:
|
341 |
|
|
occurrences.append(exception.eventwithact if exception.event_type_id == 1 else exception)
|
342 |
a04356e2
|
Benjamin Dauvergne
|
return sorted(occurrences, key=lambda o: o.start_datetime)
|
343 |
ba34dc24
|
Jérôme Schneider
|
else:
|
344 |
a04356e2
|
Benjamin Dauvergne
|
return [self]
|
345 |
ba34dc24
|
Jérôme Schneider
|
|
346 |
4ce756c8
|
Benjamin Dauvergne
|
def save(self, *args, **kwargs):
|
347 |
0acdd88a
|
Benjamin Dauvergne
|
assert self.recurrence_periodicity is None or self.exception_to is None
|
348 |
12a8ef2b
|
Benjamin Dauvergne
|
assert self.exception_to is None or self.exception_to.recurrence_periodicity is not None
|
349 |
|
|
assert self.start_datetime is not None
|
350 |
f16de572
|
Benjamin Dauvergne
|
self.sanitize() # init periodicity fields
|
351 |
4ce756c8
|
Benjamin Dauvergne
|
super(Event, self).save(*args, **kwargs)
|
352 |
5f3ce603
|
Benjamin Dauvergne
|
self.acts_cleaning()
|
353 |
4ce756c8
|
Benjamin Dauvergne
|
|
354 |
fcd786f5
|
Benjamin Dauvergne
|
def delete(self, *args, **kwargs):
|
355 |
a04356e2
|
Benjamin Dauvergne
|
self.canceled = True
|
356 |
5f3ce603
|
Benjamin Dauvergne
|
# save will clean acts
|
357 |
|
|
self.save(*args, **kwargs)
|
358 |
|
|
|
359 |
|
|
def acts_cleaning(self):
|
360 |
|
|
# list of occurences may have changed
|
361 |
|
|
from ..actes.models import Act
|
362 |
|
|
if self.exception_to:
|
363 |
|
|
# maybe a new exception, so look for parent acts with same date
|
364 |
|
|
# as exception date
|
365 |
|
|
acts = Act.objects.filter(models.Q(parent_event=self)
|
366 |
|
|
|models.Q(parent_event=self.exception_to,
|
367 |
|
|
date=self.exception_date))
|
368 |
|
|
else:
|
369 |
|
|
acts = Act.objects.filter(parent_event=self)
|
370 |
|
|
acts = acts.prefetch_related('actvalidationstate_set')
|
371 |
|
|
if acts:
|
372 |
|
|
eventwithact = self.eventwithact
|
373 |
|
|
for act in acts:
|
374 |
1d10b2c6
|
Benjamin Dauvergne
|
if act.is_billed:
|
375 |
|
|
pass
|
376 |
|
|
occurrence = eventwithact.today_occurrence(act.date)
|
377 |
|
|
if occurrence:
|
378 |
|
|
occurrence.update_act(act)
|
379 |
|
|
else:
|
380 |
|
|
act.delete()
|
381 |
fcd786f5
|
Benjamin Dauvergne
|
|
382 |
76974b6f
|
Benjamin Dauvergne
|
def to_interval(self):
|
383 |
|
|
return Interval(self.start_datetime, self.end_datetime)
|
384 |
ba34dc24
|
Jérôme Schneider
|
|
385 |
76974b6f
|
Benjamin Dauvergne
|
def is_event_absence(self):
|
386 |
|
|
return False
|
387 |
ba34dc24
|
Jérôme Schneider
|
|
388 |
e6b125bd
|
Mikaël Ates
|
def get_missing_participants(self):
|
389 |
|
|
missing_participants = []
|
390 |
|
|
for participant in self.participants.all():
|
391 |
|
|
holidays = None
|
392 |
|
|
worker = participant.worker
|
393 |
|
|
holidays = Holiday.objects.for_worker(worker) \
|
394 |
|
|
.for_timed_period(self.start_datetime.date(), self.start_datetime.time(), self.end_datetime.time())
|
395 |
|
|
if holidays:
|
396 |
|
|
missing_participants.append(participant)
|
397 |
|
|
return missing_participants
|
398 |
|
|
|
399 |
3e9b47e9
|
Benjamin Dauvergne
|
RECURRENCE_DESCRIPTION = [
|
400 |
|
|
u'Tous les %s', #(1, None, None),
|
401 |
|
|
u'Un %s sur deux', #(2, None, None),
|
402 |
|
|
u'Un %s sur trois', #(3, None, None),
|
403 |
|
|
u'Un %s sur quatre', #(4, None, None),
|
404 |
|
|
u'Un %s sur cinq', #(5, None, None),
|
405 |
|
|
u'Le premier %s du mois', #(None, 0, None),
|
406 |
|
|
u'Le deuxième %s du mois', #(None, 1, None),
|
407 |
|
|
u'Le troisième %s du mois', #(None, 2, None),
|
408 |
|
|
u'Le quatrième %s du mois', #(None, 3, None),
|
409 |
|
|
u'Le dernier %s du mois', #(None, 4, None),
|
410 |
|
|
u'Les %s les semaines paires', #(None, None, 0),
|
411 |
|
|
u'Les %s les semaines impaires', #(None, None, 1)
|
412 |
|
|
]
|
413 |
|
|
|
414 |
|
|
WEEKDAY_DESRIPTION = [
|
415 |
|
|
u'lundi',
|
416 |
|
|
u'mardi',
|
417 |
|
|
u'mercredi',
|
418 |
|
|
u'jeudi',
|
419 |
|
|
u'vendredi',
|
420 |
|
|
u'samedi',
|
421 |
|
|
u'dimanche'
|
422 |
|
|
]
|
423 |
|
|
|
424 |
|
|
def recurrence_description(self):
|
425 |
|
|
'''Self description of this recurring event'''
|
426 |
|
|
if not self.recurrence_periodicity:
|
427 |
|
|
return None
|
428 |
|
|
parts = []
|
429 |
9ccdb2fe
|
Benjamin Dauvergne
|
parts.append(self.RECURRENCE_DESCRIPTION[self.recurrence_periodicity-1] \
|
430 |
3e9b47e9
|
Benjamin Dauvergne
|
% self.WEEKDAY_DESRIPTION[self.recurrence_week_day])
|
431 |
|
|
if self.recurrence_end_date:
|
432 |
|
|
parts.append(u'du')
|
433 |
|
|
else:
|
434 |
|
|
parts.append(u'à partir du')
|
435 |
|
|
parts.append(self.start_datetime.strftime('%d/%m/%Y'))
|
436 |
|
|
if self.recurrence_end_date:
|
437 |
|
|
parts.append(u'au')
|
438 |
|
|
parts.append(self.recurrence_end_date.strftime('%d/%m/%Y'))
|
439 |
|
|
return u' '.join(parts)
|
440 |
|
|
|
441 |
ba34dc24
|
Jérôme Schneider
|
def __unicode__(self):
|
442 |
76974b6f
|
Benjamin Dauvergne
|
return self.title
|
443 |
ba34dc24
|
Jérôme Schneider
|
|
444 |
6190a8ce
|
Benjamin Dauvergne
|
def __repr__(self):
|
445 |
|
|
return '<Event: on {start_datetime} with {participants}'.format(
|
446 |
|
|
start_datetime=self.start_datetime,
|
447 |
|
|
participants=self.participants.all() if self.id else '<un-saved>')
|
448 |
|
|
|
449 |
ba34dc24
|
Jérôme Schneider
|
|
450 |
76974b6f
|
Benjamin Dauvergne
|
class EventWithActManager(managers.EventManager):
|
451 |
|
|
def create_patient_appointment(self, creator, title, patient,
|
452 |
|
|
doctors=[], act_type=None, service=None, start_datetime=None, end_datetime=None,
|
453 |
|
|
room=None, periodicity=1, until=False):
|
454 |
|
|
appointment = self.create_event(creator=creator,
|
455 |
|
|
title=title,
|
456 |
|
|
event_type=EventType(id=1),
|
457 |
|
|
participants=doctors,
|
458 |
|
|
services=[service],
|
459 |
|
|
start_datetime=start_datetime,
|
460 |
|
|
end_datetime=end_datetime,
|
461 |
|
|
room=room,
|
462 |
|
|
periodicity=periodicity,
|
463 |
|
|
until=until,
|
464 |
|
|
act_type=act_type,
|
465 |
|
|
patient=patient)
|
466 |
|
|
return appointment
|
467 |
|
|
|
468 |
|
|
|
469 |
|
|
class EventWithAct(Event):
|
470 |
|
|
'''An event corresponding to an act.'''
|
471 |
|
|
objects = EventWithActManager()
|
472 |
|
|
act_type = models.ForeignKey('ressources.ActType',
|
473 |
|
|
verbose_name=u'Type d\'acte')
|
474 |
|
|
patient = models.ForeignKey('dossiers.PatientRecord')
|
475 |
ddcbee95
|
Benjamin Dauvergne
|
convocation_sent = models.BooleanField(blank=True,
|
476 |
e9fb7b88
|
Benjamin Dauvergne
|
verbose_name=u'Convoqué', db_index=True)
|
477 |
ba34dc24
|
Jérôme Schneider
|
|
478 |
5c2d0ea2
|
Benjamin Dauvergne
|
|
479 |
ba34dc24
|
Jérôme Schneider
|
@property
|
480 |
76974b6f
|
Benjamin Dauvergne
|
def act(self):
|
481 |
6b0ae820
|
Benjamin Dauvergne
|
for act in self.act_set.all():
|
482 |
|
|
if act.date == self.start_datetime.date():
|
483 |
|
|
return act
|
484 |
433b67bb
|
Benjamin Dauvergne
|
return self.build_act()
|
485 |
76974b6f
|
Benjamin Dauvergne
|
|
486 |
433b67bb
|
Benjamin Dauvergne
|
def get_state(self):
|
487 |
|
|
act = self.act
|
488 |
|
|
if act.id:
|
489 |
|
|
return act.get_state()
|
490 |
|
|
return None
|
491 |
|
|
|
492 |
4b5bbcb1
|
Mikaël Ates
|
def is_absent(self):
|
493 |
|
|
act = self.act
|
494 |
|
|
if act.id:
|
495 |
|
|
return act.is_absent()
|
496 |
|
|
return False
|
497 |
|
|
|
498 |
433b67bb
|
Benjamin Dauvergne
|
def build_act(self):
|
499 |
76974b6f
|
Benjamin Dauvergne
|
from ..actes.models import Act, ActValidationState
|
500 |
433b67bb
|
Benjamin Dauvergne
|
act = Act()
|
501 |
|
|
self.init_act(act)
|
502 |
|
|
old_save = act.save
|
503 |
|
|
def save(*args, **kwargs):
|
504 |
8fa0047b
|
Benjamin Dauvergne
|
act.save = old_save
|
505 |
433b67bb
|
Benjamin Dauvergne
|
old_save(*args, **kwargs)
|
506 |
5534cf1d
|
Jérôme Schneider
|
act.comment = self.description
|
507 |
433b67bb
|
Benjamin Dauvergne
|
act.doctors = self.participants.select_subclasses()
|
508 |
981abc28
|
Jérôme Schneider
|
last_validation_state = ActValidationState.objects.create(
|
509 |
|
|
act=act, state_name='NON_VALIDE',
|
510 |
|
|
author=self.creator, previous_state=None)
|
511 |
|
|
act.last_validation_state = last_validation_state
|
512 |
32551648
|
Jérôme Schneider
|
old_save(*args, **kwargs)
|
513 |
433b67bb
|
Benjamin Dauvergne
|
act.save = save
|
514 |
76974b6f
|
Benjamin Dauvergne
|
return act
|
515 |
|
|
|
516 |
|
|
def update_act(self, act):
|
517 |
|
|
'''Update an act to match details of the meeting'''
|
518 |
433b67bb
|
Benjamin Dauvergne
|
self.init_act(act)
|
519 |
|
|
act.save()
|
520 |
|
|
|
521 |
|
|
def init_act(self, act):
|
522 |
76974b6f
|
Benjamin Dauvergne
|
delta = self.timedelta()
|
523 |
|
|
duration = delta.seconds // 60
|
524 |
|
|
act._duration = duration
|
525 |
|
|
act.act_type = self.act_type
|
526 |
|
|
act.patient = self.patient
|
527 |
046a69ee
|
Benjamin Dauvergne
|
act.parent_event = self
|
528 |
76974b6f
|
Benjamin Dauvergne
|
act.date = self.start_datetime.date()
|
529 |
dfea3c5e
|
Benjamin Dauvergne
|
act.time = self.start_datetime.time()
|
530 |
76974b6f
|
Benjamin Dauvergne
|
|
531 |
|
|
def save(self, *args, **kwargs):
|
532 |
|
|
'''Force event_type to be patient meeting.'''
|
533 |
|
|
self.event_type = EventType(id=1)
|
534 |
|
|
super(EventWithAct, self).save(*args, **kwargs)
|
535 |
ba0e06ef
|
Benjamin Dauvergne
|
|
536 |
5f4c5dd2
|
Frédéric Péters
|
def is_event_absence(self):
|
537 |
76974b6f
|
Benjamin Dauvergne
|
return self.act.is_absent()
|
538 |
|
|
|
539 |
|
|
def __unicode__(self):
|
540 |
|
|
kwargs = {
|
541 |
|
|
'patient': self.patient,
|
542 |
|
|
'start_datetime': self.start_datetime,
|
543 |
|
|
'act_type': self.act_type
|
544 |
|
|
}
|
545 |
|
|
kwargs['doctors'] = ', '.join(map(unicode, self.participants.all())) if self.id else ''
|
546 |
|
|
return u'Rdv le {start_datetime} de {patient} avec {doctors} pour ' \
|
547 |
|
|
'{act_type} ({act_type.id})'.format(**kwargs)
|
548 |
6eff18f3
|
Benjamin Dauvergne
|
|
549 |
|
|
|
550 |
|
|
from django.db.models.signals import m2m_changed
|
551 |
|
|
from django.dispatch import receiver
|
552 |
|
|
|
553 |
|
|
|
554 |
|
|
@receiver(m2m_changed, sender=Event.participants.through)
|
555 |
|
|
def participants_changed(sender, instance, action, **kwargs):
|
556 |
|
|
if action.startswith('post'):
|
557 |
|
|
workers = [ p.worker for p in instance.participants.prefetch_related('worker') ]
|
558 |
|
|
for act in instance.act_set.all():
|
559 |
|
|
act.doctors = workers
|