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