#1894, #1895, #1896
[auf_rh_dae.git] / project / rh / models.py
1 # -=- encoding: utf-8 -=-
2
3 from django.core.files.storage import FileSystemStorage
4 from django.db import models
5 from django.conf import settings
6 from auf.django.metadata.models import AUFMetadata
7 from auf.django.metadata.managers import NoDeleteManager
8 import datamaster_modeles.models as ref
9
10
11 # Constantes
12 HELP_TEXT_DATE = "format: aaaa-mm-jj"
13 REGIME_TRAVAIL_DEFAULT = 100.00
14 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = 35.00
15
16
17 # Upload de fichiers
18 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
19 base_url=settings.PRIVE_MEDIA_URL)
20
21 def poste_piece_dispatch(instance, filename):
22 path = "poste/%s/%s" % (instance.poste_id, filename)
23 return path
24
25 def dossier_piece_dispatch(instance, filename):
26 path = "dossier/%s/%s" % (instance.dossier_id, filename)
27 return path
28
29
30 class Commentaire(AUFMetadata):
31 texte = models.TextField()
32 owner = models.ForeignKey('auth.User', db_column='owner', related_name='+')
33
34 class Meta:
35 abstract = True
36 ordering = ['-date_creation']
37
38 def __unicode__(self):
39 return u'%s' % (self.texte)
40
41
42 ### POSTE
43
44 POSTE_APPEL_CHOICES = (
45 ('interne', 'Interne'),
46 ('externe', 'Externe'),
47 )
48
49 class PosteManager(NoDeleteManager):
50 def get_query_set(self):
51 return super(PosteManager, self).get_query_set().select_related('implantation')
52
53 class Poste_(AUFMetadata):
54 """Un Poste est un emploi (job) à combler dans une implantation.
55 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
56 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
57 """
58
59 objects = PosteManager()
60
61 # Identification
62 nom = models.CharField(max_length=255,
63 verbose_name="Titre du poste", )
64 nom_feminin = models.CharField(max_length=255,
65 verbose_name="Titre du poste (au féminin)",
66 null=True)
67 implantation = models.ForeignKey(ref.Implantation,
68 db_column='implantation', related_name='+')
69 type_poste = models.ForeignKey('TypePoste', db_column='type_poste',
70 related_name='+',
71 null=True)
72 service = models.ForeignKey('Service', db_column='service', null=True,
73 related_name='+',
74 verbose_name="Direction/Service/Pôle support",
75 default=1) # default = Rectorat
76 responsable = models.ForeignKey('Poste', db_column='responsable',
77 related_name='+', null=True,
78 verbose_name="Poste du responsable",
79 default=149) # default = Recteur
80
81 # Contrat
82 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
83 default=REGIME_TRAVAIL_DEFAULT, null=True,
84 verbose_name="Temps de travail",
85 help_text="% du temps complet")
86 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
87 decimal_places=2, null=True,
88 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
89 verbose_name="Nb. heures par semaine")
90
91 # Recrutement
92 local = models.NullBooleanField(verbose_name="Local", default=True,
93 null=True, blank=True)
94 expatrie = models.NullBooleanField(verbose_name="Expatrié", default=False,
95 null=True, blank=True)
96 mise_a_disposition = models.NullBooleanField(
97 verbose_name="Mise à disposition",
98 null=True, default=False)
99 appel = models.CharField(max_length=10, null=True,
100 verbose_name="Appel à candidature",
101 choices=POSTE_APPEL_CHOICES,
102 default='interne')
103
104 # Rémunération
105 classement_min = models.ForeignKey('Classement',
106 db_column='classement_min', related_name='+',
107 null=True, blank=True)
108 classement_max = models.ForeignKey('Classement',
109 db_column='classement_max', related_name='+',
110 null=True, blank=True)
111 valeur_point_min = models.ForeignKey('ValeurPoint',
112 db_column='valeur_point_min', related_name='+',
113 null=True, blank=True)
114 valeur_point_max = models.ForeignKey('ValeurPoint',
115 db_column='valeur_point_max', related_name='+',
116 null=True, blank=True)
117 devise_min = models.ForeignKey('Devise', db_column='devise_min', null=True,
118 related_name='+', default=5)
119 devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
120 related_name='+', default=5)
121 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
122 null=True, default=0)
123 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
124 null=True, default=0)
125 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
126 null=True, default=0)
127 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
128 null=True, default=0)
129 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
130 null=True, default=0)
131 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
132 null=True, default=0)
133
134 # Comparatifs de rémunération
135 devise_comparaison = models.ForeignKey('Devise', null=True,
136 db_column='devise_comparaison',
137 related_name='+',
138 default=5)
139 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
140 null=True, blank=True)
141 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
142 null=True, blank=True)
143 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
144 null=True, blank=True)
145 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
146 null=True, blank=True)
147 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
148 null=True, blank=True)
149 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
150 null=True, blank=True)
151 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
152 null=True, blank=True)
153 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
154 null=True, blank=True)
155 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
156 null=True, blank=True)
157 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
158 null=True, blank=True)
159
160 # Justification
161 justification = models.TextField(null=True, blank=True)
162
163 # Autres Metadata
164 date_validation = models.DateTimeField(null=True, blank=True) # de dae
165 date_debut = models.DateField(verbose_name="Date de début", null=True,
166 help_text=HELP_TEXT_DATE)
167 date_fin = models.DateField(verbose_name="Date de fin",
168 help_text=HELP_TEXT_DATE,
169 null=True, blank=True)
170
171 class Meta:
172 abstract = True
173 ordering = ['implantation__nom', 'nom']
174 verbose_name = "Poste"
175 verbose_name_plural = "Postes"
176
177 def __unicode__(self):
178 representation = u'%s - %s [%s]' % (self.implantation, self.nom,
179 self.id)
180 if self.is_vacant():
181 representation = representation + u' (vacant)'
182 return representation
183
184 def is_vacant(self):
185 # TODO : si existe un dossier actif pour ce poste, return False
186 # self.dossier_set.all() fonctionne pas
187 return False
188
189
190 class Poste(Poste_):
191 __doc__ = Poste_.__doc__
192
193
194 class Poste(Poste_):
195 __doc__ = Poste_.__doc__
196
197
198 POSTE_FINANCEMENT_CHOICES = (
199 ('A', 'A - Frais de personnel'),
200 ('B', 'B - Projet(s)-Titre(s)'),
201 ('C', 'C - Autre')
202 )
203
204
205 class PosteFinancement_(models.Model):
206 """Pour un Poste, structure d'informations décrivant comment on prévoit
207 financer ce Poste.
208 """
209 poste = models.ForeignKey('Poste', db_column='poste',
210 related_name='%(app_label)s_financements')
211 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
212 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
213 help_text="ex.: 33.33 % (décimale avec point)")
214 commentaire = models.TextField(
215 help_text="Spécifiez la source de financement.")
216
217 class Meta:
218 abstract = True
219 ordering = ['type']
220
221 def __unicode__(self):
222 return u'%s : %s %' % (self.type, self.pourcentage)
223
224
225 class PosteFinancement(PosteFinancement_):
226 __doc__ = PosteFinancement_.__doc__
227
228
229 class PostePiece(models.Model):
230 """Documents relatifs au Poste.
231 Ex.: Description de poste
232 """
233 poste = models.ForeignKey('Poste', db_column='poste',
234 related_name='pieces')
235 nom = models.CharField(verbose_name="Nom", max_length=255)
236 fichier = models.FileField(verbose_name="Fichier",
237 upload_to=poste_piece_dispatch,
238 storage=storage_prive)
239
240 class Meta:
241 ordering = ['nom']
242
243 def __unicode__(self):
244 return u'%s' % (self.nom)
245
246 class PosteComparaison(models.Model):
247 """
248 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
249 """
250 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
251 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
252 nom = models.CharField(verbose_name="Poste", max_length=255, null=True, blank=True)
253 montant = models.IntegerField(null=True)
254 devise = models.ForeignKey("Devise", default=5, related_name='+', null=True, blank=True)
255
256 def taux_devise(self):
257 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
258 if len(liste_taux) == 0:
259 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
260 else:
261 return liste_taux[0].taux
262
263 def montant_euros(self):
264 return round(float(self.montant) * float(self.taux_devise()), 2)
265
266
267 class PosteCommentaire(Commentaire):
268 poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
269
270
271 ### EMPLOYÉ/PERSONNE
272
273 GENRE_CHOICES = (
274 ('M', 'Homme'),
275 ('F', 'Femme'),
276 )
277 SITUATION_CHOICES = (
278 ('C', 'Célibataire'),
279 ('F', 'Fiancé'),
280 ('M', 'Marié'),
281 )
282
283 class Employe(AUFMetadata):
284 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
285 Dossiers qu'il occupe ou a occupé de Postes.
286
287 Cette classe aurait pu avantageusement s'appeler Personne car la notion
288 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
289 """
290 # Identification
291 nom = models.CharField(max_length=255)
292 prenom = models.CharField(max_length=255, verbose_name="Prénom")
293 nom_affichage = models.CharField(max_length=255,
294 verbose_name="Nom d'affichage",
295 null=True, blank=True)
296 nationalite = models.ForeignKey(ref.Pays, to_field='code',
297 db_column='nationalite',
298 related_name='employes_nationalite',
299 verbose_name="Nationalité")
300 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
301 verbose_name="Date de naissance",
302 null=True, blank=True)
303 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
304
305 # Infos personnelles
306 situation_famille = models.CharField(max_length=1,
307 choices=SITUATION_CHOICES,
308 verbose_name="Situation familiale",
309 null=True, blank=True)
310 date_entree = models.DateField(verbose_name="Date d'entrée à l'AUF",
311 help_text=HELP_TEXT_DATE,
312 null=True, blank=True)
313
314 # Coordonnées
315 tel_domicile = models.CharField(max_length=255,
316 verbose_name="Tél. domicile",
317 null=True, blank=True)
318 tel_cellulaire = models.CharField(max_length=255,
319 verbose_name="Tél. cellulaire",
320 null=True, blank=True)
321 adresse = models.CharField(max_length=255, null=True, blank=True)
322 ville = models.CharField(max_length=255, null=True, blank=True)
323 province = models.CharField(max_length=255, null=True, blank=True)
324 code_postal = models.CharField(max_length=255, null=True, blank=True)
325 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
326 related_name='employes',
327 null=True, blank=True)
328
329 class Meta:
330 ordering = ['nom_affichage','nom','prenom']
331 verbose_name = "Employé"
332 verbose_name_plural = "Employés"
333
334 def __unicode__(self):
335 return u'%s' % (self.get_nom())
336
337 def get_nom(self):
338 nom_affichage = self.nom_affichage
339 if not nom_affichage:
340 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
341 return nom_affichage
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='+',
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
507 class Dossier(Dossier_):
508 __doc__ = Dossier_.__doc__
509
510
511 class DossierPiece(models.Model):
512 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
513 Ex.: Lettre de motivation.
514 """
515 dossier = models.ForeignKey('Dossier', db_column='dossier',
516 related_name='+')
517 nom = models.CharField(verbose_name="Nom", max_length=255)
518 fichier = models.FileField(verbose_name="Fichier",
519 upload_to=dossier_piece_dispatch,
520 storage=storage_prive)
521
522 class Meta:
523 ordering = ['nom']
524
525 def __unicode__(self):
526 return u'%s' % (self.nom)
527
528 class DossierCommentaire(Commentaire):
529 dossier = models.ForeignKey('Dossier', db_column='dossier',
530 related_name='+')
531
532 class DossierComparaison(models.Model):
533 """
534 Photo d'une comparaison salariale au moment de l'embauche.
535 """
536 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
537 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True)
538 poste = models.CharField(max_length=255, null=True, blank=True)
539 personne = models.CharField(max_length=255, null=True, blank=True)
540 montant = models.IntegerField(null=True)
541 devise = models.ForeignKey('Devise', default=5, related_name='+', null=True, blank=True)
542
543 def taux_devise(self):
544 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
545 if len(liste_taux) == 0:
546 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
547 else:
548 return liste_taux[0].taux
549
550 def montant_euros(self):
551 return round(float(self.montant) * float(self.taux_devise()), 2)
552
553
554 ### RÉMUNÉRATION
555
556 class RemunerationMixin(AUFMetadata):
557 # Identification
558 dossier = models.ForeignKey('Dossier', db_column='dossier',
559 related_name='%(app_label)s_%(class)s_remunerations')
560 type = models.ForeignKey('TypeRemuneration', db_column='type',
561 related_name='+',
562 verbose_name="Type de rémunération")
563 type_revalorisation = models.ForeignKey('TypeRevalorisation',
564 db_column='type_revalorisation',
565 related_name='+',
566 verbose_name="Type de revalorisation",
567 null=True, blank=True)
568 montant = models.FloatField(null=True, blank=True,
569 default=0)
570 # Annuel (12 mois, 52 semaines, 364 jours?)
571 devise = models.ForeignKey('Devise', to_field='id',
572 db_column='devise', related_name='+',
573 default=5)
574 # commentaire = precision
575 commentaire = models.CharField(max_length=255, null=True, blank=True)
576 # date_debut = anciennement date_effectif
577 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
578 verbose_name="Date de début",
579 null=True, blank=True)
580 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
581 verbose_name="Date de fin",
582 null=True, blank=True)
583
584 class Meta:
585 abstract = True
586 ordering = ['type__nom', '-date_fin']
587
588 def __unicode__(self):
589 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
590
591 class Remuneration_(RemunerationMixin):
592 """Structure de rémunération (données budgétaires) en situation normale
593 pour un Dossier. Si un Evenement existe, utiliser la structure de
594 rémunération EvenementRemuneration de cet événement.
595 """
596
597 def montant_mois(self):
598 return round(self.montant / 12, 2)
599
600 def taux_devise(self):
601 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
602
603 def montant_euro(self):
604 return round(float(self.montant) / float(self.taux_devise()), 2)
605
606 def montant_euro_mois(self):
607 return round(self.montant_euro() / 12, 2)
608
609 def __unicode__(self):
610 try:
611 devise = self.devise.code
612 except:
613 devise = "???"
614 return "%s %s" % (self.montant, devise)
615
616 class Meta:
617 abstract = True
618 verbose_name = "Rémunération"
619 verbose_name_plural = "Rémunérations"
620
621
622 class Remuneration(Remuneration_):
623 __doc__ = Remuneration_.__doc__
624
625
626 ### CONTRATS
627
628 class ContratManager(NoDeleteManager):
629 def get_query_set(self):
630 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
631
632
633 class Contrat(AUFMetadata):
634 """Document juridique qui encadre la relation de travail d'un Employe
635 pour un Poste particulier. Pour un Dossier (qui documente cette
636 relation de travail) plusieurs contrats peuvent être associés.
637 """
638
639 objects = ContratManager()
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_(AUFMetadata):
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 class Meta:
722 abstract = True
723
724
725 class EvenementRemuneration(EvenementRemuneration_):
726 __doc__ = EvenementRemuneration_.__doc__
727
728
729 ### RÉFÉRENCES RH
730
731 class FamilleEmploi(AUFMetadata):
732 """Catégorie utilisée dans la gestion des Postes.
733 Catégorie supérieure à TypePoste.
734 """
735 nom = models.CharField(max_length=255)
736
737 class Meta:
738 ordering = ['nom']
739 verbose_name = "Famille d'emploi"
740 verbose_name_plural = "Familles d'emploi"
741
742 def __unicode__(self):
743 return u'%s' % (self.nom)
744
745 class TypePoste(AUFMetadata):
746 """Catégorie de Poste.
747 """
748 nom = models.CharField(max_length=255)
749 nom_feminin = models.CharField(max_length=255,
750 verbose_name="Nom féminin")
751
752 is_responsable = models.BooleanField(default=False,
753 verbose_name="Poste de responsabilité")
754 famille_emploi = models.ForeignKey('FamilleEmploi',
755 db_column='famille_emploi',
756 related_name='+',
757 verbose_name="Famille d'emploi")
758
759 class Meta:
760 ordering = ['nom']
761 verbose_name = "Type de poste"
762 verbose_name_plural = "Types de poste"
763
764 def __unicode__(self):
765 return u'%s' % (self.nom)
766
767
768 TYPE_PAIEMENT_CHOICES = (
769 ('Régulier', 'Régulier'),
770 ('Ponctuel', 'Ponctuel'),
771 )
772
773 NATURE_REMUNERATION_CHOICES = (
774 ('Accessoire', 'Accessoire'),
775 ('Charges', 'Charges'),
776 ('Indemnité', 'Indemnité'),
777 ('RAS', 'Rémunération autre source'),
778 ('Traitement', 'Traitement'),
779 )
780
781 class TypeRemuneration(AUFMetadata):
782 """Catégorie de Remuneration.
783 """
784 nom = models.CharField(max_length=255)
785 type_paiement = models.CharField(max_length=30,
786 choices=TYPE_PAIEMENT_CHOICES,
787 verbose_name="Type de paiement")
788 nature_remuneration = models.CharField(max_length=30,
789 choices=NATURE_REMUNERATION_CHOICES,
790 verbose_name="Nature de la rémunération")
791
792 class Meta:
793 ordering = ['nom']
794 verbose_name = "Type de rémunération"
795 verbose_name_plural = "Types de rémunération"
796
797 def __unicode__(self):
798 return u'%s' % (self.nom)
799
800 class TypeRevalorisation(AUFMetadata):
801 """Justification du changement de la Remuneration.
802 (Actuellement utilisé dans aucun traitement informatique.)
803 """
804 nom = models.CharField(max_length=255)
805
806 class Meta:
807 ordering = ['nom']
808 verbose_name = "Type de revalorisation"
809 verbose_name_plural = "Types de revalorisation"
810
811 def __unicode__(self):
812 return u'%s' % (self.nom)
813
814 class Service(AUFMetadata):
815 """Unité administrative où les Postes sont rattachés.
816 """
817 nom = models.CharField(max_length=255)
818
819 class Meta:
820 ordering = ['nom']
821 verbose_name = "Service"
822 verbose_name_plural = "Services"
823
824 def __unicode__(self):
825 return u'%s' % (self.nom)
826
827
828 TYPE_ORGANISME_CHOICES = (
829 ('MAD', 'Mise à disposition'),
830 ('DET', 'Détachement'),
831 )
832
833 class OrganismeBstg(AUFMetadata):
834 """Organisation d'où provient un Employe mis à disposition (MAD) de
835 ou détaché (DET) à l'AUF à titre gratuit.
836
837 (BSTG = bien et service à titre gratuit.)
838 """
839 nom = models.CharField(max_length=255)
840 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
841 pays = models.ForeignKey(ref.Pays, to_field='code',
842 db_column='pays',
843 related_name='organismes_bstg',
844 null=True, blank=True)
845
846 class Meta:
847 ordering = ['type', 'nom']
848 verbose_name = "Organisme BSTG"
849 verbose_name_plural = "Organismes BSTG"
850
851 def __unicode__(self):
852 return u'%s (%s)' % (self.nom, self.get_type_display())
853
854 class Statut(AUFMetadata):
855 """Statut de l'Employe dans le cadre d'un Dossier particulier.
856 """
857 # Identification
858 code = models.CharField(max_length=25, unique=True)
859 nom = models.CharField(max_length=255)
860
861 class Meta:
862 ordering = ['code']
863 verbose_name = "Statut d'employé"
864 verbose_name_plural = "Statuts d'employé"
865
866 def __unicode__(self):
867 return u'%s : %s' % (self.code, self.nom)
868
869
870 TYPE_CLASSEMENT_CHOICES = (
871 ('S', 'S -Soutien'),
872 ('T', 'T - Technicien'),
873 ('P', 'P - Professionel'),
874 ('C', 'C - Cadre'),
875 ('D', 'D - Direction'),
876 ('SO', 'SO - Sans objet [expatriés]'),
877 ('HG', 'HG - Hors grille [direction]'),
878 )
879
880
881 class Classement_(AUFMetadata):
882 """Éléments de classement de la
883 "Grille générique de classement hiérarchique".
884
885 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
886 classement dans la grille. Le classement donne le coefficient utilisé dans:
887
888 salaire de base = coefficient * valeur du point de l'Implantation du Poste
889 """
890 # Identification
891 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
892 echelon = models.IntegerField(verbose_name="Échelon")
893 degre = models.IntegerField(verbose_name="Degré")
894 coefficient = models.FloatField(default=0, verbose_name="Coéfficient",
895 null=True)
896 # Méta
897 # annee # au lieu de date_debut et date_fin
898 commentaire = models.TextField(null=True, blank=True)
899
900 class Meta:
901 abstract = True
902 ordering = ['type','echelon','degre','coefficient']
903 verbose_name = "Classement"
904 verbose_name_plural = "Classements"
905
906 def __unicode__(self):
907 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
908 self.coefficient)
909
910 class Classement(Classement_):
911 __doc__ = Classement_.__doc__
912
913
914 class TauxChange_(AUFMetadata):
915 """Taux de change de la devise vers l'euro (EUR)
916 pour chaque année budgétaire.
917 """
918 # Identification
919 devise = models.ForeignKey('Devise', db_column='devise',
920 related_name='+')
921 annee = models.IntegerField(verbose_name="Année")
922 taux = models.FloatField(verbose_name="Taux vers l'euro")
923
924 class Meta:
925 abstract = True
926 ordering = ['-annee', 'devise__code']
927 verbose_name = "Taux de change"
928 verbose_name_plural = "Taux de change"
929
930 def __unicode__(self):
931 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
932
933
934 class TauxChange(TauxChange_):
935 __doc__ = TauxChange_.__doc__
936
937 class ValeurPointManager(NoDeleteManager):
938 def get_query_set(self):
939 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
940
941
942 class ValeurPoint_(AUFMetadata):
943 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
944 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
945 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
946
947 salaire de base = coefficient * valeur du point de l'Implantation du Poste
948 """
949
950 objects = ValeurPointManager()
951
952 valeur = models.FloatField(null=True)
953 devise = models.ForeignKey('Devise', db_column='devise', null=True,
954 related_name='+', default=5)
955 implantation = models.ForeignKey(ref.Implantation,
956 db_column='implantation',
957 related_name='%(app_label)s_valeur_point')
958 # Méta
959 annee = models.IntegerField()
960
961 class Meta:
962 ordering = ['-annee', 'implantation__nom']
963 abstract = True
964 verbose_name = "Valeur du point"
965 verbose_name_plural = "Valeurs du point"
966
967 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
968 def get_tauxchange_courant(self):
969 """
970 Recherche le taux courant associé à la valeur d'un point.
971 Tous les taux de l'année courante sont chargés, pour optimiser un
972 affichage en liste. (On pourrait probablement améliorer le manager pour
973 lui greffer le taux courant sous forme de JOIN)
974 """
975 for tauxchange in self.tauxchange:
976 if tauxchange.implantation_id == self.implantation_id:
977 return tauxchange
978 return None
979
980 def __unicode__(self):
981 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
982
983
984 class ValeurPoint(ValeurPoint_):
985 __doc__ = ValeurPoint_.__doc__
986
987
988 class Devise(AUFMetadata):
989 """Devise monétaire.
990 """
991 code = models.CharField(max_length=10, unique=True)
992 nom = models.CharField(max_length=255)
993
994 class Meta:
995 ordering = ['code']
996 verbose_name = "Devise"
997 verbose_name_plural = "Devises"
998
999 def __unicode__(self):
1000 return u'%s - %s' % (self.code, self.nom)
1001
1002 class TypeContrat(AUFMetadata):
1003 """Type de contrat.
1004 """
1005 nom = models.CharField(max_length=255)
1006 nom_long = models.CharField(max_length=255)
1007
1008 class Meta:
1009 ordering = ['nom']
1010 verbose_name = "Type de contrat"
1011 verbose_name_plural = "Types de contrat"
1012
1013 def __unicode__(self):
1014 return u'%s' % (self.nom)
1015
1016
1017 ### AUTRES
1018
1019 class ResponsableImplantation(AUFMetadata):
1020 """Le responsable d'une implantation.
1021 Anciennement géré sur le Dossier du responsable.
1022 """
1023 employe = models.ForeignKey('Employe', db_column='employe',
1024 related_name='+',
1025 null=True, blank=True)
1026 implantation = models.ForeignKey(ref.Implantation,
1027 db_column='implantation', related_name='+',
1028 unique=True)
1029
1030 def __unicode__(self):
1031 return u'%s : %s' % (self.implantation, self.employe)
1032
1033 class Meta:
1034 ordering = ['implantation__nom']
1035 verbose_name = "Responsable d'implantation"
1036 verbose_name_plural = "Responsables d'implantation"