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