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