rh.api.photo sans logique accès
[auf_rh_dae.git] / project / rh / models.py
1 # -=- encoding: utf-8 -=-
2
3 from django.core.files.storage import FileSystemStorage
4 from django.db import models
5 from django.conf import settings
6 from auf.django.metadata.models import AUFMetadata
7 from auf.django.metadata.managers import NoDeleteManager
8 import datamaster_modeles.models as ref
9 from validators import validate_date_passee
10
11 # Constantes
12 HELP_TEXT_DATE = "format: aaaa-mm-jj"
13 REGIME_TRAVAIL_DEFAULT = 100.00
14 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = 35.00
15
16
17 # Upload de fichiers
18 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
19 base_url=settings.PRIVE_MEDIA_URL)
20
21 def poste_piece_dispatch(instance, filename):
22 path = "poste/%s/%s" % (instance.poste_id, filename)
23 return path
24
25 def dossier_piece_dispatch(instance, filename):
26 path = "dossier/%s/%s" % (instance.dossier_id, filename)
27 return path
28
29 def employe_piece_dispatch(instance, filename):
30 path = "employe/%s/%s" % (instance.employe_id, filename)
31 return path
32
33
34 class Commentaire(AUFMetadata):
35 texte = models.TextField()
36 owner = models.ForeignKey('auth.User', db_column='owner', related_name='+')
37
38 class Meta:
39 abstract = True
40 ordering = ['-date_creation']
41
42 def __unicode__(self):
43 return u'%s' % (self.texte)
44
45
46 ### POSTE
47
48 POSTE_APPEL_CHOICES = (
49 ('interne', 'Interne'),
50 ('externe', 'Externe'),
51 )
52
53 class PosteManager(NoDeleteManager):
54 def get_query_set(self):
55 return super(PosteManager, self).get_query_set().select_related('implantation')
56
57 class Poste_(AUFMetadata):
58 """Un Poste est un emploi (job) à combler dans une implantation.
59 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
60 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
61 """
62
63 objects = PosteManager()
64
65 # Identification
66 nom = models.CharField(max_length=255,
67 verbose_name="Titre du poste", )
68 nom_feminin = models.CharField(max_length=255,
69 verbose_name="Titre du poste (au féminin)",
70 null=True)
71 implantation = models.ForeignKey(ref.Implantation,
72 db_column='implantation', related_name='+')
73 type_poste = models.ForeignKey('TypePoste', db_column='type_poste',
74 related_name='+',
75 null=True)
76 service = models.ForeignKey('Service', db_column='service', null=True,
77 related_name='+',
78 verbose_name="Direction/Service/Pôle support",
79 default=1) # default = Rectorat
80 responsable = models.ForeignKey('Poste', db_column='responsable',
81 related_name='+', null=True,
82 verbose_name="Poste du responsable",
83 default=149) # default = Recteur
84
85 # Contrat
86 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
87 default=REGIME_TRAVAIL_DEFAULT, null=True,
88 verbose_name="Temps de travail",
89 help_text="% du temps complet")
90 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
91 decimal_places=2, null=True,
92 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
93 verbose_name="Nb. heures par semaine")
94
95 # Recrutement
96 local = models.NullBooleanField(verbose_name="Local", default=True,
97 null=True, blank=True)
98 expatrie = models.NullBooleanField(verbose_name="Expatrié", default=False,
99 null=True, blank=True)
100 mise_a_disposition = models.NullBooleanField(
101 verbose_name="Mise à disposition",
102 null=True, default=False)
103 appel = models.CharField(max_length=10, null=True,
104 verbose_name="Appel à candidature",
105 choices=POSTE_APPEL_CHOICES,
106 default='interne')
107
108 # Rémunération
109 classement_min = models.ForeignKey('Classement',
110 db_column='classement_min', related_name='+',
111 null=True, blank=True)
112 classement_max = models.ForeignKey('Classement',
113 db_column='classement_max', related_name='+',
114 null=True, blank=True)
115 valeur_point_min = models.ForeignKey('ValeurPoint',
116 db_column='valeur_point_min', related_name='+',
117 null=True, blank=True)
118 valeur_point_max = models.ForeignKey('ValeurPoint',
119 db_column='valeur_point_max', related_name='+',
120 null=True, blank=True)
121 devise_min = models.ForeignKey('Devise', db_column='devise_min', null=True,
122 related_name='+', default=5)
123 devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
124 related_name='+', default=5)
125 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
126 null=True, default=0)
127 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
128 null=True, default=0)
129 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
130 null=True, default=0)
131 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
132 null=True, default=0)
133 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
134 null=True, default=0)
135 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
136 null=True, default=0)
137
138 # Comparatifs de rémunération
139 devise_comparaison = models.ForeignKey('Devise', null=True,
140 db_column='devise_comparaison',
141 related_name='+',
142 default=5)
143 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
144 null=True, blank=True)
145 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
146 null=True, blank=True)
147 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
148 null=True, blank=True)
149 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
150 null=True, blank=True)
151 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
152 null=True, blank=True)
153 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
154 null=True, blank=True)
155 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
156 null=True, blank=True)
157 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
158 null=True, blank=True)
159 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
160 null=True, blank=True)
161 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
162 null=True, blank=True)
163
164 # Justification
165 justification = models.TextField(null=True, blank=True)
166
167 # Autres Metadata
168 date_validation = models.DateTimeField(null=True, blank=True) # de dae
169 date_debut = models.DateField(verbose_name="Date de début",
170 help_text=HELP_TEXT_DATE,
171 null=True, blank=True)
172 date_fin = models.DateField(verbose_name="Date de fin",
173 help_text=HELP_TEXT_DATE,
174 null=True, blank=True)
175
176 class Meta:
177 abstract = True
178 ordering = ['implantation__nom', 'nom']
179 verbose_name = "Poste"
180 verbose_name_plural = "Postes"
181
182 def __unicode__(self):
183 representation = u'%s - %s [%s]' % (self.implantation, self.nom,
184 self.id)
185 if self.is_vacant():
186 representation = representation + u' (vacant)'
187 return representation
188
189 def is_vacant(self):
190 # TODO : si existe un dossier actif pour ce poste, return False
191 # self.dossier_set.all() fonctionne pas
192 return False
193
194
195 class Poste(Poste_):
196 __doc__ = Poste_.__doc__
197
198
199 class Poste(Poste_):
200 __doc__ = Poste_.__doc__
201
202
203 POSTE_FINANCEMENT_CHOICES = (
204 ('A', 'A - Frais de personnel'),
205 ('B', 'B - Projet(s)-Titre(s)'),
206 ('C', 'C - Autre')
207 )
208
209
210 class PosteFinancement_(models.Model):
211 """Pour un Poste, structure d'informations décrivant comment on prévoit
212 financer ce Poste.
213 """
214 poste = models.ForeignKey('Poste', db_column='poste',
215 related_name='%(app_label)s_financements')
216 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
217 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
218 help_text="ex.: 33.33 % (décimale avec point)")
219 commentaire = models.TextField(
220 help_text="Spécifiez la source de financement.")
221
222 class Meta:
223 abstract = True
224 ordering = ['type']
225
226 def __unicode__(self):
227 return u'%s : %s %' % (self.type, self.pourcentage)
228
229
230 class PosteFinancement(PosteFinancement_):
231 __doc__ = PosteFinancement_.__doc__
232
233
234 class PostePiece(models.Model):
235 """Documents relatifs au Poste.
236 Ex.: Description de poste
237 """
238 poste = models.ForeignKey('Poste', db_column='poste',
239 related_name='pieces')
240 nom = models.CharField(verbose_name="Nom", max_length=255)
241 fichier = models.FileField(verbose_name="Fichier",
242 upload_to=poste_piece_dispatch,
243 storage=storage_prive)
244
245 class Meta:
246 ordering = ['nom']
247
248 def __unicode__(self):
249 return u'%s' % (self.nom)
250
251 class PosteComparaison(models.Model):
252 """
253 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
254 """
255 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
256 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
257 nom = models.CharField(verbose_name="Poste", max_length=255, null=True, blank=True)
258 montant = models.IntegerField(null=True)
259 devise = models.ForeignKey("Devise", default=5, related_name='+', null=True, blank=True)
260
261 def taux_devise(self):
262 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
263 if len(liste_taux) == 0:
264 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
265 else:
266 return liste_taux[0].taux
267
268 def montant_euros(self):
269 return round(float(self.montant) * float(self.taux_devise()), 2)
270
271
272 class PosteCommentaire(Commentaire):
273 poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
274
275
276 ### EMPLOYÉ/PERSONNE
277
278 GENRE_CHOICES = (
279 ('M', 'Homme'),
280 ('F', 'Femme'),
281 )
282 SITUATION_CHOICES = (
283 ('C', 'Célibataire'),
284 ('F', 'Fiancé'),
285 ('M', 'Marié'),
286 )
287
288 class Employe(AUFMetadata):
289 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
290 Dossiers qu'il occupe ou a occupé de Postes.
291
292 Cette classe aurait pu avantageusement s'appeler Personne car la notion
293 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
294 """
295 # Identification
296 nom = models.CharField(max_length=255)
297 prenom = models.CharField(max_length=255, verbose_name="Prénom")
298 nom_affichage = models.CharField(max_length=255,
299 verbose_name="Nom d'affichage",
300 null=True, blank=True)
301 nationalite = models.ForeignKey(ref.Pays, to_field='code',
302 db_column='nationalite',
303 related_name='employes_nationalite',
304 verbose_name="Nationalité")
305 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
306 verbose_name="Date de naissance",
307 validators=[validate_date_passee],
308 null=True, blank=True)
309 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
310
311 # Infos personnelles
312 situation_famille = models.CharField(max_length=1,
313 choices=SITUATION_CHOICES,
314 verbose_name="Situation familiale",
315 null=True, blank=True)
316 date_entree = models.DateField(verbose_name="Date d'entrée à l'AUF",
317 help_text=HELP_TEXT_DATE,
318 null=True, blank=True)
319
320 # Coordonnées
321 tel_domicile = models.CharField(max_length=255,
322 verbose_name="Tél. domicile",
323 null=True, blank=True)
324 tel_cellulaire = models.CharField(max_length=255,
325 verbose_name="Tél. cellulaire",
326 null=True, blank=True)
327 adresse = models.CharField(max_length=255, null=True, blank=True)
328 ville = models.CharField(max_length=255, null=True, blank=True)
329 province = models.CharField(max_length=255, null=True, blank=True)
330 code_postal = models.CharField(max_length=255, null=True, blank=True)
331 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
332 related_name='employes',
333 null=True, blank=True)
334
335 class Meta:
336 ordering = ['nom_affichage','nom','prenom']
337 verbose_name = "Employé"
338 verbose_name_plural = "Employés"
339
340 def __unicode__(self):
341 return u'%s' % (self.get_nom())
342
343 def get_nom(self):
344 nom_affichage = self.nom_affichage
345 if not nom_affichage:
346 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
347 return nom_affichage
348
349 def civilite(self):
350 civilite = u''
351 if self.genre.upper() == u'M':
352 civilite = u'M.'
353 elif self.genre.upper() == u'F':
354 civilite = u'Mme'
355 return civilite
356
357 def url_photo(self):
358 """Retourne l'URL du service retournant la photo de l'Employe.
359 Équivalent reverse url 'rh_photo' avec id en param.
360 """
361 from django.core.urlresolvers import reverse
362 return reverse('rh_photo', kwargs={'id':self.id})
363
364 class EmployePiece(models.Model):
365 """Documents relatifs à un employé.
366 Ex.: CV...
367 """
368 employe = models.ForeignKey('Employe', db_column='employe',
369 related_name='+')
370 nom = models.CharField(verbose_name="Nom", max_length=255)
371 fichier = models.FileField(verbose_name="Fichier",
372 upload_to=employe_piece_dispatch,
373 storage=storage_prive)
374
375 class Meta:
376 ordering = ['nom']
377
378 def __unicode__(self):
379 return u'%s' % (self.nom)
380
381 class EmployeCommentaire(Commentaire):
382 employe = models.ForeignKey('Employe', db_column='employe',
383 related_name='+')
384
385
386 LIEN_PARENTE_CHOICES = (
387 ('Conjoint', 'Conjoint'),
388 ('Conjointe', 'Conjointe'),
389 ('Fille', 'Fille'),
390 ('Fils', 'Fils'),
391 )
392
393 class AyantDroit(AUFMetadata):
394 """Personne en relation avec un Employe.
395 """
396 # Identification
397 nom = models.CharField(max_length=255)
398 prenom = models.CharField(max_length=255,
399 verbose_name="Prénom",)
400 nom_affichage = models.CharField(max_length=255,
401 verbose_name="Nom d'affichage",
402 null=True, blank=True)
403 nationalite = models.ForeignKey(ref.Pays, to_field='code',
404 db_column='nationalite',
405 related_name='ayantdroits_nationalite',
406 verbose_name="Nationalité")
407 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
408 verbose_name="Date de naissance",
409 validators=[validate_date_passee],
410 null=True, blank=True)
411 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
412
413 # Relation
414 employe = models.ForeignKey('Employe', db_column='employe',
415 related_name='ayantdroits',
416 verbose_name="Employé")
417 lien_parente = models.CharField(max_length=10,
418 choices=LIEN_PARENTE_CHOICES,
419 verbose_name="Lien de parenté",
420 null=True, blank=True)
421
422 class Meta:
423 ordering = ['nom_affichage']
424 verbose_name = "Ayant droit"
425 verbose_name_plural = "Ayants droit"
426
427 def __unicode__(self):
428 return u'%s' % (self.get_nom())
429
430 def get_nom(self):
431 nom_affichage = self.nom_affichage
432 if not nom_affichage:
433 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
434 return nom_affichage
435
436 class AyantDroitCommentaire(Commentaire):
437 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
438 related_name='+')
439
440
441 ### DOSSIER
442
443 STATUT_RESIDENCE_CHOICES = (
444 ('local', 'Local'),
445 ('expat', 'Expatrié'),
446 )
447
448 COMPTE_COMPTA_CHOICES = (
449 ('coda', 'CODA'),
450 ('scs', 'SCS'),
451 ('aucun', 'Aucun'),
452 )
453
454 class Dossier_(AUFMetadata):
455 """Le Dossier regroupe les informations relatives à l'occupation
456 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
457 par un Employe.
458
459 Plusieurs Contrats peuvent être associés au Dossier.
460 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
461 lequel aucun Dossier n'existe est un poste vacant.
462 """
463 # Identification
464 employe = models.ForeignKey('Employe', db_column='employe',
465 related_name='dossiers',
466 verbose_name="Employé")
467 poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
468 statut = models.ForeignKey('Statut', related_name='+', default=3,
469 null=True)
470 organisme_bstg = models.ForeignKey('OrganismeBstg',
471 db_column='organisme_bstg',
472 related_name='+',
473 verbose_name="Organisme",
474 help_text="Si détaché (DET) ou \
475 mis à disposition (MAD), \
476 préciser l'organisme.",
477 null=True, blank=True)
478
479 # Recrutement
480 remplacement = models.BooleanField(default=False)
481 statut_residence = models.CharField(max_length=10, default='local',
482 verbose_name="Statut", null=True,
483 choices=STATUT_RESIDENCE_CHOICES)
484
485 # Rémunération
486 classement = models.ForeignKey('Classement', db_column='classement',
487 related_name='+',
488 null=True, blank=True)
489 regime_travail = models.DecimalField(max_digits=12, null=True,
490 decimal_places=2,
491 default=REGIME_TRAVAIL_DEFAULT,
492 verbose_name="Régime de travail",
493 help_text="% du temps complet")
494 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
495 decimal_places=2, null=True,
496 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
497 verbose_name="Nb. heures par semaine")
498
499 # Occupation du Poste par cet Employe (anciennement "mandat")
500 date_debut = models.DateField(verbose_name="Date de début d'occupation \
501 de poste",
502 help_text=HELP_TEXT_DATE)
503 date_fin = models.DateField(verbose_name="Date de fin d'occupation \
504 de poste",
505 help_text=HELP_TEXT_DATE,
506 null=True, blank=True)
507
508 # Comptes
509 # TODO?
510
511 class Meta:
512 abstract = True
513 ordering = ['employe__nom', ]
514 verbose_name = "Dossier"
515 verbose_name_plural = "Dossiers"
516
517 def __unicode__(self):
518 poste = self.poste.nom
519 if self.employe.genre == 'F':
520 poste = self.poste.nom_feminin
521 return u'%s - %s' % (self.employe, poste)
522
523
524 class Dossier(Dossier_):
525 __doc__ = Dossier_.__doc__
526
527
528 class DossierPiece(models.Model):
529 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
530 Ex.: Lettre de motivation.
531 """
532 dossier = models.ForeignKey('Dossier', db_column='dossier',
533 related_name='+')
534 nom = models.CharField(verbose_name="Nom", max_length=255)
535 fichier = models.FileField(verbose_name="Fichier",
536 upload_to=dossier_piece_dispatch,
537 storage=storage_prive)
538
539 class Meta:
540 ordering = ['nom']
541
542 def __unicode__(self):
543 return u'%s' % (self.nom)
544
545 class DossierCommentaire(Commentaire):
546 dossier = models.ForeignKey('Dossier', db_column='dossier',
547 related_name='+')
548
549 class DossierComparaison(models.Model):
550 """
551 Photo d'une comparaison salariale au moment de l'embauche.
552 """
553 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
554 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
555 poste = models.CharField(max_length=255, null=True, blank=True)
556 personne = models.CharField(max_length=255, null=True, blank=True)
557 montant = models.IntegerField(null=True)
558 devise = models.ForeignKey('Devise', default=5, related_name='+', null=True, blank=True)
559
560 def taux_devise(self):
561 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
562 if len(liste_taux) == 0:
563 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
564 else:
565 return liste_taux[0].taux
566
567 def montant_euros(self):
568 return round(float(self.montant) * float(self.taux_devise()), 2)
569
570
571 ### RÉMUNÉRATION
572
573 class RemunerationMixin(AUFMetadata):
574 # Identification
575 dossier = models.ForeignKey('Dossier', db_column='dossier',
576 related_name='%(app_label)s_%(class)s_remunerations')
577 type = models.ForeignKey('TypeRemuneration', db_column='type',
578 related_name='+',
579 verbose_name="Type de rémunération")
580 type_revalorisation = models.ForeignKey('TypeRevalorisation',
581 db_column='type_revalorisation',
582 related_name='+',
583 verbose_name="Type de revalorisation",
584 null=True, blank=True)
585 montant = models.FloatField(null=True, blank=True,
586 default=0)
587 # Annuel (12 mois, 52 semaines, 364 jours?)
588 devise = models.ForeignKey('Devise', to_field='id',
589 db_column='devise', related_name='+',
590 default=5)
591 # commentaire = precision
592 commentaire = models.CharField(max_length=255, null=True, blank=True)
593 # date_debut = anciennement date_effectif
594 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
595 verbose_name="Date de début",
596 null=True, blank=True)
597 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
598 verbose_name="Date de fin",
599 null=True, blank=True)
600
601 class Meta:
602 abstract = True
603 ordering = ['type__nom', '-date_fin']
604
605 def __unicode__(self):
606 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
607
608 class Remuneration_(RemunerationMixin):
609 """Structure de rémunération (données budgétaires) en situation normale
610 pour un Dossier. Si un Evenement existe, utiliser la structure de
611 rémunération EvenementRemuneration de cet événement.
612 """
613
614 def montant_mois(self):
615 return round(self.montant / 12, 2)
616
617 def taux_devise(self):
618 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
619
620 def montant_euro(self):
621 return round(float(self.montant) / float(self.taux_devise()), 2)
622
623 def montant_euro_mois(self):
624 return round(self.montant_euro() / 12, 2)
625
626 def __unicode__(self):
627 try:
628 devise = self.devise.code
629 except:
630 devise = "???"
631 return "%s %s" % (self.montant, devise)
632
633 class Meta:
634 abstract = True
635 verbose_name = "Rémunération"
636 verbose_name_plural = "Rémunérations"
637
638
639 class Remuneration(Remuneration_):
640 __doc__ = Remuneration_.__doc__
641
642
643 ### CONTRATS
644
645 class ContratManager(NoDeleteManager):
646 def get_query_set(self):
647 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
648
649
650 class Contrat(AUFMetadata):
651 """Document juridique qui encadre la relation de travail d'un Employe
652 pour un Poste particulier. Pour un Dossier (qui documente cette
653 relation de travail) plusieurs contrats peuvent être associés.
654 """
655
656 objects = ContratManager()
657
658 dossier = models.ForeignKey('Dossier', db_column='dossier',
659 related_name='+')
660 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
661 related_name='+',
662 verbose_name="Type de contrat")
663 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
664 verbose_name="Date de début")
665 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
666 verbose_name="Date de fin",
667 null=True, blank=True)
668
669 class Meta:
670 ordering = ['dossier__employe__nom_affichage']
671 verbose_name = "Contrat"
672 verbose_name_plural = "Contrats"
673
674 def __unicode__(self):
675 return u'%s - %s' % (self.dossier, self.id)
676
677 # TODO? class ContratPiece(models.Model):
678
679
680 ### ÉVÉNEMENTS
681
682 class Evenement_(AUFMetadata):
683 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
684 d'un Dossier qui vient altérer des informations normales liées à un Dossier
685 (ex.: la Remuneration).
686
687 Ex.: congé de maternité, maladie...
688
689 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
690 différent et une rémunération en conséquence. On souhaite toutefois
691 conserver le Dossier intact afin d'éviter une re-saisie des données lors
692 du retour à la normale.
693 """
694 dossier = models.ForeignKey('Dossier', db_column='dossier',
695 related_name='+')
696 nom = models.CharField(max_length=255)
697 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
698 verbose_name="Date de début")
699 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
700 verbose_name="Date de fin",
701 null=True, blank=True)
702
703 class Meta:
704 abstract = True
705 ordering = ['nom']
706 verbose_name = "Évènement"
707 verbose_name_plural = "Évènements"
708
709 def __unicode__(self):
710 return u'%s' % (self.nom)
711
712
713 class Evenement(Evenement_):
714 __doc__ = Evenement_.__doc__
715
716
717 class EvenementRemuneration_(RemunerationMixin):
718 """Structure de rémunération liée à un Evenement qui remplace
719 temporairement la Remuneration normale d'un Dossier, pour toute la durée
720 de l'Evenement.
721 """
722 evenement = models.ForeignKey("Evenement", db_column='evenement',
723 related_name='+',
724 verbose_name="Évènement")
725 # TODO : le champ dossier hérité de Remuneration doit être dérivé
726 # de l'Evenement associé
727
728 class Meta:
729 abstract = True
730 ordering = ['evenement', 'type__nom', '-date_fin']
731 verbose_name = "Évènement - rémunération"
732 verbose_name_plural = "Évènements - rémunérations"
733
734
735 class EvenementRemuneration(EvenementRemuneration_):
736 __doc__ = EvenementRemuneration_.__doc__
737
738 class Meta:
739 abstract = True
740
741
742 class EvenementRemuneration(EvenementRemuneration_):
743 __doc__ = EvenementRemuneration_.__doc__
744
745
746 ### RÉFÉRENCES RH
747
748 class FamilleEmploi(AUFMetadata):
749 """Catégorie utilisée dans la gestion des Postes.
750 Catégorie supérieure à TypePoste.
751 """
752 nom = models.CharField(max_length=255)
753
754 class Meta:
755 ordering = ['nom']
756 verbose_name = "Famille d'emploi"
757 verbose_name_plural = "Familles d'emploi"
758
759 def __unicode__(self):
760 return u'%s' % (self.nom)
761
762 class TypePoste(AUFMetadata):
763 """Catégorie de Poste.
764 """
765 nom = models.CharField(max_length=255)
766 nom_feminin = models.CharField(max_length=255,
767 verbose_name="Nom féminin")
768
769 is_responsable = models.BooleanField(default=False,
770 verbose_name="Poste de responsabilité")
771 famille_emploi = models.ForeignKey('FamilleEmploi',
772 db_column='famille_emploi',
773 related_name='+',
774 verbose_name="Famille d'emploi")
775
776 class Meta:
777 ordering = ['nom']
778 verbose_name = "Type de poste"
779 verbose_name_plural = "Types de poste"
780
781 def __unicode__(self):
782 return u'%s' % (self.nom)
783
784
785 TYPE_PAIEMENT_CHOICES = (
786 ('Régulier', 'Régulier'),
787 ('Ponctuel', 'Ponctuel'),
788 )
789
790 NATURE_REMUNERATION_CHOICES = (
791 ('Accessoire', 'Accessoire'),
792 ('Charges', 'Charges'),
793 ('Indemnité', 'Indemnité'),
794 ('RAS', 'Rémunération autre source'),
795 ('Traitement', 'Traitement'),
796 )
797
798 class TypeRemuneration(AUFMetadata):
799 """Catégorie de Remuneration.
800 """
801 nom = models.CharField(max_length=255)
802 type_paiement = models.CharField(max_length=30,
803 choices=TYPE_PAIEMENT_CHOICES,
804 verbose_name="Type de paiement")
805 nature_remuneration = models.CharField(max_length=30,
806 choices=NATURE_REMUNERATION_CHOICES,
807 verbose_name="Nature de la rémunération")
808
809 class Meta:
810 ordering = ['nom']
811 verbose_name = "Type de rémunération"
812 verbose_name_plural = "Types de rémunération"
813
814 def __unicode__(self):
815 return u'%s' % (self.nom)
816
817 class TypeRevalorisation(AUFMetadata):
818 """Justification du changement de la Remuneration.
819 (Actuellement utilisé dans aucun traitement informatique.)
820 """
821 nom = models.CharField(max_length=255)
822
823 class Meta:
824 ordering = ['nom']
825 verbose_name = "Type de revalorisation"
826 verbose_name_plural = "Types de revalorisation"
827
828 def __unicode__(self):
829 return u'%s' % (self.nom)
830
831 class Service(AUFMetadata):
832 """Unité administrative où les Postes sont rattachés.
833 """
834 nom = models.CharField(max_length=255)
835
836 class Meta:
837 ordering = ['nom']
838 verbose_name = "Service"
839 verbose_name_plural = "Services"
840
841 def __unicode__(self):
842 return u'%s' % (self.nom)
843
844
845 TYPE_ORGANISME_CHOICES = (
846 ('MAD', 'Mise à disposition'),
847 ('DET', 'Détachement'),
848 )
849
850 class OrganismeBstg(AUFMetadata):
851 """Organisation d'où provient un Employe mis à disposition (MAD) de
852 ou détaché (DET) à l'AUF à titre gratuit.
853
854 (BSTG = bien et service à titre gratuit.)
855 """
856 nom = models.CharField(max_length=255)
857 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
858 pays = models.ForeignKey(ref.Pays, to_field='code',
859 db_column='pays',
860 related_name='organismes_bstg',
861 null=True, blank=True)
862
863 class Meta:
864 ordering = ['type', 'nom']
865 verbose_name = "Organisme BSTG"
866 verbose_name_plural = "Organismes BSTG"
867
868 def __unicode__(self):
869 return u'%s (%s)' % (self.nom, self.get_type_display())
870
871 class Statut(AUFMetadata):
872 """Statut de l'Employe dans le cadre d'un Dossier particulier.
873 """
874 # Identification
875 code = models.CharField(max_length=25, unique=True)
876 nom = models.CharField(max_length=255)
877
878 class Meta:
879 ordering = ['code']
880 verbose_name = "Statut d'employé"
881 verbose_name_plural = "Statuts d'employé"
882
883 def __unicode__(self):
884 return u'%s : %s' % (self.code, self.nom)
885
886
887 TYPE_CLASSEMENT_CHOICES = (
888 ('S', 'S -Soutien'),
889 ('T', 'T - Technicien'),
890 ('P', 'P - Professionel'),
891 ('C', 'C - Cadre'),
892 ('D', 'D - Direction'),
893 ('SO', 'SO - Sans objet [expatriés]'),
894 ('HG', 'HG - Hors grille [direction]'),
895 )
896
897
898 class Classement_(AUFMetadata):
899 """Éléments de classement de la
900 "Grille générique de classement hiérarchique".
901
902 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
903 classement dans la grille. Le classement donne le coefficient utilisé dans:
904
905 salaire de base = coefficient * valeur du point de l'Implantation du Poste
906 """
907 # Identification
908 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
909 echelon = models.IntegerField(verbose_name="Échelon")
910 degre = models.IntegerField(verbose_name="Degré")
911 coefficient = models.FloatField(default=0, verbose_name="Coéfficient",
912 null=True)
913 # Méta
914 # annee # au lieu de date_debut et date_fin
915 commentaire = models.TextField(null=True, blank=True)
916
917 class Meta:
918 abstract = True
919 ordering = ['type','echelon','degre','coefficient']
920 verbose_name = "Classement"
921 verbose_name_plural = "Classements"
922
923 def __unicode__(self):
924 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
925 self.coefficient)
926
927 class Classement(Classement_):
928 __doc__ = Classement_.__doc__
929
930
931 class TauxChange_(AUFMetadata):
932 """Taux de change de la devise vers l'euro (EUR)
933 pour chaque année budgétaire.
934 """
935 # Identification
936 devise = models.ForeignKey('Devise', db_column='devise',
937 related_name='+')
938 annee = models.IntegerField(verbose_name="Année")
939 taux = models.FloatField(verbose_name="Taux vers l'euro")
940
941 class Meta:
942 abstract = True
943 ordering = ['-annee', 'devise__code']
944 verbose_name = "Taux de change"
945 verbose_name_plural = "Taux de change"
946
947 def __unicode__(self):
948 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
949
950
951 class TauxChange(TauxChange_):
952 __doc__ = TauxChange_.__doc__
953
954 class ValeurPointManager(NoDeleteManager):
955 def get_query_set(self):
956 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
957
958
959 class ValeurPoint_(AUFMetadata):
960 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
961 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
962 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
963
964 salaire de base = coefficient * valeur du point de l'Implantation du Poste
965 """
966
967 objects = ValeurPointManager()
968
969 valeur = models.FloatField(null=True)
970 devise = models.ForeignKey('Devise', db_column='devise', null=True,
971 related_name='+', default=5)
972 implantation = models.ForeignKey(ref.Implantation,
973 db_column='implantation',
974 related_name='%(app_label)s_valeur_point')
975 # Méta
976 annee = models.IntegerField()
977
978 class Meta:
979 ordering = ['-annee', 'implantation__nom']
980 abstract = True
981 verbose_name = "Valeur du point"
982 verbose_name_plural = "Valeurs du point"
983
984 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
985 def get_tauxchange_courant(self):
986 """
987 Recherche le taux courant associé à la valeur d'un point.
988 Tous les taux de l'année courante sont chargés, pour optimiser un
989 affichage en liste. (On pourrait probablement améliorer le manager pour
990 lui greffer le taux courant sous forme de JOIN)
991 """
992 for tauxchange in self.tauxchange:
993 if tauxchange.implantation_id == self.implantation_id:
994 return tauxchange
995 return None
996
997 def __unicode__(self):
998 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
999
1000
1001 class ValeurPoint(ValeurPoint_):
1002 __doc__ = ValeurPoint_.__doc__
1003
1004
1005 class Devise(AUFMetadata):
1006 """Devise monétaire.
1007 """
1008 code = models.CharField(max_length=10, unique=True)
1009 nom = models.CharField(max_length=255)
1010
1011 class Meta:
1012 ordering = ['code']
1013 verbose_name = "Devise"
1014 verbose_name_plural = "Devises"
1015
1016 def __unicode__(self):
1017 return u'%s - %s' % (self.code, self.nom)
1018
1019 class TypeContrat(AUFMetadata):
1020 """Type de contrat.
1021 """
1022 nom = models.CharField(max_length=255)
1023 nom_long = models.CharField(max_length=255)
1024
1025 class Meta:
1026 ordering = ['nom']
1027 verbose_name = "Type de contrat"
1028 verbose_name_plural = "Types de contrat"
1029
1030 def __unicode__(self):
1031 return u'%s' % (self.nom)
1032
1033
1034 ### AUTRES
1035
1036 class ResponsableImplantation(AUFMetadata):
1037 """Le responsable d'une implantation.
1038 Anciennement géré sur le Dossier du responsable.
1039 """
1040 employe = models.ForeignKey('Employe', db_column='employe',
1041 related_name='+',
1042 null=True, blank=True)
1043 implantation = models.ForeignKey(ref.Implantation,
1044 db_column='implantation', related_name='+',
1045 unique=True)
1046
1047 def __unicode__(self):
1048 return u'%s : %s' % (self.implantation, self.employe)
1049
1050 class Meta:
1051 ordering = ['implantation__nom']
1052 verbose_name = "Responsable d'implantation"
1053 verbose_name_plural = "Responsables d'implantation"
1054
1055 def dossier_piece_dispatch(instance, filename):
1056 path = "dossier/%s/%s" % (instance.dossier_id, filename)
1057 return path