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