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