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