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