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