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