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