merge master
[auf_rh_dae.git] / project / rh / models.py
1 # -=- encoding: utf-8 -=-
2
3 import datetime
4 from datetime import date
5 from decimal import Decimal
6
7 import reversion
8 from auf.django.emploi.models import \
9 GENRE_CHOICES, SITUATION_CHOICES # devrait plutot être dans references
10 from auf.django.references import models as ref
11 from django.core.files.storage import FileSystemStorage
12 from django.db import models
13 from django.db.models import Q
14 from django.conf import settings
15
16 from project.rh.change_list import \
17 RechercheTemporelle, KEY_STATUT, STATUT_ACTIF, STATUT_INACTIF, \
18 STATUT_FUTUR
19 from project.rh.managers import PosteManager, DossierManager, EmployeManager, \
20 DossierComparaisonManager, \
21 PosteComparaisonManager, \
22 TypeRemunerationManager
23 from project.rh.validators import validate_date_passee
24
25
26 # Constantes
27 HELP_TEXT_DATE = "format: jj-mm-aaaa"
28 REGIME_TRAVAIL_DEFAULT = Decimal('100.00')
29 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = Decimal('35.00')
30 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT = \
31 "Saisir le nombre d'heure de travail à temps complet (100%), " \
32 "sans tenir compte du régime de travail"
33
34 # Upload de fichiers
35 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
36 base_url=settings.PRIVE_MEDIA_URL)
37
38
39 def poste_piece_dispatch(instance, filename):
40 path = "%s/poste/%s/%s" % (
41 instance._meta.app_label, instance.poste_id, filename
42 )
43 return path
44
45
46 def dossier_piece_dispatch(instance, filename):
47 path = "%s/dossier/%s/%s" % (
48 instance._meta.app_label, instance.dossier_id, filename
49 )
50 return path
51
52
53 def employe_piece_dispatch(instance, filename):
54 path = "%s/employe/%s/%s" % (
55 instance._meta.app_label, instance.employe_id, filename
56 )
57 return path
58
59
60 def contrat_dispatch(instance, filename):
61 path = "%s/contrat/%s/%s" % (
62 instance._meta.app_label, instance.dossier_id, filename
63 )
64 return path
65
66
67 class ArchivableManager(models.Manager):
68
69 def get_query_set(self):
70 return super(ArchivableManager, self).get_query_set() \
71 .filter(archive=False)
72
73
74 class Archivable(models.Model):
75 archive = models.BooleanField(u'archivé', default=False)
76
77 objects = ArchivableManager()
78 avec_archives = models.Manager()
79
80 class Meta:
81 abstract = True
82
83
84 class DevisableMixin(object):
85
86 def get_annee_pour_taux_devise(self):
87 return datetime.datetime.now().year
88
89 def taux_devise(self, devise=None):
90 if devise is None:
91 devise = self.devise
92
93 if devise is None:
94 return None
95 if devise.code == "EUR":
96 return 1
97
98 annee = self.get_annee_pour_taux_devise()
99 taux = [
100 tc.taux
101 for tc in TauxChange.objects.filter(devise=devise, annee=annee)
102 ]
103 taux = set(taux)
104
105 if len(taux) == 0:
106 raise Exception(
107 u"Pas de taux pour %s en %s" % (devise.code, annee)
108 )
109
110 if len(taux) > 1:
111 raise Exception(u"Il existe plusieurs taux de %s en %s" %
112 (devise.code, annee))
113 else:
114 return list(taux)[0]
115
116 def montant_euros(self):
117 try:
118 taux = self.taux_devise()
119 except Exception, e:
120 return e
121 if not taux:
122 return None
123 return int(round(float(self.montant) * float(taux), 2))
124
125
126 class Commentaire(models.Model):
127 texte = models.TextField()
128 owner = models.ForeignKey(
129 'auth.User', db_column='owner', related_name='+',
130 verbose_name=u"Commentaire de"
131 )
132 date_creation = models.DateTimeField(
133 u'date', auto_now_add=True, blank=True, null=True
134 )
135
136 class Meta:
137 abstract = True
138 ordering = ['-date_creation']
139
140 def __unicode__(self):
141 return u'%s' % (self.texte)
142
143
144 ### POSTE
145
146 POSTE_APPEL_CHOICES = (
147 ('interne', 'Interne'),
148 ('externe', 'Externe'),
149 )
150
151
152 class Poste_(models.Model):
153 """
154 Un Poste est un emploi (job) à combler dans une implantation.
155 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
156 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
157 """
158
159 objects = PosteManager()
160
161 # Identification
162 nom = models.CharField(u"Titre du poste", max_length=255)
163 nom_feminin = models.CharField(
164 u"Titre du poste (au féminin)", max_length=255, null=True
165 )
166 implantation = models.ForeignKey(
167 ref.Implantation,
168 help_text=u"Taper le nom de l'implantation ou sa région",
169 db_column='implantation', related_name='+'
170 )
171 type_poste = models.ForeignKey(
172 'TypePoste', db_column='type_poste',
173 help_text=u"Taper le nom du type de poste", related_name='+',
174 null=True, verbose_name=u"type de poste"
175 )
176 service = models.ForeignKey(
177 'Service', db_column='service', related_name='%(app_label)s_postes',
178 verbose_name=u"direction/service/pôle support", null=True
179 )
180 responsable = models.ForeignKey(
181 'Poste', db_column='responsable',
182 related_name='+', null=True,
183 help_text=u"Taper le nom du poste ou du type de poste",
184 verbose_name=u"Poste du responsable"
185 )
186
187 # Contrat
188 regime_travail = models.DecimalField(
189 u"temps de travail", max_digits=12, decimal_places=2,
190 default=REGIME_TRAVAIL_DEFAULT, null=True,
191 help_text="% du temps complet"
192 )
193 regime_travail_nb_heure_semaine = models.DecimalField(
194 u"nb. heures par semaine", max_digits=12, decimal_places=2,
195 null=True, default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
196 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
197 )
198
199 # Recrutement
200 local = models.NullBooleanField(
201 u"local", default=True, null=True, blank=True
202 )
203 expatrie = models.NullBooleanField(
204 u"expatrié", default=False, null=True, blank=True
205 )
206 mise_a_disposition = models.NullBooleanField(
207 u"mise à disposition", null=True, default=False
208 )
209 appel = models.CharField(
210 u"Appel à candidature", max_length=10, null=True,
211 choices=POSTE_APPEL_CHOICES, default='interne'
212 )
213
214 # Rémunération
215 classement_min = models.ForeignKey(
216 'Classement', db_column='classement_min', related_name='+',
217 null=True, blank=True
218 )
219 classement_max = models.ForeignKey(
220 'Classement', db_column='classement_max', related_name='+',
221 null=True, blank=True
222 )
223 valeur_point_min = models.ForeignKey(
224 'ValeurPoint',
225 help_text=u"Taper le code ou le nom de l'implantation",
226 db_column='valeur_point_min', related_name='+', null=True,
227 blank=True
228 )
229 valeur_point_max = models.ForeignKey(
230 'ValeurPoint',
231 help_text=u"Taper le code ou le nom de l'implantation",
232 db_column='valeur_point_max', related_name='+', null=True,
233 blank=True
234 )
235 devise_min = models.ForeignKey(
236 'Devise', db_column='devise_min', null=True, related_name='+'
237 )
238 devise_max = models.ForeignKey(
239 'Devise', db_column='devise_max', null=True, related_name='+'
240 )
241 salaire_min = models.DecimalField(
242 max_digits=12, decimal_places=2, null=True, blank=True
243 )
244 salaire_max = models.DecimalField(
245 max_digits=12, decimal_places=2, null=True, blank=True
246 )
247 indemn_min = models.DecimalField(
248 max_digits=12, decimal_places=2, null=True, blank=True
249 )
250 indemn_max = models.DecimalField(
251 max_digits=12, decimal_places=2, null=True, blank=True
252 )
253 autre_min = models.DecimalField(
254 max_digits=12, decimal_places=2, null=True, blank=True
255 )
256 autre_max = models.DecimalField(
257 max_digits=12, decimal_places=2, null=True, blank=True
258 )
259
260 # Comparatifs de rémunération
261 devise_comparaison = models.ForeignKey(
262 'Devise', null=True, blank=True, db_column='devise_comparaison',
263 related_name='+'
264 )
265 comp_locale_min = models.DecimalField(
266 max_digits=12, decimal_places=2, null=True, blank=True
267 )
268 comp_locale_max = models.DecimalField(
269 max_digits=12, decimal_places=2, null=True, blank=True
270 )
271 comp_universite_min = models.DecimalField(
272 max_digits=12, decimal_places=2, null=True, blank=True
273 )
274 comp_universite_max = models.DecimalField(
275 max_digits=12, decimal_places=2, null=True, blank=True
276 )
277 comp_fonctionpub_min = models.DecimalField(
278 max_digits=12, decimal_places=2, null=True, blank=True
279 )
280 comp_fonctionpub_max = models.DecimalField(
281 max_digits=12, decimal_places=2, null=True, blank=True
282 )
283 comp_ong_min = models.DecimalField(
284 max_digits=12, decimal_places=2, null=True, blank=True
285 )
286 comp_ong_max = models.DecimalField(
287 max_digits=12, decimal_places=2, null=True, blank=True
288 )
289 comp_autre_min = models.DecimalField(
290 max_digits=12, decimal_places=2, null=True, blank=True
291 )
292 comp_autre_max = models.DecimalField(
293 max_digits=12, decimal_places=2, null=True, blank=True
294 )
295
296 # Justification
297 justification = models.TextField(null=True, blank=True)
298
299 # Autres Metadata
300 date_debut = models.DateField(
301 u"date de début", help_text=HELP_TEXT_DATE, null=True, blank=True,
302 db_index=True
303 )
304 date_fin = models.DateField(
305 u"date de fin", help_text=HELP_TEXT_DATE, null=True, blank=True,
306 db_index=True
307 )
308
309 class Meta:
310 abstract = True
311 ordering = ['implantation__nom', 'nom']
312 verbose_name = u"Poste"
313 verbose_name_plural = u"Postes"
314 ordering = ["nom"]
315
316 def __unicode__(self):
317 representation = u'%s - %s [%s]' % (
318 self.implantation, self.nom, self.id
319 )
320 return representation
321
322 prefix_implantation = "implantation__region"
323
324 def get_regions(self):
325 return [self.implantation.region]
326
327 def get_devise(self):
328 vp = ValeurPoint.objects.filter(
329 implantation=self.implantation, devise__archive=False
330 ).order_by('annee')
331 if len(vp) > 0:
332 return vp[0].devise
333 else:
334 return Devise.objects.get(code='EUR')
335
336
337 class Poste(Poste_):
338 __doc__ = Poste_.__doc__
339
340 # meta dématérialisation : pour permettre le filtrage
341 vacant = models.NullBooleanField(u"vacant", null=True, blank=True)
342
343 def is_vacant(self):
344 vacant = True
345 if self.occupe_par():
346 vacant = False
347 return vacant
348
349 def occupe_par(self):
350 """
351 Retourne la liste d'employé occupant ce poste.
352 Généralement, retourne une liste d'un élément.
353 Si poste inoccupé, retourne liste vide.
354 UTILISE pour mettre a jour le flag vacant
355 """
356 return [
357 d.employe
358 for d in self.rh_dossiers.exclude(date_fin__lt=date.today())
359 ]
360
361 reversion.register(Poste, format='xml', follow=[
362 'rh_financements', 'rh_pieces', 'rh_comparaisons_internes',
363 'commentaires'
364 ])
365
366
367 POSTE_FINANCEMENT_CHOICES = (
368 ('A', 'A - Frais de personnel'),
369 ('B', 'B - Projet(s)-Titre(s)'),
370 ('C', 'C - Autre')
371 )
372
373
374 class PosteFinancement_(models.Model):
375 """
376 Pour un Poste, structure d'informations décrivant comment on prévoit
377 financer ce Poste.
378 """
379 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
380 pourcentage = models.DecimalField(
381 max_digits=12, decimal_places=2,
382 help_text="ex.: 33.33 % (décimale avec point)"
383 )
384 commentaire = models.TextField(
385 help_text="Spécifiez la source de financement."
386 )
387
388 class Meta:
389 abstract = True
390 ordering = ['type']
391
392 def __unicode__(self):
393 return u'%s : %s %%' % (self.type, self.pourcentage)
394
395 def choix(self):
396 return u"%s" % dict(POSTE_FINANCEMENT_CHOICES)[self.type]
397
398
399 class PosteFinancement(PosteFinancement_):
400 poste = models.ForeignKey(
401 Poste, db_column='poste', related_name='rh_financements'
402 )
403
404 reversion.register(PosteFinancement, format='xml')
405
406
407 class PostePiece_(models.Model):
408 """
409 Documents relatifs au Poste.
410 Ex.: Description de poste
411 """
412 nom = models.CharField(u"Nom", max_length=255)
413 fichier = models.FileField(
414 u"Fichier", upload_to=poste_piece_dispatch, storage=storage_prive
415 )
416
417 class Meta:
418 abstract = True
419 ordering = ['nom']
420
421 def __unicode__(self):
422 return u'%s' % (self.nom)
423
424
425 class PostePiece(PostePiece_):
426 poste = models.ForeignKey(
427 Poste, db_column='poste', related_name='rh_pieces'
428 )
429
430 reversion.register(PostePiece, format='xml')
431
432
433 class PosteComparaison_(models.Model, DevisableMixin):
434 """
435 De la même manière qu'un dossier, un poste peut-être comparé à un autre
436 poste.
437 """
438 objects = PosteComparaisonManager()
439
440 implantation = models.ForeignKey(
441 ref.Implantation, null=True, blank=True, related_name="+"
442 )
443 nom = models.CharField(u"Poste", max_length=255, null=True, blank=True)
444 montant = models.IntegerField(null=True)
445 devise = models.ForeignKey(
446 "Devise", related_name='+', null=True, blank=True
447 )
448
449 class Meta:
450 abstract = True
451
452 def __unicode__(self):
453 return self.nom
454
455
456 class PosteComparaison(PosteComparaison_):
457 poste = models.ForeignKey(
458 Poste, related_name='rh_comparaisons_internes'
459 )
460
461 reversion.register(PosteComparaison, format='xml')
462
463
464 class PosteCommentaire(Commentaire):
465 poste = models.ForeignKey(
466 Poste, db_column='poste', related_name='commentaires'
467 )
468
469 reversion.register(PosteCommentaire, format='xml')
470
471 ### EMPLOYÉ/PERSONNE
472
473 class Employe(models.Model):
474 """
475 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
476 Dossiers qu'il occupe ou a occupé de Postes.
477
478 Cette classe aurait pu avantageusement s'appeler Personne car la notion
479 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
480 """
481
482 objects = EmployeManager()
483
484 # Identification
485 nom = models.CharField(max_length=255)
486 prenom = models.CharField(u"prénom", max_length=255)
487 nom_affichage = models.CharField(
488 u"nom d'affichage", max_length=255, null=True, blank=True
489 )
490 nationalite = models.ForeignKey(
491 ref.Pays, to_field='code', db_column='nationalite',
492 related_name='employes_nationalite', verbose_name=u"nationalité",
493 blank=True, null=True
494 )
495 date_naissance = models.DateField(
496 u"date de naissance", help_text=HELP_TEXT_DATE,
497 validators=[validate_date_passee], null=True, blank=True
498 )
499 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
500
501 # Infos personnelles
502 situation_famille = models.CharField(
503 u"situation familiale", max_length=1, choices=SITUATION_CHOICES,
504 null=True, blank=True
505 )
506 date_entree = models.DateField(
507 u"date d'entrée à l'AUF", help_text=HELP_TEXT_DATE, null=True,
508 blank=True
509 )
510
511 # Coordonnées
512 tel_domicile = models.CharField(
513 u"tél. domicile", max_length=255, null=True, blank=True
514 )
515 tel_cellulaire = models.CharField(
516 u"tél. cellulaire", max_length=255, null=True, blank=True
517 )
518 adresse = models.CharField(max_length=255, null=True, blank=True)
519 ville = models.CharField(max_length=255, null=True, blank=True)
520 province = models.CharField(max_length=255, null=True, blank=True)
521 code_postal = models.CharField(max_length=255, null=True, blank=True)
522 pays = models.ForeignKey(
523 ref.Pays, to_field='code', db_column='pays',
524 related_name='employes', null=True, blank=True
525 )
526 courriel_perso = models.EmailField(
527 u'adresse courriel personnelle', blank=True
528 )
529
530 # meta dématérialisation : pour permettre le filtrage
531 nb_postes = models.IntegerField(u"nombre de postes", null=True, blank=True)
532
533 class Meta:
534 ordering = ['nom', 'prenom']
535 verbose_name = u"Employé"
536 verbose_name_plural = u"Employés"
537
538 def __unicode__(self):
539 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
540
541 def civilite(self):
542 civilite = u''
543 if self.genre.upper() == u'M':
544 civilite = u'M.'
545 elif self.genre.upper() == u'F':
546 civilite = u'Mme'
547 return civilite
548
549 def url_photo(self):
550 """
551 Retourne l'URL du service retournant la photo de l'Employe.
552 Équivalent reverse url 'rh_photo' avec id en param.
553 """
554 from django.core.urlresolvers import reverse
555 return reverse('rh_photo', kwargs={'id': self.id})
556
557 def dossiers_passes(self):
558 params = {KEY_STATUT: STATUT_INACTIF, }
559 search = RechercheTemporelle(params, self.__class__)
560 search.purge_params(params)
561 q = search.get_q_temporel(self.rh_dossiers)
562 return self.rh_dossiers.filter(q)
563
564 def dossiers_futurs(self):
565 params = {KEY_STATUT: STATUT_FUTUR, }
566 search = RechercheTemporelle(params, self.__class__)
567 search.purge_params(params)
568 q = search.get_q_temporel(self.rh_dossiers)
569 return self.rh_dossiers.filter(q)
570
571 def dossiers_encours(self):
572 params = {KEY_STATUT: STATUT_ACTIF, }
573 search = RechercheTemporelle(params, self.__class__)
574 search.purge_params(params)
575 q = search.get_q_temporel(self.rh_dossiers)
576 return self.rh_dossiers.filter(q)
577
578 def dossier_principal(self):
579 """
580 Retourne le dossier principal (ou le plus ancien si il y en a
581 plusieurs)
582 """
583 try:
584 dossier = self.rh_dossiers \
585 .filter(principal=True).order_by('date_debut')[0]
586 except IndexError, Dossier.DoesNotExist:
587 dossier = None
588 return dossier
589
590 def postes_encours(self):
591 postes_encours = set()
592 for d in self.dossiers_encours():
593 postes_encours.add(d.poste)
594 return postes_encours
595
596 def poste_principal(self):
597 """
598 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
599 Idée derrière :
600 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
601 """
602 # DEPRECATED : on a maintenant Dossier.principal
603 poste = Poste.objects.none()
604 try:
605 poste = self.dossiers_encours().order_by('date_debut')[0].poste
606 except:
607 pass
608 return poste
609
610 prefix_implantation = "rh_dossiers__poste__implantation__region"
611
612 def get_regions(self):
613 regions = []
614 for d in self.dossiers.all():
615 regions.append(d.poste.implantation.region)
616 return regions
617
618 reversion.register(Employe, format='xml', follow=[
619 'pieces', 'commentaires', 'ayantdroits'
620 ])
621
622
623 class EmployePiece(models.Model):
624 """
625 Documents relatifs à un employé.
626 Ex.: CV...
627 """
628 employe = models.ForeignKey(
629 'Employe', db_column='employe', related_name="pieces",
630 verbose_name=u"employé"
631 )
632 nom = models.CharField(max_length=255)
633 fichier = models.FileField(
634 u"fichier", upload_to=employe_piece_dispatch, storage=storage_prive
635 )
636
637 class Meta:
638 ordering = ['nom']
639 verbose_name = u"Employé pièce"
640 verbose_name_plural = u"Employé pièces"
641
642 def __unicode__(self):
643 return u'%s' % (self.nom)
644
645 reversion.register(EmployePiece, format='xml')
646
647
648 class EmployeCommentaire(Commentaire):
649 employe = models.ForeignKey(
650 'Employe', db_column='employe', related_name='commentaires'
651 )
652
653 class Meta:
654 verbose_name = u"Employé commentaire"
655 verbose_name_plural = u"Employé commentaires"
656
657 reversion.register(EmployeCommentaire, format='xml')
658
659
660 LIEN_PARENTE_CHOICES = (
661 ('Conjoint', 'Conjoint'),
662 ('Conjointe', 'Conjointe'),
663 ('Fille', 'Fille'),
664 ('Fils', 'Fils'),
665 )
666
667
668 class AyantDroit(models.Model):
669 """
670 Personne en relation avec un Employe.
671 """
672 # Identification
673 nom = models.CharField(max_length=255)
674 prenom = models.CharField(u"prénom", max_length=255)
675 nom_affichage = models.CharField(
676 u"nom d'affichage", max_length=255, null=True, blank=True
677 )
678 nationalite = models.ForeignKey(
679 ref.Pays, to_field='code', db_column='nationalite',
680 related_name='ayantdroits_nationalite',
681 verbose_name=u"nationalité", null=True, blank=True
682 )
683 date_naissance = models.DateField(
684 u"Date de naissance", help_text=HELP_TEXT_DATE,
685 validators=[validate_date_passee], null=True, blank=True
686 )
687 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
688
689 # Relation
690 employe = models.ForeignKey(
691 'Employe', db_column='employe', related_name='ayantdroits',
692 verbose_name=u"Employé"
693 )
694 lien_parente = models.CharField(
695 u"lien de parenté", max_length=10, choices=LIEN_PARENTE_CHOICES,
696 null=True, blank=True
697 )
698
699 class Meta:
700 ordering = ['nom', ]
701 verbose_name = u"Ayant droit"
702 verbose_name_plural = u"Ayants droit"
703
704 def __unicode__(self):
705 return u'%s %s' % (self.nom.upper(), self.prenom, )
706
707 prefix_implantation = "employe__dossiers__poste__implantation__region"
708
709 def get_regions(self):
710 regions = []
711 for d in self.employe.dossiers.all():
712 regions.append(d.poste.implantation.region)
713 return regions
714
715 reversion.register(AyantDroit, format='xml', follow=['commentaires'])
716
717
718 class AyantDroitCommentaire(Commentaire):
719 ayant_droit = models.ForeignKey(
720 'AyantDroit', db_column='ayant_droit', related_name='commentaires'
721 )
722
723 reversion.register(AyantDroitCommentaire, format='xml')
724
725
726 ### DOSSIER
727
728 STATUT_RESIDENCE_CHOICES = (
729 ('local', 'Local'),
730 ('expat', 'Expatrié'),
731 )
732
733 COMPTE_COMPTA_CHOICES = (
734 ('coda', 'CODA'),
735 ('scs', 'SCS'),
736 ('aucun', 'Aucun'),
737 )
738
739
740 class Dossier_(models.Model, DevisableMixin):
741 """
742 Le Dossier regroupe les informations relatives à l'occupation
743 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
744 par un Employe.
745
746 Plusieurs Contrats peuvent être associés au Dossier.
747 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
748 lequel aucun Dossier n'existe est un poste vacant.
749 """
750
751 objects = DossierManager()
752
753 # TODO: OneToOne ??
754 statut = models.ForeignKey('Statut', related_name='+', null=True)
755 organisme_bstg = models.ForeignKey(
756 'OrganismeBstg', db_column='organisme_bstg', related_name='+',
757 verbose_name=u"organisme",
758 help_text=(
759 u"Si détaché (DET) ou mis à disposition (MAD), "
760 u"préciser l'organisme."
761 ), null=True, blank=True
762 )
763
764 # Recrutement
765 remplacement = models.BooleanField(default=False)
766 remplacement_de = models.ForeignKey(
767 'self', related_name='+', help_text=u"Taper le nom de l'employé",
768 null=True, blank=True
769 )
770 statut_residence = models.CharField(
771 u"statut", max_length=10, default='local', null=True,
772 choices=STATUT_RESIDENCE_CHOICES
773 )
774
775 # Rémunération
776 classement = models.ForeignKey(
777 'Classement', db_column='classement', related_name='+', null=True,
778 blank=True
779 )
780 regime_travail = models.DecimalField(
781 u"régime de travail", max_digits=12, null=True, decimal_places=2,
782 default=REGIME_TRAVAIL_DEFAULT, help_text="% du temps complet"
783 )
784 regime_travail_nb_heure_semaine = models.DecimalField(
785 u"nb. heures par semaine", max_digits=12,
786 decimal_places=2, null=True,
787 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
788 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
789 )
790
791 # Occupation du Poste par cet Employe (anciennement "mandat")
792 date_debut = models.DateField(
793 u"date de début d'occupation de poste", db_index=True
794 )
795 date_fin = models.DateField(
796 u"Date de fin d'occupation de poste", null=True, blank=True,
797 db_index=True
798 )
799
800 # Comptes
801 # TODO?
802
803 class Meta:
804 abstract = True
805 ordering = ['employe__nom', ]
806 verbose_name = u"Dossier"
807 verbose_name_plural = "Dossiers"
808
809 def salaire_theorique(self):
810 annee = date.today().year
811 coeff = self.classement.coefficient
812 implantation = self.poste.implantation
813 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
814
815 montant = coeff * point.valeur
816 devise = point.devise
817 return {'montant': montant, 'devise': devise}
818
819 def __unicode__(self):
820 poste = self.poste.nom
821 if self.employe.genre == 'F':
822 poste = self.poste.nom_feminin
823 return u'%s - %s' % (self.employe, poste)
824
825 prefix_implantation = "poste__implantation__region"
826
827 def get_regions(self):
828 return [self.poste.implantation.region]
829
830 def remunerations(self):
831 key = "%s_remunerations" % self._meta.app_label
832 remunerations = getattr(self, key)
833 return remunerations.all().order_by('-date_debut')
834
835 def remunerations_en_cours(self):
836 q = Q(date_fin__exact=None) | Q(date_fin__gt=datetime.date.today())
837 return self.remunerations().all().filter(q).order_by('date_debut')
838
839 def get_salaire(self):
840 try:
841 return [r for r in self.remunerations().order_by('-date_debut')
842 if r.type_id == 1][0]
843 except:
844 return None
845
846 def get_salaire_euros(self):
847 tx = self.taux_devise()
848 return (float)(tx) * (float)(self.salaire)
849
850 def get_remunerations_brutes(self):
851 """
852 1 Salaire de base
853 3 Indemnité de base
854 4 Indemnité d'expatriation
855 5 Indemnité pour frais
856 6 Indemnité de logement
857 7 Indemnité de fonction
858 8 Indemnité de responsabilité
859 9 Indemnité de transport
860 10 Indemnité compensatrice
861 11 Indemnité de subsistance
862 12 Indemnité différentielle
863 13 Prime d'installation
864 14 Billet d'avion
865 15 Déménagement
866 16 Indemnité de départ
867 18 Prime de 13ième mois
868 19 Prime d'intérim
869 """
870 ids = [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
871 return [r for r in self.remunerations_en_cours().all()
872 if r.type_id in ids]
873
874 def get_charges_salariales(self):
875 """
876 20 Charges salariales ?
877 """
878 ids = [20]
879 return [r for r in self.remunerations_en_cours().all()
880 if r.type_id in ids]
881
882 def get_charges_patronales(self):
883 """
884 17 Charges patronales
885 """
886 ids = [17]
887 return [r for r in self.remunerations_en_cours().all()
888 if r.type_id in ids]
889
890 def get_remunerations_tierces(self):
891 """
892 2 Salaire MAD
893 """
894 return [r for r in self.remunerations_en_cours().all()
895 if r.type_id in (2,)]
896
897 # DEVISE LOCALE
898
899 def get_total_local_charges_salariales(self):
900 devise = self.poste.get_devise()
901 total = 0.0
902 for r in self.get_charges_salariales():
903 if r.devise != devise:
904 return None
905 total += float(r.montant)
906 return total
907
908 def get_total_local_charges_patronales(self):
909 devise = self.poste.get_devise()
910 total = 0.0
911 for r in self.get_charges_patronales():
912 if r.devise != devise:
913 return None
914 total += float(r.montant)
915 return total
916
917 def get_local_salaire_brut(self):
918 """
919 somme des rémuérations brutes
920 """
921 devise = self.poste.get_devise()
922 total = 0.0
923 for r in self.get_remunerations_brutes():
924 if r.devise != devise:
925 return None
926 total += float(r.montant)
927 return total
928
929 def get_local_salaire_net(self):
930 """
931 salaire brut - charges salariales
932 """
933 devise = self.poste.get_devise()
934 total_charges = 0.0
935 for r in self.get_charges_salariales():
936 if r.devise != devise:
937 return None
938 total_charges += float(r.montant)
939 return self.get_local_salaire_brut() - total_charges
940
941 def get_local_couts_auf(self):
942 """
943 salaire net + charges patronales
944 """
945 devise = self.poste.get_devise()
946 total_charges = 0.0
947 for r in self.get_charges_patronales():
948 if r.devise != devise:
949 return None
950 total_charges += float(r.montant)
951 return self.get_local_salaire_net() + total_charges
952
953 def get_total_local_remunerations_tierces(self):
954 devise = self.poste.get_devise()
955 total = 0.0
956 for r in self.get_remunerations_tierces():
957 if r.devise != devise:
958 return None
959 total += float(r.montant)
960 return total
961
962 # DEVISE EURO
963
964 def get_total_charges_salariales(self):
965 total = 0.0
966 for r in self.get_charges_salariales():
967 total += r.montant_euros()
968 return total
969
970 def get_total_charges_patronales(self):
971 total = 0.0
972 for r in self.get_charges_patronales():
973 total += r.montant_euros()
974 return total
975
976 def get_salaire_brut(self):
977 """
978 somme des rémuérations brutes
979 """
980 total = 0.0
981 for r in self.get_remunerations_brutes():
982 total += r.montant_euros()
983 return total
984
985 def get_salaire_net(self):
986 """
987 salaire brut - charges salariales
988 """
989 total_charges = 0.0
990 for r in self.get_charges_salariales():
991 total_charges += r.montant_euros()
992 return self.get_salaire_brut() - total_charges
993
994 def get_couts_auf(self):
995 """
996 salaire net + charges patronales
997 """
998 total_charges = 0.0
999 for r in self.get_charges_patronales():
1000 total_charges += r.montant_euros()
1001 return self.get_salaire_net() + total_charges
1002
1003 def get_total_remunerations_tierces(self):
1004 total = 0.0
1005 for r in self.get_remunerations_tierces():
1006 total += r.montant_euros()
1007 return total
1008
1009 def premier_contrat(self):
1010 """contrat avec plus petite date de début"""
1011 try:
1012 contrat = self.rh_contrats.exclude(date_debut=None) \
1013 .order_by('date_debut')[0]
1014 except IndexError, Contrat.DoesNotExist:
1015 contrat = None
1016 return contrat
1017
1018 def dernier_contrat(self):
1019 """contrat avec plus grande date de fin"""
1020 try:
1021 contrat = self.rh_contrats.exclude(date_debut=None) \
1022 .order_by('-date_debut')[0]
1023 except IndexError, Contrat.DoesNotExist:
1024 contrat = None
1025 return contrat
1026
1027 def actif(self):
1028 today = date.today()
1029 return (self.date_debut is None or self.date_debut <= today) \
1030 and (self.date_fin is None or self.date_fin >= today) \
1031 and not (self.date_fin is None and self.date_debut is None)
1032
1033
1034 class Dossier(Dossier_):
1035 __doc__ = Dossier_.__doc__
1036 poste = models.ForeignKey(
1037 Poste, db_column='poste', related_name='rh_dossiers',
1038 help_text=u"Taper le nom du poste ou du type de poste",
1039 )
1040 employe = models.ForeignKey(
1041 'Employe', db_column='employe',
1042 help_text=u"Taper le nom de l'employé",
1043 related_name='rh_dossiers', verbose_name=u"employé"
1044 )
1045 principal = models.BooleanField(
1046 u"dossier principal", default=True,
1047 help_text=(
1048 u"Ce dossier est pour le principal poste occupé par l'employé"
1049 )
1050 )
1051
1052 reversion.register(Dossier, format='xml', follow=[
1053 'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
1054 'rh_contrats', 'commentaires'
1055 ])
1056
1057
1058 class DossierPiece_(models.Model):
1059 """
1060 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1061 Ex.: Lettre de motivation.
1062 """
1063 nom = models.CharField(max_length=255)
1064 fichier = models.FileField(
1065 upload_to=dossier_piece_dispatch, storage=storage_prive
1066 )
1067
1068 class Meta:
1069 abstract = True
1070 ordering = ['nom']
1071
1072 def __unicode__(self):
1073 return u'%s' % (self.nom)
1074
1075
1076 class DossierPiece(DossierPiece_):
1077 dossier = models.ForeignKey(
1078 Dossier, db_column='dossier', related_name='rh_dossierpieces'
1079 )
1080
1081 reversion.register(DossierPiece, format='xml')
1082
1083 class DossierCommentaire(Commentaire):
1084 dossier = models.ForeignKey(
1085 Dossier, db_column='dossier', related_name='commentaires'
1086 )
1087
1088 reversion.register(DossierCommentaire, format='xml')
1089
1090
1091 class DossierComparaison_(models.Model, DevisableMixin):
1092 """
1093 Photo d'une comparaison salariale au moment de l'embauche.
1094 """
1095 objects = DossierComparaisonManager()
1096
1097 implantation = models.ForeignKey(
1098 ref.Implantation, related_name="+", null=True, blank=True
1099 )
1100 poste = models.CharField(max_length=255, null=True, blank=True)
1101 personne = models.CharField(max_length=255, null=True, blank=True)
1102 montant = models.IntegerField(null=True)
1103 devise = models.ForeignKey(
1104 'Devise', related_name='+', null=True, blank=True
1105 )
1106
1107 class Meta:
1108 abstract = True
1109
1110 def __unicode__(self):
1111 return "%s (%s)" % (self.poste, self.personne)
1112
1113
1114 class DossierComparaison(DossierComparaison_):
1115 dossier = models.ForeignKey(
1116 Dossier, related_name='rh_comparaisons'
1117 )
1118
1119 reversion.register(DossierComparaison, format='xml')
1120
1121
1122 ### RÉMUNÉRATION
1123
1124 class RemunerationMixin(models.Model):
1125
1126 # Identification
1127 type = models.ForeignKey(
1128 'TypeRemuneration', db_column='type', related_name='+',
1129 verbose_name=u"type de rémunération"
1130 )
1131 type_revalorisation = models.ForeignKey(
1132 'TypeRevalorisation', db_column='type_revalorisation',
1133 related_name='+', verbose_name=u"type de revalorisation",
1134 null=True, blank=True
1135 )
1136 montant = models.DecimalField(
1137 null=True, blank=True, max_digits=12, decimal_places=2
1138 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1139 devise = models.ForeignKey('Devise', db_column='devise', related_name='+')
1140
1141 # commentaire = precision
1142 commentaire = models.CharField(max_length=255, null=True, blank=True)
1143
1144 # date_debut = anciennement date_effectif
1145 date_debut = models.DateField(
1146 u"date de début", null=True, blank=True, db_index=True
1147 )
1148 date_fin = models.DateField(
1149 u"date de fin", null=True, blank=True, db_index=True
1150 )
1151
1152 class Meta:
1153 abstract = True
1154 ordering = ['type__nom', '-date_fin']
1155
1156 def __unicode__(self):
1157 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
1158
1159
1160 class Remuneration_(RemunerationMixin, DevisableMixin):
1161 """
1162 Structure de rémunération (données budgétaires) en situation normale
1163 pour un Dossier. Si un Evenement existe, utiliser la structure de
1164 rémunération EvenementRemuneration de cet événement.
1165 """
1166
1167 def montant_mois(self):
1168 return round(self.montant / 12, 2)
1169
1170 def montant_avec_regime(self):
1171 return round(self.montant * (self.dossier.regime_travail / 100), 2)
1172
1173 def montant_euro_mois(self):
1174 return round(self.montant_euros() / 12, 2)
1175
1176 def __unicode__(self):
1177 try:
1178 devise = self.devise.code
1179 except:
1180 devise = "???"
1181 return "%s %s" % (self.montant, devise)
1182
1183 class Meta:
1184 abstract = True
1185 verbose_name = u"Rémunération"
1186 verbose_name_plural = u"Rémunérations"
1187
1188
1189 class Remuneration(Remuneration_):
1190 dossier = models.ForeignKey(
1191 Dossier, db_column='dossier', related_name='rh_remunerations'
1192 )
1193
1194 reversion.register(Remuneration, format='xml')
1195
1196
1197 ### CONTRATS
1198
1199 class ContratManager(models.Manager):
1200
1201 def get_query_set(self):
1202 return super(ContratManager, self).get_query_set() \
1203 .select_related('dossier', 'dossier__poste')
1204
1205
1206 class Contrat_(models.Model):
1207 """
1208 Document juridique qui encadre la relation de travail d'un Employe
1209 pour un Poste particulier. Pour un Dossier (qui documente cette
1210 relation de travail) plusieurs contrats peuvent être associés.
1211 """
1212 objects = ContratManager()
1213 type_contrat = models.ForeignKey(
1214 'TypeContrat', db_column='type_contrat',
1215 verbose_name=u'type de contrat', related_name='+'
1216 )
1217 date_debut = models.DateField(
1218 u"date de début", db_index=True
1219 )
1220 date_fin = models.DateField(
1221 u"date de fin", null=True, blank=True, db_index=True
1222 )
1223 fichier = models.FileField(
1224 upload_to=contrat_dispatch, storage=storage_prive, null=True,
1225 blank=True
1226 )
1227
1228 class Meta:
1229 abstract = True
1230 ordering = ['dossier__employe__nom']
1231 verbose_name = u"Contrat"
1232 verbose_name_plural = u"Contrats"
1233
1234 def __unicode__(self):
1235 return u'%s - %s' % (self.dossier, self.id)
1236
1237
1238 class Contrat(Contrat_):
1239 dossier = models.ForeignKey(
1240 Dossier, db_column='dossier', related_name='rh_contrats'
1241 )
1242
1243 reversion.register(Contrat, format='xml')
1244
1245
1246 ### RÉFÉRENCES RH
1247
1248 class CategorieEmploi(models.Model):
1249 """
1250 Catégorie utilisée dans la gestion des Postes.
1251 Catégorie supérieure à TypePoste.
1252 """
1253 nom = models.CharField(max_length=255)
1254
1255 class Meta:
1256 ordering = ('nom',)
1257 verbose_name = u"catégorie d'emploi"
1258 verbose_name_plural = u"catégories d'emploi"
1259
1260 def __unicode__(self):
1261 return self.nom
1262
1263 reversion.register(CategorieEmploi, format='xml')
1264
1265
1266 class FamilleProfessionnelle(models.Model):
1267 """
1268 Famille professionnelle d'un poste.
1269 """
1270 nom = models.CharField(max_length=100)
1271
1272 class Meta:
1273 ordering = ('nom',)
1274 verbose_name = u'famille professionnelle'
1275 verbose_name_plural = u'familles professionnelles'
1276
1277 def __unicode__(self):
1278 return self.nom
1279
1280 reversion.register(FamilleProfessionnelle, format='xml')
1281
1282
1283 class TypePoste(models.Model):
1284 """
1285 Catégorie de Poste.
1286 """
1287 nom = models.CharField(max_length=255)
1288 nom_feminin = models.CharField(u"nom féminin", max_length=255)
1289 is_responsable = models.BooleanField(
1290 u"poste de responsabilité", default=False
1291 )
1292 categorie_emploi = models.ForeignKey(
1293 CategorieEmploi, db_column='categorie_emploi', related_name='+',
1294 verbose_name=u"catégorie d'emploi"
1295 )
1296 famille_professionnelle = models.ForeignKey(
1297 FamilleProfessionnelle, related_name='types_de_poste',
1298 verbose_name=u"famille professionnelle", blank=True, null=True
1299 )
1300
1301 class Meta:
1302 ordering = ['nom']
1303 verbose_name = u"Type de poste"
1304 verbose_name_plural = u"Types de poste"
1305
1306 def __unicode__(self):
1307 return u'%s' % (self.nom)
1308
1309 reversion.register(TypePoste, format='xml')
1310
1311
1312 TYPE_PAIEMENT_CHOICES = (
1313 (u'Régulier', u'Régulier'),
1314 (u'Ponctuel', u'Ponctuel'),
1315 )
1316
1317 NATURE_REMUNERATION_CHOICES = (
1318 (u'Accessoire', u'Accessoire'),
1319 (u'Charges', u'Charges'),
1320 (u'Indemnité', u'Indemnité'),
1321 (u'RAS', u'Rémunération autre source'),
1322 (u'Traitement', u'Traitement'),
1323 )
1324
1325
1326 class TypeRemuneration(Archivable):
1327 """
1328 Catégorie de Remuneration.
1329 """
1330 objects = TypeRemunerationManager()
1331
1332 nom = models.CharField(max_length=255)
1333 type_paiement = models.CharField(
1334 u"type de paiement", max_length=30, choices=TYPE_PAIEMENT_CHOICES
1335 )
1336 nature_remuneration = models.CharField(
1337 u"nature de la rémunération", max_length=30,
1338 choices=NATURE_REMUNERATION_CHOICES
1339 )
1340
1341 class Meta:
1342 ordering = ['nom']
1343 verbose_name = u"Type de rémunération"
1344 verbose_name_plural = u"Types de rémunération"
1345
1346 def __unicode__(self):
1347 return self.nom
1348
1349 reversion.register(TypeRemuneration, format='xml')
1350
1351
1352 class TypeRevalorisation(models.Model):
1353 """
1354 Justification du changement de la Remuneration.
1355 (Actuellement utilisé dans aucun traitement informatique.)
1356 """
1357 nom = models.CharField(max_length=255)
1358
1359 class Meta:
1360 ordering = ['nom']
1361 verbose_name = u"Type de revalorisation"
1362 verbose_name_plural = u"Types de revalorisation"
1363
1364 def __unicode__(self):
1365 return u'%s' % (self.nom)
1366
1367 reversion.register(TypeRevalorisation, format='xml')
1368
1369
1370 class Service(Archivable):
1371 """
1372 Unité administrative où les Postes sont rattachés.
1373 """
1374 nom = models.CharField(max_length=255)
1375
1376 class Meta:
1377 ordering = ['nom']
1378 verbose_name = u"service"
1379 verbose_name_plural = u"services"
1380
1381 def __unicode__(self):
1382 return self.nom
1383
1384 reversion.register(Service, format='xml')
1385
1386
1387 TYPE_ORGANISME_CHOICES = (
1388 ('MAD', 'Mise à disposition'),
1389 ('DET', 'Détachement'),
1390 )
1391
1392
1393 class OrganismeBstg(models.Model):
1394 """
1395 Organisation d'où provient un Employe mis à disposition (MAD) de
1396 ou détaché (DET) à l'AUF à titre gratuit.
1397
1398 (BSTG = bien et service à titre gratuit.)
1399 """
1400 nom = models.CharField(max_length=255)
1401 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
1402 pays = models.ForeignKey(ref.Pays, to_field='code',
1403 db_column='pays',
1404 related_name='organismes_bstg',
1405 null=True, blank=True)
1406
1407 class Meta:
1408 ordering = ['type', 'nom']
1409 verbose_name = u"Organisme BSTG"
1410 verbose_name_plural = u"Organismes BSTG"
1411
1412 def __unicode__(self):
1413 return u'%s (%s)' % (self.nom, self.get_type_display())
1414
1415 prefix_implantation = "pays__region"
1416
1417 def get_regions(self):
1418 return [self.pays.region]
1419
1420 reversion.register(OrganismeBstg, format='xml')
1421
1422
1423 class Statut(models.Model):
1424 """
1425 Statut de l'Employe dans le cadre d'un Dossier particulier.
1426 """
1427 # Identification
1428 code = models.CharField(
1429 max_length=25, unique=True,
1430 help_text=(
1431 u"Saisir un code court mais lisible pour ce statut : "
1432 u"le code est utilisé pour associer les statuts aux autres "
1433 u"données tout en demeurant plus lisible qu'un identifiant "
1434 u"numérique."
1435 )
1436 )
1437 nom = models.CharField(max_length=255)
1438
1439 class Meta:
1440 ordering = ['code']
1441 verbose_name = u"Statut d'employé"
1442 verbose_name_plural = u"Statuts d'employé"
1443
1444 def __unicode__(self):
1445 return u'%s : %s' % (self.code, self.nom)
1446
1447 reversion.register(Statut, format='xml')
1448
1449
1450 TYPE_CLASSEMENT_CHOICES = (
1451 ('S', 'S -Soutien'),
1452 ('T', 'T - Technicien'),
1453 ('P', 'P - Professionel'),
1454 ('C', 'C - Cadre'),
1455 ('D', 'D - Direction'),
1456 ('SO', 'SO - Sans objet [expatriés]'),
1457 ('HG', 'HG - Hors grille [direction]'),
1458 )
1459
1460
1461 class ClassementManager(models.Manager):
1462 """
1463 Ordonner les spcéfiquement les classements.
1464 """
1465 def get_query_set(self):
1466 qs = super(self.__class__, self).get_query_set()
1467 qs = qs.extra(select={
1468 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1469 })
1470 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
1471 return qs.all()
1472
1473
1474 class Classement_(models.Model):
1475 """
1476 Éléments de classement de la
1477 "Grille générique de classement hiérarchique".
1478
1479 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1480 classement dans la grille. Le classement donne le coefficient utilisé dans:
1481
1482 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1483 """
1484 objects = ClassementManager()
1485
1486 # Identification
1487 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
1488 echelon = models.IntegerField(u"échelon", blank=True, default=0)
1489 degre = models.IntegerField(u"degré", blank=True, default=0)
1490 coefficient = models.FloatField(u"coefficient", blank=True, null=True)
1491
1492 # Méta
1493 # annee # au lieu de date_debut et date_fin
1494 commentaire = models.TextField(null=True, blank=True)
1495
1496 class Meta:
1497 abstract = True
1498 ordering = ['type', 'echelon', 'degre', 'coefficient']
1499 verbose_name = u"Classement"
1500 verbose_name_plural = u"Classements"
1501
1502 def __unicode__(self):
1503 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
1504
1505
1506 class Classement(Classement_):
1507 __doc__ = Classement_.__doc__
1508
1509 reversion.register(Classement, format='xml')
1510
1511
1512 class TauxChange_(models.Model):
1513 """
1514 Taux de change de la devise vers l'euro (EUR)
1515 pour chaque année budgétaire.
1516 """
1517 # Identification
1518 devise = models.ForeignKey('Devise', db_column='devise')
1519 annee = models.IntegerField(u"année")
1520 taux = models.FloatField(u"taux vers l'euro")
1521
1522 class Meta:
1523 abstract = True
1524 ordering = ['-annee', 'devise__code']
1525 verbose_name = u"Taux de change"
1526 verbose_name_plural = u"Taux de change"
1527 unique_together = ('devise', 'annee')
1528
1529 def __unicode__(self):
1530 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
1531
1532
1533 class TauxChange(TauxChange_):
1534 __doc__ = TauxChange_.__doc__
1535
1536 reversion.register(TauxChange, format='xml')
1537
1538
1539 class ValeurPointManager(models.Manager):
1540
1541 def get_query_set(self):
1542 return super(ValeurPointManager, self).get_query_set() \
1543 .select_related('devise', 'implantation')
1544
1545
1546 class ValeurPoint_(models.Model):
1547 """
1548 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1549 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1550 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1551
1552 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1553 """
1554
1555 objects = models.Manager()
1556 actuelles = ValeurPointManager()
1557
1558 valeur = models.FloatField(null=True)
1559 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
1560 implantation = models.ForeignKey(ref.Implantation,
1561 db_column='implantation',
1562 related_name='%(app_label)s_valeur_point')
1563 # Méta
1564 annee = models.IntegerField()
1565
1566 class Meta:
1567 ordering = ['-annee', 'implantation__nom']
1568 abstract = True
1569 verbose_name = u"Valeur du point"
1570 verbose_name_plural = u"Valeurs du point"
1571 unique_together = ('implantation', 'annee')
1572
1573 def __unicode__(self):
1574 return u'%s %s %s [%s] %s' % (
1575 self.devise.code, self.annee, self.valeur,
1576 self.implantation.nom_court, self.devise.nom
1577 )
1578
1579
1580 class ValeurPoint(ValeurPoint_):
1581 __doc__ = ValeurPoint_.__doc__
1582
1583 reversion.register(ValeurPoint, format='xml')
1584
1585
1586 class Devise(Archivable):
1587 """
1588 Devise monétaire.
1589 """
1590 code = models.CharField(max_length=10, unique=True)
1591 nom = models.CharField(max_length=255)
1592
1593 class Meta:
1594 ordering = ['code']
1595 verbose_name = u"devise"
1596 verbose_name_plural = u"devises"
1597
1598 def __unicode__(self):
1599 return u'%s - %s' % (self.code, self.nom)
1600
1601 reversion.register(Devise, format='xml')
1602
1603
1604 class TypeContrat(models.Model):
1605 """
1606 Type de contrat.
1607 """
1608 nom = models.CharField(max_length=255)
1609 nom_long = models.CharField(max_length=255)
1610
1611 class Meta:
1612 ordering = ['nom']
1613 verbose_name = u"Type de contrat"
1614 verbose_name_plural = u"Types de contrat"
1615
1616 def __unicode__(self):
1617 return u'%s' % (self.nom)
1618
1619 reversion.register(TypeContrat, format='xml')
1620
1621
1622 ### AUTRES
1623
1624 class ResponsableImplantationProxy(ref.Implantation):
1625
1626 def save(self):
1627 pass
1628
1629 class Meta:
1630 proxy = True
1631 verbose_name = u"Responsable d'implantation"
1632 verbose_name_plural = u"Responsables d'implantation"
1633
1634
1635 class ResponsableImplantation(models.Model):
1636 """
1637 Le responsable d'une implantation.
1638 Anciennement géré sur le Dossier du responsable.
1639 """
1640 employe = models.ForeignKey(
1641 'Employe', db_column='employe', related_name='+', null=True,
1642 blank=True
1643 )
1644 implantation = models.OneToOneField(
1645 "ResponsableImplantationProxy", db_column='implantation',
1646 related_name='responsable', unique=True
1647 )
1648
1649 def __unicode__(self):
1650 return u'%s : %s' % (self.implantation, self.employe)
1651
1652 class Meta:
1653 ordering = ['implantation__nom']
1654 verbose_name = "Responsable d'implantation"
1655 verbose_name_plural = "Responsables d'implantation"
1656
1657 reversion.register(ResponsableImplantation, format='xml')