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