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