Project

General

Profile

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

calebasse / calebasse / personnes / models.py @ 5b90c12e

1
# -*- coding: utf-8 -*-
2

    
3
from datetime import datetime, date, time as datetime_time
4

    
5
from django.db import models
6
from django.db.models import query
7
from django.contrib.auth.models import User
8
from django.template.defaultfilters import date as date_filter
9
from django import forms
10

    
11
import reversion
12
from model_utils.managers import InheritanceManager
13

    
14
from calebasse.models import PhoneNumberField
15
from calebasse.ressources.models import Service, NamedAbstractModel
16
from calebasse.models import (BaseModelMixin, WeekRankField,
17
    PhoneNumberField, ZipCodeField)
18
from calebasse.utils import weeks_since_epoch, weekday_ranks
19

    
20
from interval import Interval
21

    
22
from model_utils import Choices
23
from model_utils.managers import PassThroughManager
24

    
25
class Role(NamedAbstractModel):
26
    users = models.ManyToManyField(User,
27
                verbose_name=u'Utilisateurs', blank=True)
28

    
29
class People(BaseModelMixin, models.Model):
30
    GENDERS =  Choices(
31
            (1, 'Masculin'),
32
            (2, 'Féminin'),
33
    )
34

    
35
    objects = InheritanceManager()
36
    last_name = models.CharField(max_length=128, verbose_name=u'Nom',
37
            db_index=True)
38
    first_name = models.CharField(max_length=128, verbose_name=u'Prénom(s)',
39
        blank=True, null=True)
40
    display_name = models.CharField(max_length=256,
41
            verbose_name=u'Nom complet', editable=False, db_index=True)
42
    gender = models.IntegerField(verbose_name=u"Genre", choices=GENDERS,
43
            max_length=1, blank=True, null=True)
44
    email = models.EmailField(blank=True, null=True)
45
    phone = PhoneNumberField(verbose_name=u"Téléphone", blank=True, null=True)
46

    
47
    def save(self, **kwargs):
48
        if self.first_name:
49
            self.display_name = self.first_name + ' ' + self.last_name.upper()
50
        else:
51
            self.display_name = self.last_name.upper()
52
        super(People, self).save(**kwargs)
53

    
54
    def __unicode__(self):
55
        return self.display_name
56

    
57
    def get_initials(self):
58
        initials = []
59
        if self.first_name:
60
            initials = [name[0].upper() for name in ' '.join(self.first_name.split('-')).split()]
61
        initials += [name[0].upper() for name in ' '.join(self.last_name.split('-')).split()]
62
        return ''.join(initials)
63

    
64
    class Meta:
65
        ordering = ['last_name', 'first_name']
66

    
67
class WorkerQuerySet(query.QuerySet):
68
    def for_service(self, service, type=None):
69
        if type:
70
            return self.filter(enabled=True, services__in=[service], type=type)
71
        else:
72
            return self.filter(enabled=True, services__in=[service])
73

    
74

    
75
class Worker(People):
76
    objects = PassThroughManager.for_queryset_class(WorkerQuerySet)()
77

    
78
    initials = models.CharField(max_length=5, verbose_name=u'Initiales', default='', blank=True)
79
    type = models.ForeignKey('ressources.WorkerType',
80
            verbose_name=u'Type de personnel')
81
    services = models.ManyToManyField('ressources.Service', blank=True, null=True)
82
    enabled = models.BooleanField(verbose_name=u'Actif',
83
                default=True)
84
    old_camsp_id = models.CharField(max_length=256,
85
            verbose_name=u'Ancien ID au CAMSP', blank=True, null=True)
86
    old_cmpp_id = models.CharField(max_length=256,
87
            verbose_name=u'Ancien ID au CMPP', blank=True, null=True)
88
    old_sessad_dys_id = models.CharField(max_length=256,
89
            verbose_name=u'Ancien ID au SESSAD TED', blank=True, null=True)
90
    old_sessad_ted_id = models.CharField(max_length=256,
91
            verbose_name=u'Ancien ID au SESSAD DYS', blank=True, null=True)
92

    
93
    def save(self, **kwargs):
94
        if not self.initials:
95
            self.initials = self.get_initials()
96
        super(Worker, self).save(**kwargs)
97

    
98
    def is_active(self):
99
        return self.enabled
100

    
101
    def is_away(self):
102
        if self.timetable_set.filter(weekday=date.today().weekday()).exists():
103
            return False
104
        return True
105

    
106
    @models.permalink
107
    def get_absolute_url(self):
108
        return ('worker_update', (), {
109
            'service': self.services.all()[0].name.lower(),
110
            'pk': self.pk })
111

    
112
    class Meta:
113
        verbose_name = u'Personnel'
114
        verbose_name_plural = u'Personnels'
115

    
116

    
117
reversion.register(Worker, follow=['people_ptr'])
118
reversion.register(User)
119

    
120
class ExternalWorker(People):
121
    description = models.TextField(blank=True, null=True, default=None)
122
    address = models.CharField(max_length=120,
123
            verbose_name=u"Adresse", blank=True, null=True, default=None)
124
    address_complement = models.CharField(max_length=120,
125
            blank=True,
126
            null=True,
127
            default=None,
128
            verbose_name=u"Complément d'adresse")
129
    zip_code = ZipCodeField(verbose_name=u"Code postal",
130
        blank=True, null=True, default=None)
131
    city = models.CharField(max_length=80, verbose_name=u"Ville",
132
        blank=True, null=True, default=None)
133
    phone_work = PhoneNumberField(verbose_name=u"Téléphone du travail",
134
        blank=True, null=True, default=None)
135
    fax = models.CharField(max_length=30,
136
            blank=True, null=True, default=None)
137
    type = models.ForeignKey('ressources.WorkerType',
138
            verbose_name=u'Spécialité', default=18)
139
    old_id = models.CharField(max_length=256,
140
            verbose_name=u'Ancien ID', blank=True, null=True)
141
    old_service = models.CharField(max_length=256,
142
            verbose_name=u'Ancien Service', blank=True, null=True)
143
    class Meta:
144
        verbose_name = u'Intervenant extérieur'
145
        verbose_name_plural = u'Intervenants extérieurs'
146

    
147
reversion.register(ExternalWorker, follow=['people_ptr'])
148

    
149
class ExternalTherapist(People):
150
    description = models.TextField(blank=True, null=True, default=None)
151
    address = models.CharField(max_length=120,
152
            verbose_name=u"Adresse", blank=True, null=True, default=None)
153
    address_complement = models.CharField(max_length=120,
154
            blank=True,
155
            null=True,
156
            default=None,
157
            verbose_name=u"Complément d'adresse")
158
    zip_code = ZipCodeField(verbose_name=u"Code postal",
159
        blank=True, null=True, default=None)
160
    city = models.CharField(max_length=80, verbose_name=u"Ville",
161
        blank=True, null=True, default=None)
162
    phone_work = PhoneNumberField(verbose_name=u"Téléphone du travail",
163
        blank=True, null=True, default=None)
164
    fax = models.CharField(max_length=30,
165
            blank=True, null=True, default=None)
166
    type = models.ForeignKey('ressources.WorkerType',
167
            verbose_name=u'Spécialité', default=18)
168
    old_id = models.CharField(max_length=256,
169
            verbose_name=u'Ancien ID', blank=True, null=True)
170
    old_service = models.CharField(max_length=256,
171
            verbose_name=u'Ancien Service', blank=True, null=True)
172
    old_id = models.CharField(max_length=256,
173
            verbose_name=u'Ancien ID', blank=True, null=True)
174
    old_service = models.CharField(max_length=256,
175
            verbose_name=u'Ancien Service', blank=True, null=True)
176
    class Meta:
177
        verbose_name = u'Médecin extérieur'
178
        verbose_name_plural = u'Médecins extérieurs'
179

    
180
reversion.register(ExternalTherapist, follow=['people_ptr'])
181

    
182
class UserWorker(BaseModelMixin, models.Model):
183
    user = models.OneToOneField('auth.User')
184
    worker = models.ForeignKey('Worker',
185
            verbose_name=u'Personnel')
186

    
187
    def __unicode__(self):
188
        return u'Lien entre la personne {0} et l\'utilisateur {1}'.format(
189
                self.worker, self.user)
190

    
191
reversion.register(UserWorker)
192

    
193
class SchoolTeacher(People):
194
    schools = models.ManyToManyField('ressources.School')
195
    role = models.ForeignKey('ressources.SchoolTeacherRole')
196

    
197
reversion.register(SchoolTeacher, follow=['user_ptr'])
198

    
199
class TimeTableQuerySet(query.QuerySet):
200
    def current(self, today=None):
201
        if today is None:
202
            today = date.today()
203
        return self.filter(models.Q(start_date__lte=today) | models.Q(start_date__isnull=True)) \
204
                    .filter(models.Q(end_date__gte=today) | models.Q(end_date__isnull=True))
205

    
206
    def for_today(self, today=None):
207
        if today is None:
208
            today = date.today()
209
        qs = self.current(today)
210
        qs = qs.filter(weekday=today.weekday())
211
        filters = []
212
        # week periods
213
        for week_period in range(1,5):
214
            filters.append(models.Q(week_period=week_period,
215
                week_offset=weeks_since_epoch(today) % week_period))
216
        # week parity
217
        parity = today.isocalendar()[1] % 2
218
        filters.append(models.Q(week_parity=parity))
219
        # week ranks
220
        filters.append(models.Q(week_rank__in=weekday_ranks(today)))
221
        qs = qs.filter(reduce(models.Q.__or__, filters))
222
        return qs
223

    
224
PERIODICITIES = (
225
        (1, u'Toutes les semaines'),
226
        (2, u'Une semaine sur deux'),
227
        (3, u'Une semaine sur trois'),
228
        (4, u'Une semaine sur quatre'),
229
        (5, u'Une semaine sur cinq'),
230
        (6, u'La première semaine du mois'),
231
        (7, u'La deuxième semaine du mois'),
232
        (8, u'La troisième semaine du mois'),
233
        (9, u'La quatrième semaine du mois'),
234
        (10, u'La dernière semaine du mois'),
235
        (11, u'Les semaines paires'),
236
        (12, u'Les semaines impaires')
237
)
238

    
239

    
240
class TimeTable(BaseModelMixin, models.Model):
241
    objects = PassThroughManager.for_queryset_class(TimeTableQuerySet)()
242
    worker = models.ForeignKey(Worker,
243
            verbose_name=u'Intervenant')
244
    services = models.ManyToManyField('ressources.Service')
245
    WEEKDAYS = Choices(*enumerate(('lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi',
246
        'samedi', 'dimanche')))
247

    
248
    weekday = models.PositiveIntegerField(
249
        verbose_name=u"Jour de la semaine",
250
        choices=WEEKDAYS)
251
    start_time = models.TimeField(
252
        verbose_name=u'Heure de début')
253
    end_time = models.TimeField(
254
        verbose_name=u'Heure de fin')
255
    start_date = models.DateField(
256
        verbose_name=u'Début',
257
        help_text=u'format: jj/mm/aaaa')
258
    end_date = models.DateField(
259
        verbose_name=u'Fin', blank=True, null=True,
260
        help_text=u'format: jj/mm/aaaa')
261

    
262
    periodicity = models.PositiveIntegerField(
263
            choices=PERIODICITIES,
264
            verbose_name=u"Périodicité",
265
            default=1,
266
            blank=True,
267
            null=True)
268

    
269
    PERIODS = (
270
            (1, u'Toutes les semaines'),
271
            (2, u'Une semaine sur deux'),
272
            (3, u'Une semaine sur trois'),
273
            (4, u'Une semaine sur quatre'),
274
            (5, u'Une semaine sur cinq')
275
    )
276
    OFFSET = range(0,4)
277
    week_offset = models.PositiveIntegerField(
278
            choices=zip(OFFSET, OFFSET),
279
            verbose_name=u"Décalage en semaines par rapport au 1/1/1970 pour le calcul de période",
280
            default=0)
281
    week_period = models.PositiveIntegerField(
282
            choices=PERIODS,
283
            verbose_name=u"Période en semaines",
284
            blank=True, null=True)
285

    
286
    PARITIES = (
287
            (0, u'Les semaines paires'),
288
            (1, u'Les semaines impaires')
289
    )
290
    week_parity = models.PositiveIntegerField(
291
            choices=PARITIES,
292
            verbose_name=u"Parité des semaines",
293
            blank=True, null=True)
294

    
295
    WEEK_RANKS = (
296
            (0, u'La première semaine du mois'),
297
            (1, u'La deuxième semaine du mois'),
298
            (2, u'La troisième semaine du mois'),
299
            (3, u'La quatrième semaine du mois'),
300
            (4, u'La dernière semaine du mois')
301
    )
302

    
303
    week_rank = models.PositiveIntegerField(
304
            verbose_name=u"Rang de la semaine dans le mois",
305
            choices=WEEK_RANKS,
306
            blank=True, null=True)
307

    
308
    def clean(self):
309
        if (self.week_period is None) + (self.week_parity is None) + \
310
                (self.week_rank is None) != 2:
311
            raise forms.ValidationError('Only one periodicity criteria can be used')
312
        if self.week_period and self.start_date:
313
            self.week_offset = weeks_since_epoch(self.start_date) % self.week_period
314

    
315
    def __unicode__(self):
316
        s = u'%s pour au %s le %s de %s à %s' % \
317
                (self.worker, ', '.join(map(unicode, self.services.all())), self.weekday, self.start_time,
318
                        self.end_time)
319
        if self.end_time:
320
            s += u' à partir du %s' % self.start_date
321
        else:
322
            s += u' du %s au %s' % (self.start_data, self.end_date)
323
        return s
324

    
325
    class Meta:
326
        verbose_name = u'Emploi du temps'
327
        verbose_name_plural = u'Emplois du temps'
328

    
329
    def to_interval(self, date):
330
        return Interval(datetime.combine(date, self.start_time),
331
                datetime.combine(date, self.end_time))
332

    
333
class HolidayQuerySet(query.QuerySet):
334
    # To grab group holidays:
335
    # No worker AND
336
    # Either the holiday has no service, that means for all
337
    # Or the user must be in the service of the holiday
338
    def for_worker(self, worker):
339
        filter_query = models.Q(worker=worker) \
340
              | models.Q(worker__isnull=True,
341
                           services = None) \
342
              | models.Q(worker__isnull=True,
343
                           services__in = worker.services.all())
344
        return self.filter(filter_query)
345

    
346
    def for_worker_id(self, worker_id):
347
        worker = None
348
        try:
349
            worker = Worker.objects.get(pk=worker_id)
350
        except:
351
            return None
352
        filter_query = models.Q(worker=worker) \
353
              | models.Q(worker__isnull=True,
354
                           services = None) \
355
              | models.Q(worker__isnull=True,
356
                           services__in = worker.services.all())
357
        return self.filter(filter_query)
358

    
359
    def for_type(self, holiday_type):
360
        return self.filter(holiday_type = holiday_type)
361

    
362
    def for_service(self, service):
363
        return self.filter(worker__isnull = True) \
364
                   .filter(models.Q(services = service)
365
                          |models.Q(services__isnull = True))
366

    
367
    def for_service_workers(self, service):
368
        return self.filter(models.Q(worker__services = [service])
369
                |models.Q(services__in = [service])
370
                |models.Q(worker__isnull=True, services__isnull = True))
371

    
372
    def future(self):
373
        return self.filter(end_date__gte=date.today())
374

    
375
    def today(self, today=None):
376
        today = today or date.today()
377
        return self.filter(start_date__lte=today,
378
                end_date__gte=today)
379

    
380
    def for_period(self, start_date, end_date):
381
        return self.filter(start_date__lte=end_date, end_date__gte=start_date)
382

    
383
    def for_timed_period(self, date, start_time, end_time):
384
        filter_query = models.Q(start_date__lt=date, end_date__gt=date) \
385
            | models.Q(start_date=date, start_time__isnull=True, end_date__gt=date) \
386
            | models.Q(start_date=date, start_time__lt=end_time, end_date__gt=date) \
387
            | models.Q(start_date__lt=date, end_date=date, end_time__isnull=True) \
388
            | models.Q(start_date__lt=date, end_date=date, end_time__gt=start_time) \
389
            | models.Q(start_date=date, end_date=date, start_time__isnull=True, end_time__isnull=True) \
390
            | models.Q(start_date=date, end_date=date, start_time__isnull=True, end_time__gt=start_time) \
391
            | models.Q(start_date=date, end_date=date, start_time__lt=end_time, end_time__isnull=True) \
392
            | models.Q(start_date=date, end_date=date, start_time__lte=start_time, end_time__gt=start_time) \
393
            | models.Q(start_date=date, end_date=date, start_time__lt=end_time, end_time__gte=end_time)
394
        return self.filter(filter_query)
395

    
396
def time2french(time):
397
    if time.minute:
398
        return '{0}h{1}'.format(time.hour, time.minute)
399
    return '{0}h'.format(time.hour)
400

    
401
class Holiday(BaseModelMixin, models.Model):
402
    objects = PassThroughManager().for_queryset_class(HolidayQuerySet)()
403

    
404
    holiday_type = models.ForeignKey('ressources.HolidayType',
405
            verbose_name=u'Type de congé')
406
    worker = models.ForeignKey(Worker, blank=True, null=True,
407
            verbose_name=u"Personnel")
408
    services = models.ManyToManyField(Service, null = True,
409
                                      blank = True, verbose_name = u'Services')
410
    start_date = models.DateField(verbose_name=u"Date de début",
411
        help_text=u'format: jj/mm/aaaa')
412
    end_date = models.DateField(verbose_name=u"Date de fin",
413
        help_text=u'format: jj/mm/aaaa')
414
    start_time = models.TimeField(verbose_name=u"Horaire de début", blank=True,
415
            null=True)
416
    end_time = models.TimeField(verbose_name=u"Horaire de fin", blank=True,
417
            null=True)
418
    comment = models.TextField(verbose_name=u'Commentaire', blank=True)
419

    
420
    class Meta:
421
        verbose_name = u'Congé'
422
        verbose_name_plural = u'Congés'
423
        ordering = ('start_date', 'start_time')
424

    
425
    def is_current(self):
426
        return self.start_date <= date.today() <= self.end_date
427

    
428
    def for_all_services(self):
429
        return self.services.count() == Service.objects.count()
430

    
431
    def __unicode__(self):
432
        ret = ''
433
        if self.start_date == self.end_date:
434
            ret = u'le {0}'.format(date_filter(self.start_date, 'j F Y'))
435
            if self.start_time:
436
                ret += u', à partir de {0}'.format(time2french(self.start_time))
437
            if self.end_time:
438
                ret += u", jusqu'à {0}".format(time2french(self.end_time))
439
        else:
440
            ret = u'du {0}'.format(date_filter(self.start_date, 'j F Y'))
441
            if self.start_time:
442
                ret += u' (à partir de {0})'.format(time2french(self.start_time))
443
            ret += u' au {0}'.format(date_filter(self.end_date, 'j F Y'))
444
            if self.end_time:
445
                ret += u" (jusqu'à {0})".format(time2french(self.end_time))
446
        return ret
447

    
448
    def to_interval(self, date=None):
449
        if date == self.start_date:
450
            start_time = self.start_time or datetime_time(8, 0)
451
        else:
452
            start_time = datetime_time(8, 0)
453
        if date == self.end_date:
454
            end_time = self.end_time or datetime_time(20, 0)
455
        else:
456
            end_time = datetime_time(20, 0)
457
        return Interval(datetime.combine(self.start_date, start_time),
458
                datetime.combine(self.end_date, end_time))
(5-5/8)