Projet

Général

Profil

Télécharger (22,4 ko) Statistiques
| Branche: | Tag: | Révision:

calebasse / calebasse / agenda / models.py @ 7a2fc3bb

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