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