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