Projet

Général

Profil

Télécharger (17,9 ko) Statistiques
| Branche: | Tag: | Révision:

calebasse / calebasse / personnes / models.py @ a01d85be

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
from model_utils.managers import InheritanceManager
12

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

    
19
from interval import Interval
20

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

    
24
from ..middleware.request import get_request
25

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

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

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

    
48
    def save(self, **kwargs):
49
        if self.first_name:
50
            self.display_name = self.first_name + ' ' + self.last_name.upper()
51
        else:
52
            self.display_name = self.last_name.upper()
53
        super(People, self).save(**kwargs)
54
        get_request().record('people-save', '{obj_id} saved by {user} from {ip}', obj_id=self.id)
55

    
56
    def __unicode__(self):
57
        return self.display_name
58

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

    
66
    class Meta:
67
        ordering = ['last_name', 'first_name']
68

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

    
76

    
77
class Worker(People):
78
    objects = PassThroughManager.for_queryset_class(WorkerQuerySet)()
79

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

    
95
    def save(self, **kwargs):
96
        if not self.initials:
97
            self.initials = self.get_initials()
98
        super(Worker, self).save(**kwargs)
99
        get_request().record('worker-save', '{obj_id} saved by {user} from {ip}', obj_id=self.id)
100

    
101
    def is_active(self):
102
        return self.enabled
103

    
104
    def is_away(self):
105
        if self.timetable_set.filter(weekday=date.today().weekday()).exists():
106
            return False
107
        return True
108

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

    
115
    class Meta:
116
        verbose_name = u'Personnel'
117
        verbose_name_plural = u'Personnels'
118

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

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

    
177
class UserWorker(BaseModelMixin, models.Model):
178
    user = models.OneToOneField('auth.User')
179
    worker = models.ForeignKey('Worker',
180
            verbose_name=u'Personnel')
181

    
182
    def __unicode__(self):
183
        return u'Lien entre la personne {0} et l\'utilisateur {1}'.format(
184
                self.worker, self.user)
185

    
186
class SchoolTeacher(People):
187
    schools = models.ManyToManyField('ressources.School')
188
    role = models.ForeignKey('ressources.SchoolTeacherRole')
189

    
190
class TimeTableQuerySet(query.QuerySet):
191
    def current(self, today=None):
192
        if today is None:
193
            today = date.today()
194
        return self.filter(models.Q(start_date__lte=today) | models.Q(start_date__isnull=True)) \
195
                    .filter(models.Q(end_date__gte=today) | models.Q(end_date__isnull=True))
196

    
197
    def for_today(self, today=None):
198
        if today is None:
199
            today = date.today()
200
        qs = self.current(today)
201
        qs = qs.filter(weekday=today.weekday())
202
        filters = []
203
        # week periods
204
        for week_period in range(1,5):
205
            filters.append(models.Q(week_period=week_period,
206
                week_offset=weeks_since_epoch(today) % week_period))
207
        # week parity
208
        parity = today.isocalendar()[1] % 2
209
        filters.append(models.Q(week_parity=parity))
210
        # week ranks
211
        filters.append(models.Q(week_rank__in=weekday_ranks(today)))
212
        qs = qs.filter(reduce(models.Q.__or__, filters))
213
        return qs
214

    
215
PERIODICITIES = (
216
        (1, u'Toutes les semaines'),
217
        (2, u'Une semaine sur deux'),
218
        (3, u'Une semaine sur trois'),
219
        (4, u'Une semaine sur quatre'),
220
        (5, u'Une semaine sur cinq'),
221
        (6, u'La première semaine du mois'),
222
        (7, u'La deuxième semaine du mois'),
223
        (8, u'La troisième semaine du mois'),
224
        (9, u'La quatrième semaine du mois'),
225
        (10, u'La dernière semaine du mois'),
226
        (11, u'Les semaines paires'),
227
        (12, u'Les semaines impaires')
228
)
229

    
230

    
231
class TimeTable(BaseModelMixin, models.Model):
232
    objects = PassThroughManager.for_queryset_class(TimeTableQuerySet)()
233
    worker = models.ForeignKey(Worker,
234
            verbose_name=u'Intervenant')
235
    services = models.ManyToManyField('ressources.Service')
236
    WEEKDAYS = Choices(*enumerate(('lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi',
237
        'samedi', 'dimanche')))
238

    
239
    weekday = models.PositiveIntegerField(
240
        verbose_name=u"Jour de la semaine",
241
        choices=WEEKDAYS)
242
    start_time = models.TimeField(
243
        verbose_name=u'Heure de début')
244
    end_time = models.TimeField(
245
        verbose_name=u'Heure de fin')
246
    start_date = models.DateField(
247
        verbose_name=u'Début',
248
        help_text=u'format: jj/mm/aaaa')
249
    end_date = models.DateField(
250
        verbose_name=u'Fin', blank=True, null=True,
251
        help_text=u'format: jj/mm/aaaa')
252

    
253
    periodicity = models.PositiveIntegerField(
254
            choices=PERIODICITIES,
255
            verbose_name=u"Périodicité",
256
            default=1,
257
            blank=True,
258
            null=True)
259

    
260
    PERIODS = (
261
            (1, u'Toutes les semaines'),
262
            (2, u'Une semaine sur deux'),
263
            (3, u'Une semaine sur trois'),
264
            (4, u'Une semaine sur quatre'),
265
            (5, u'Une semaine sur cinq')
266
    )
267
    OFFSET = range(0,4)
268
    week_offset = models.PositiveIntegerField(
269
            choices=zip(OFFSET, OFFSET),
270
            verbose_name=u"Décalage en semaines par rapport au 1/1/1970 pour le calcul de période",
271
            default=0)
272
    week_period = models.PositiveIntegerField(
273
            choices=PERIODS,
274
            verbose_name=u"Période en semaines",
275
            blank=True, null=True)
276

    
277
    PARITIES = (
278
            (0, u'Les semaines paires'),
279
            (1, u'Les semaines impaires')
280
    )
281
    week_parity = models.PositiveIntegerField(
282
            choices=PARITIES,
283
            verbose_name=u"Parité des semaines",
284
            blank=True, null=True)
285

    
286
    WEEK_RANKS = (
287
            (0, u'La première semaine du mois'),
288
            (1, u'La deuxième semaine du mois'),
289
            (2, u'La troisième semaine du mois'),
290
            (3, u'La quatrième semaine du mois'),
291
            (4, u'La dernière semaine du mois')
292
    )
293

    
294
    week_rank = models.PositiveIntegerField(
295
            verbose_name=u"Rang de la semaine dans le mois",
296
            choices=WEEK_RANKS,
297
            blank=True, null=True)
298

    
299
    def clean(self):
300
        if (self.week_period is None) + (self.week_parity is None) + \
301
                (self.week_rank is None) != 2:
302
            raise forms.ValidationError('Only one periodicity criteria can be used')
303
        if self.week_period and self.start_date:
304
            self.week_offset = weeks_since_epoch(self.start_date) % self.week_period
305

    
306
    def __unicode__(self):
307
        s = u'%s pour au %s le %s de %s à %s' % \
308
                (self.worker, ', '.join(map(unicode, self.services.all())), self.weekday, self.start_time,
309
                        self.end_time)
310
        if self.end_time:
311
            s += u' à partir du %s' % self.start_date
312
        else:
313
            s += u' du %s au %s' % (self.start_data, self.end_date)
314
        return s
315

    
316
    class Meta:
317
        verbose_name = u'Emploi du temps'
318
        verbose_name_plural = u'Emplois du temps'
319

    
320
    def to_interval(self, date):
321
        return Interval(datetime.combine(date, self.start_time),
322
                datetime.combine(date, self.end_time))
323

    
324
class HolidayQuerySet(query.QuerySet):
325
    # To grab group holidays:
326
    # No worker AND
327
    # Either the holiday has no service, that means for all
328
    # Or the user must be in the service of the holiday
329
    def for_worker(self, worker):
330
        filter_query = models.Q(worker=worker) \
331
              | models.Q(worker__isnull=True,
332
                           services = None) \
333
              | models.Q(worker__isnull=True,
334
                           services__in = worker.services.all())
335
        return self.filter(filter_query).distinct()
336

    
337
    def for_worker_id(self, worker_id):
338
        worker = None
339
        try:
340
            worker = Worker.objects.get(pk=worker_id)
341
        except:
342
            return None
343
        filter_query = models.Q(worker=worker) \
344
              | models.Q(worker__isnull=True,
345
                           services = None) \
346
              | models.Q(worker__isnull=True,
347
                           services__in = worker.services.all())
348
        return self.filter(filter_query)
349

    
350
    def for_type(self, holiday_type):
351
        return self.filter(holiday_type = holiday_type)
352

    
353
    def for_service(self, service):
354
        return self.filter(worker__isnull = True) \
355
                   .filter(models.Q(services = service)
356
                          |models.Q(services__isnull = True))
357

    
358
    def for_service_workers(self, service):
359
        return self.filter(models.Q(worker__services = [service])
360
                |models.Q(services__in = [service])
361
                |models.Q(worker__isnull=True, services__isnull = True))
362

    
363
    def future(self):
364
        return self.filter(end_date__gte=date.today())
365

    
366
    def today(self, today=None):
367
        today = today or date.today()
368
        return self.filter(start_date__lte=today,
369
                end_date__gte=today)
370

    
371
    def for_period(self, start_date, end_date):
372
        return self.filter(start_date__lte=end_date, end_date__gte=start_date)
373

    
374
    def for_timed_period(self, date, start_time, end_time):
375
        filter_query = models.Q(start_date__lt=date, end_date__gt=date) \
376
            | models.Q(start_date=date, start_time__isnull=True, end_date__gt=date) \
377
            | models.Q(start_date=date, start_time__lt=end_time, end_date__gt=date) \
378
            | models.Q(start_date__lt=date, end_date=date, end_time__isnull=True) \
379
            | models.Q(start_date__lt=date, end_date=date, end_time__gt=start_time) \
380
            | models.Q(start_date=date, end_date=date, start_time__isnull=True, end_time__isnull=True) \
381
            | models.Q(start_date=date, end_date=date, start_time__isnull=True, end_time__gt=start_time) \
382
            | models.Q(start_date=date, end_date=date, start_time__lt=end_time, end_time__isnull=True) \
383
            | models.Q(start_date=date, end_date=date, start_time__lte=start_time, end_time__gt=start_time) \
384
            | models.Q(start_date=date, end_date=date, start_time__lt=end_time, end_time__gte=end_time)
385
        return self.filter(filter_query)
386

    
387
def time2french(time):
388
    if time.minute:
389
        return '{0}h{1}'.format(time.hour, time.minute)
390
    return '{0}h'.format(time.hour)
391

    
392
class Holiday(BaseModelMixin, models.Model):
393
    objects = PassThroughManager().for_queryset_class(HolidayQuerySet)()
394

    
395
    holiday_type = models.ForeignKey('ressources.HolidayType',
396
            verbose_name=u'Type de congé')
397
    worker = models.ForeignKey(Worker, blank=True, null=True,
398
            verbose_name=u"Personnel")
399
    services = models.ManyToManyField(Service, blank = False, null = False,
400
                                      verbose_name = u'Services')
401
    start_date = models.DateField(verbose_name=u"Date de début",
402
        help_text=u'format: jj/mm/aaaa')
403
    end_date = models.DateField(verbose_name=u"Date de fin",
404
        help_text=u'format: jj/mm/aaaa')
405
    start_time = models.TimeField(verbose_name=u"Horaire de début", blank=True,
406
            null=True)
407
    end_time = models.TimeField(verbose_name=u"Horaire de fin", blank=True,
408
            null=True)
409
    comment = models.TextField(verbose_name=u'Commentaire', blank=True)
410

    
411
    class Meta:
412
        verbose_name = u'Congé'
413
        verbose_name_plural = u'Congés'
414
        ordering = ('start_date', 'start_time')
415

    
416
    def is_current(self):
417
        return self.start_date <= date.today() <= self.end_date
418

    
419
    def for_all_services(self):
420
        return self.services.count() == Service.objects.count()
421

    
422
    def __unicode__(self):
423
        ret = ''
424
        if self.start_date == self.end_date:
425
            ret = u'le {0}'.format(date_filter(self.start_date, 'j F Y'))
426
            if self.start_time:
427
                ret += u', à partir de {0}'.format(time2french(self.start_time))
428
            if self.end_time:
429
                ret += u", jusqu'à {0}".format(time2french(self.end_time))
430
        else:
431
            ret = u'du {0}'.format(date_filter(self.start_date, 'j F Y'))
432
            if self.start_time:
433
                ret += u' (à partir de {0})'.format(time2french(self.start_time))
434
            ret += u' au {0}'.format(date_filter(self.end_date, 'j F Y'))
435
            if self.end_time:
436
                ret += u" (jusqu'à {0})".format(time2french(self.end_time))
437
        return ret
438

    
439
    def to_interval(self, date=None):
440
        if date == self.start_date:
441
            start_time = self.start_time or datetime_time(8, 0)
442
        else:
443
            start_time = datetime_time(8, 0)
444
        if date == self.end_date:
445
            end_time = self.end_time or datetime_time(20, 0)
446
        else:
447
            end_time = datetime_time(20, 0)
448
        return Interval(datetime.combine(self.start_date, start_time),
449
                datetime.combine(self.end_date, end_time))
(5-5/8)