a410f9b811f704562636781ddc79261d2135b517
[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 Contrat(AUFMetadata):
599 """Document juridique qui encadre la relation de travail d'un Employe
600 pour un Poste particulier. Pour un Dossier (qui documente cette
601 relation de travail) plusieurs contrats peuvent être associés.
602 """
603 dossier = models.ForeignKey('Dossier', db_column='dossier',
604 related_name='+')
605 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
606 related_name='+',
607 verbose_name="Type de contrat")
608 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
609 verbose_name="Date de début")
610 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
611 verbose_name="Date de fin",
612 null=True, blank=True)
613
614 class Meta:
615 ordering = ['dossier__employe__nom_affichage']
616 verbose_name = "Contrat"
617 verbose_name_plural = "Contrats"
618
619 def __unicode__(self):
620 return u'%s - %s' % (self.dossier, self.id)
621
622 # TODO? class ContratPiece(models.Model):
623
624
625 ### ÉVÉNEMENTS
626
627 class Evenement_(AUFMetadata):
628 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
629 d'un Dossier qui vient altérer des informations normales liées à un Dossier
630 (ex.: la Remuneration).
631
632 Ex.: congé de maternité, maladie...
633
634 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
635 différent et une rémunération en conséquence. On souhaite toutefois
636 conserver le Dossier intact afin d'éviter une re-saisie des données lors
637 du retour à la normale.
638 """
639 dossier = models.ForeignKey('Dossier', db_column='dossier',
640 related_name='+')
641 nom = models.CharField(max_length=255)
642 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
643 verbose_name="Date de début")
644 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
645 verbose_name="Date de fin",
646 null=True, blank=True)
647
648 class Meta:
649 abstract = True
650 ordering = ['nom']
651 verbose_name = "Évènement"
652 verbose_name_plural = "Évènements"
653
654 def __unicode__(self):
655 return u'%s' % (self.nom)
656
657
658 class Evenement(Evenement_):
659 __doc__ = Evenement_.__doc__
660
661
662 class EvenementRemuneration_(RemunerationMixin):
663 """Structure de rémunération liée à un Evenement qui remplace
664 temporairement la Remuneration normale d'un Dossier, pour toute la durée
665 de l'Evenement.
666 """
667 evenement = models.ForeignKey("Evenement", db_column='evenement',
668 related_name='+',
669 verbose_name="Évènement")
670 # TODO : le champ dossier hérité de Remuneration doit être dérivé
671 # de l'Evenement associé
672
673 class Meta:
674 abstract = True
675 ordering = ['evenement', 'type__nom', '-date_fin']
676 verbose_name = "Évènement - rémunération"
677 verbose_name_plural = "Évènements - rémunérations"
678
679
680 class EvenementRemuneration(EvenementRemuneration_):
681 __doc__ = EvenementRemuneration_.__doc__
682
683 class Meta:
684 abstract = True
685
686
687 class EvenementRemuneration(EvenementRemuneration_):
688 __doc__ = EvenementRemuneration_.__doc__
689
690
691 ### RÉFÉRENCES RH
692
693 class FamilleEmploi(AUFMetadata):
694 """Catégorie utilisée dans la gestion des Postes.
695 Catégorie supérieure à TypePoste.
696 """
697 nom = models.CharField(max_length=255)
698
699 class Meta:
700 ordering = ['nom']
701 verbose_name = "Famille d'emploi"
702 verbose_name_plural = "Familles d'emploi"
703
704 def __unicode__(self):
705 return u'%s' % (self.nom)
706
707 class TypePoste(AUFMetadata):
708 """Catégorie de Poste.
709 """
710 nom = models.CharField(max_length=255)
711 nom_feminin = models.CharField(max_length=255,
712 verbose_name="Nom féminin")
713
714 is_responsable = models.BooleanField(default=False,
715 verbose_name="Poste de responsabilité")
716 famille_emploi = models.ForeignKey('FamilleEmploi',
717 db_column='famille_emploi',
718 related_name='+',
719 verbose_name="Famille d'emploi")
720
721 class Meta:
722 ordering = ['nom']
723 verbose_name = "Type de poste"
724 verbose_name_plural = "Types de poste"
725
726 def __unicode__(self):
727 return u'%s' % (self.nom)
728
729
730 TYPE_PAIEMENT_CHOICES = (
731 ('Régulier', 'Régulier'),
732 ('Ponctuel', 'Ponctuel'),
733 )
734
735 NATURE_REMUNERATION_CHOICES = (
736 ('Accessoire', 'Accessoire'),
737 ('Charges', 'Charges'),
738 ('Indemnité', 'Indemnité'),
739 ('RAS', 'Rémunération autre source'),
740 ('Traitement', 'Traitement'),
741 )
742
743 class TypeRemuneration(AUFMetadata):
744 """Catégorie de Remuneration.
745 """
746 nom = models.CharField(max_length=255)
747 type_paiement = models.CharField(max_length=30,
748 choices=TYPE_PAIEMENT_CHOICES,
749 verbose_name="Type de paiement")
750 nature_remuneration = models.CharField(max_length=30,
751 choices=NATURE_REMUNERATION_CHOICES,
752 verbose_name="Nature de la rémunération")
753
754 class Meta:
755 ordering = ['nom']
756 verbose_name = "Type de rémunération"
757 verbose_name_plural = "Types de rémunération"
758
759 def __unicode__(self):
760 return u'%s' % (self.nom)
761
762 class TypeRevalorisation(AUFMetadata):
763 """Justification du changement de la Remuneration.
764 (Actuellement utilisé dans aucun traitement informatique.)
765 """
766 nom = models.CharField(max_length=255)
767
768 class Meta:
769 ordering = ['nom']
770 verbose_name = "Type de revalorisation"
771 verbose_name_plural = "Types de revalorisation"
772
773 def __unicode__(self):
774 return u'%s' % (self.nom)
775
776 class Service(AUFMetadata):
777 """Unité administrative où les Postes sont rattachés.
778 """
779 nom = models.CharField(max_length=255)
780
781 class Meta:
782 ordering = ['nom']
783 verbose_name = "Service"
784 verbose_name_plural = "Services"
785
786 def __unicode__(self):
787 return u'%s' % (self.nom)
788
789
790 TYPE_ORGANISME_CHOICES = (
791 ('MAD', 'Mise à disposition'),
792 ('DET', 'Détachement'),
793 )
794
795 class OrganismeBstg(AUFMetadata):
796 """Organisation d'où provient un Employe mis à disposition (MAD) de
797 ou détaché (DET) à l'AUF à titre gratuit.
798
799 (BSTG = bien et service à titre gratuit.)
800 """
801 nom = models.CharField(max_length=255)
802 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
803 pays = models.ForeignKey(ref.Pays, to_field='code',
804 db_column='pays',
805 related_name='organismes_bstg',
806 null=True, blank=True)
807
808 class Meta:
809 ordering = ['type', 'nom']
810 verbose_name = "Organisme BSTG"
811 verbose_name_plural = "Organismes BSTG"
812
813 def __unicode__(self):
814 return u'%s (%s)' % (self.nom, self.get_type_display())
815
816 class Statut(AUFMetadata):
817 """Statut de l'Employe dans le cadre d'un Dossier particulier.
818 """
819 # Identification
820 code = models.CharField(max_length=25, unique=True)
821 nom = models.CharField(max_length=255)
822
823 class Meta:
824 ordering = ['code']
825 verbose_name = "Statut d'employé"
826 verbose_name_plural = "Statuts d'employé"
827
828 def __unicode__(self):
829 return u'%s : %s' % (self.code, self.nom)
830
831
832 TYPE_CLASSEMENT_CHOICES = (
833 ('S', 'S -Soutien'),
834 ('T', 'T - Technicien'),
835 ('P', 'P - Professionel'),
836 ('C', 'C - Cadre'),
837 ('D', 'D - Direction'),
838 ('SO', 'SO - Sans objet [expatriés]'),
839 ('HG', 'HG - Hors grille [direction]'),
840 )
841
842
843 class Classement_(AUFMetadata):
844 """Éléments de classement de la
845 "Grille générique de classement hiérarchique".
846
847 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
848 classement dans la grille. Le classement donne le coefficient utilisé dans:
849
850 salaire de base = coefficient * valeur du point de l'Implantation du Poste
851 """
852 # Identification
853 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
854 echelon = models.IntegerField(verbose_name="Échelon")
855 degre = models.IntegerField(verbose_name="Degré")
856 coefficient = models.FloatField(default=0, verbose_name="Coéfficient",
857 null=True)
858 # Méta
859 # annee # au lieu de date_debut et date_fin
860 commentaire = models.TextField(null=True, blank=True)
861
862 class Meta:
863 abstract = True
864 ordering = ['type','echelon','degre','coefficient']
865 verbose_name = "Classement"
866 verbose_name_plural = "Classements"
867
868 def __unicode__(self):
869 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
870 self.coefficient)
871
872 class Classement(Classement_):
873 __doc__ = Classement_.__doc__
874
875
876 class TauxChange_(AUFMetadata):
877 """Taux de change de la devise vers l'euro (EUR)
878 pour chaque année budgétaire.
879 """
880 # Identification
881 devise = models.ForeignKey('Devise', db_column='devise',
882 related_name='+')
883 annee = models.IntegerField(verbose_name="Année")
884 taux = models.FloatField(verbose_name="Taux vers l'euro")
885
886 class Meta:
887 abstract = True
888 ordering = ['-annee', 'devise__code']
889 verbose_name = "Taux de change"
890 verbose_name_plural = "Taux de change"
891
892 def __unicode__(self):
893 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
894
895
896 class TauxChange(TauxChange_):
897 __doc__ = TauxChange_.__doc__
898
899
900 class ValeurPoint_(AUFMetadata):
901 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
902 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
903 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
904
905 salaire de base = coefficient * valeur du point de l'Implantation du Poste
906 """
907 valeur = models.FloatField(null=True)
908 devise = models.ForeignKey('Devise', db_column='devise', null=True,
909 related_name='+', default=5)
910 implantation = models.ForeignKey(ref.Implantation,
911 db_column='implantation',
912 related_name='%(app_label)s_valeur_point')
913 # Méta
914 annee = models.IntegerField()
915
916 class Meta:
917 ordering = ['annee', 'implantation__nom']
918 abstract = True
919 ordering = ['annee']
920 verbose_name = "Valeur du point"
921 verbose_name_plural = "Valeurs du point"
922
923 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
924 def get_tauxchange_courant(self):
925 """
926 Recherche le taux courant associé à la valeur d'un point.
927 Tous les taux de l'année courante sont chargés, pour optimiser un
928 affichage en liste. (On pourrait probablement améliorer le manager pour
929 lui greffer le taux courant sous forme de JOIN)
930 """
931 for tauxchange in self.tauxchange:
932 if tauxchange.implantation_id == self.implantation_id:
933 return tauxchange
934 return None
935
936 def __unicode__(self):
937 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
938
939
940 class ValeurPoint(ValeurPoint_):
941 __doc__ = ValeurPoint_.__doc__
942
943
944 class Devise(AUFMetadata):
945 """Devise monétaire.
946 """
947 code = models.CharField(max_length=10, unique=True)
948 nom = models.CharField(max_length=255)
949
950 class Meta:
951 ordering = ['code']
952 verbose_name = "Devise"
953 verbose_name_plural = "Devises"
954
955 def __unicode__(self):
956 return u'%s - %s' % (self.code, self.nom)
957
958 class TypeContrat(AUFMetadata):
959 """Type de contrat.
960 """
961 nom = models.CharField(max_length=255)
962 nom_long = models.CharField(max_length=255)
963
964 class Meta:
965 ordering = ['nom']
966 verbose_name = "Type de contrat"
967 verbose_name_plural = "Types de contrat"
968
969 def __unicode__(self):
970 return u'%s' % (self.nom)
971
972
973 ### AUTRES
974
975 class ResponsableImplantation(AUFMetadata):
976 """Le responsable d'une implantation.
977 Anciennement géré sur le Dossier du responsable.
978 """
979 employe = models.ForeignKey('Employe', db_column='employe',
980 related_name='+',
981 null=True, blank=True)
982 implantation = models.ForeignKey(ref.Implantation,
983 db_column='implantation', related_name='+',
984 unique=True)
985
986 def __unicode__(self):
987 return u'%s : %s' % (self.implantation, self.employe)
988
989 class Meta:
990 ordering = ['implantation__nom']
991 verbose_name = "Responsable d'implantation"
992 verbose_name_plural = "Responsables d'implantation"