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