Project

General

Profile

Download (21.7 KB) Statistics
| Branch: | Tag: | Revision:

calebasse / calebasse / agenda / models.py @ 9ec8b7a2

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