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