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