fix responsable / implantation
[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__zone_administrative"
345
346 def get_zones_administratives(self):
347 return [self.implantation.zone_administrative]
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, Dossier)
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, Dossier)
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, Dossier)
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 = \
633 "rh_dossiers__poste__implantation__zone_administrative"
634
635 def get_zones_administratives(self):
636 return [
637 d.poste.implantation.zone_administrative
638 for d in self.dossiers.all()
639 ]
640
641 reversion.register(Employe, format='xml', follow=[
642 'pieces', 'commentaires', 'ayantdroits'
643 ])
644
645
646 class EmployePiece(models.Model):
647 """
648 Documents relatifs à un employé.
649 Ex.: CV...
650 """
651 employe = models.ForeignKey(
652 'Employe', db_column='employe', related_name="pieces",
653 verbose_name=u"employé"
654 )
655 nom = models.CharField(max_length=255)
656 fichier = models.FileField(
657 u"fichier", upload_to=employe_piece_dispatch, storage=storage_prive
658 )
659
660 class Meta:
661 ordering = ['nom']
662 verbose_name = u"Employé pièce"
663 verbose_name_plural = u"Employé pièces"
664
665 def __unicode__(self):
666 return u'%s' % (self.nom)
667
668 reversion.register(EmployePiece, format='xml')
669
670
671 class EmployeCommentaire(Commentaire):
672 employe = models.ForeignKey(
673 'Employe', db_column='employe', related_name='commentaires'
674 )
675
676 class Meta:
677 verbose_name = u"Employé commentaire"
678 verbose_name_plural = u"Employé commentaires"
679
680 reversion.register(EmployeCommentaire, format='xml')
681
682
683 LIEN_PARENTE_CHOICES = (
684 ('Conjoint', 'Conjoint'),
685 ('Conjointe', 'Conjointe'),
686 ('Fille', 'Fille'),
687 ('Fils', 'Fils'),
688 )
689
690
691 class AyantDroit(models.Model):
692 """
693 Personne en relation avec un Employe.
694 """
695 # Identification
696 nom = models.CharField(max_length=255)
697 prenom = models.CharField(u"prénom", max_length=255)
698 nom_affichage = models.CharField(
699 u"nom d'affichage", max_length=255, null=True, blank=True
700 )
701 nationalite = models.ForeignKey(
702 ref.Pays, to_field='code', db_column='nationalite',
703 related_name='ayantdroits_nationalite',
704 verbose_name=u"nationalité", null=True, blank=True
705 )
706 date_naissance = models.DateField(
707 u"Date de naissance", help_text=HELP_TEXT_DATE,
708 validators=[validate_date_passee], null=True, blank=True
709 )
710 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
711
712 # Relation
713 employe = models.ForeignKey(
714 'Employe', db_column='employe', related_name='ayantdroits',
715 verbose_name=u"Employé"
716 )
717 lien_parente = models.CharField(
718 u"lien de parenté", max_length=10, choices=LIEN_PARENTE_CHOICES,
719 null=True, blank=True
720 )
721
722 class Meta:
723 ordering = ['nom', ]
724 verbose_name = u"Ayant droit"
725 verbose_name_plural = u"Ayants droit"
726
727 def __unicode__(self):
728 return u'%s %s' % (self.nom.upper(), self.prenom, )
729
730 prefix_implantation = \
731 "employe__dossiers__poste__implantation__zone_administrative"
732
733 def get_zones_administratives(self):
734 return [
735 d.poste.implantation.zone_administrative
736 for d in self.employe.dossiers.all()
737 ]
738
739 reversion.register(AyantDroit, format='xml', follow=['commentaires'])
740
741
742 class AyantDroitCommentaire(Commentaire):
743 ayant_droit = models.ForeignKey(
744 'AyantDroit', db_column='ayant_droit', related_name='commentaires'
745 )
746
747 reversion.register(AyantDroitCommentaire, format='xml')
748
749
750 ### DOSSIER
751
752 STATUT_RESIDENCE_CHOICES = (
753 ('local', 'Local'),
754 ('expat', 'Expatrié'),
755 )
756
757 COMPTE_COMPTA_CHOICES = (
758 ('coda', 'CODA'),
759 ('scs', 'SCS'),
760 ('aucun', 'Aucun'),
761 )
762
763
764 class Dossier_(DateActiviteMixin, models.Model, DevisableMixin,):
765 """
766 Le Dossier regroupe les informations relatives à l'occupation
767 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
768 par un Employe.
769
770 Plusieurs Contrats peuvent être associés au Dossier.
771 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
772 lequel aucun Dossier n'existe est un poste vacant.
773 """
774
775 objects = DossierManager()
776
777 # TODO: OneToOne ??
778 statut = models.ForeignKey('Statut', related_name='+', null=True)
779 organisme_bstg = models.ForeignKey(
780 'OrganismeBstg', db_column='organisme_bstg', related_name='+',
781 verbose_name=u"organisme",
782 help_text=(
783 u"Si détaché (DET) ou mis à disposition (MAD), "
784 u"préciser l'organisme."
785 ), null=True, blank=True
786 )
787
788 # Recrutement
789 remplacement = models.BooleanField(default=False)
790 remplacement_de = models.ForeignKey(
791 'self', related_name='+', help_text=u"Taper le nom de l'employé",
792 null=True, blank=True
793 )
794 statut_residence = models.CharField(
795 u"statut", max_length=10, default='local', null=True,
796 choices=STATUT_RESIDENCE_CHOICES
797 )
798
799 # Rémunération
800 classement = models.ForeignKey(
801 'Classement', db_column='classement', related_name='+', null=True,
802 blank=True
803 )
804 regime_travail = models.DecimalField(
805 u"régime de travail", max_digits=12, null=True, decimal_places=2,
806 default=REGIME_TRAVAIL_DEFAULT, help_text="% du temps complet"
807 )
808 regime_travail_nb_heure_semaine = models.DecimalField(
809 u"nb. heures par semaine", max_digits=12,
810 decimal_places=2, null=True,
811 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
812 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
813 )
814
815 # Occupation du Poste par cet Employe (anciennement "mandat")
816 date_debut = models.DateField(
817 u"date de début d'occupation de poste", db_index=True
818 )
819 date_fin = models.DateField(
820 u"Date de fin d'occupation de poste", null=True, blank=True,
821 db_index=True
822 )
823
824 # Comptes
825 # TODO?
826
827 class Meta:
828 abstract = True
829 ordering = ['employe__nom', ]
830 verbose_name = u"Dossier"
831 verbose_name_plural = "Dossiers"
832
833 def salaire_theorique(self):
834 annee = date.today().year
835 coeff = self.classement.coefficient
836 implantation = self.poste.implantation
837 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
838
839 montant = coeff * point.valeur
840 devise = point.devise
841 return {'montant': montant, 'devise': devise}
842
843 def __unicode__(self):
844 poste = self.poste.nom
845 if self.employe.genre == 'F':
846 poste = self.poste.nom_feminin
847 return u'%s - %s' % (self.employe, poste)
848
849 prefix_implantation = "poste__implantation__zone_administrative"
850
851 def get_zones_administratives(self):
852 return [self.poste.implantation.zone_administrative]
853
854 def remunerations(self):
855 key = "%s_remunerations" % self._meta.app_label
856 remunerations = getattr(self, key)
857 return remunerations.all().order_by('-date_debut')
858
859 def remunerations_en_cours(self):
860 q = Q(date_fin__exact=None) | Q(date_fin__gt=datetime.date.today())
861 return self.remunerations().all().filter(q).order_by('date_debut')
862
863 def get_salaire(self):
864 try:
865 return [r for r in self.remunerations().order_by('-date_debut')
866 if r.type_id == 1][0]
867 except:
868 return None
869
870 def get_salaire_euros(self):
871 tx = self.taux_devise()
872 return (float)(tx) * (float)(self.salaire)
873
874 def get_remunerations_brutes(self):
875 """
876 1 Salaire de base
877 3 Indemnité de base
878 4 Indemnité d'expatriation
879 5 Indemnité pour frais
880 6 Indemnité de logement
881 7 Indemnité de fonction
882 8 Indemnité de responsabilité
883 9 Indemnité de transport
884 10 Indemnité compensatrice
885 11 Indemnité de subsistance
886 12 Indemnité différentielle
887 13 Prime d'installation
888 14 Billet d'avion
889 15 Déménagement
890 16 Indemnité de départ
891 18 Prime de 13ième mois
892 19 Prime d'intérim
893 """
894 ids = [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
895 return [r for r in self.remunerations_en_cours().all()
896 if r.type_id in ids]
897
898 def get_charges_salariales(self):
899 """
900 20 Charges salariales ?
901 """
902 ids = [20]
903 return [r for r in self.remunerations_en_cours().all()
904 if r.type_id in ids]
905
906 def get_charges_patronales(self):
907 """
908 17 Charges patronales
909 """
910 ids = [17]
911 return [r for r in self.remunerations_en_cours().all()
912 if r.type_id in ids]
913
914 def get_remunerations_tierces(self):
915 """
916 2 Salaire MAD
917 """
918 return [r for r in self.remunerations_en_cours().all()
919 if r.type_id in (2,)]
920
921 # DEVISE LOCALE
922
923 def get_total_local_charges_salariales(self):
924 devise = self.poste.get_devise()
925 total = 0.0
926 for r in self.get_charges_salariales():
927 if r.devise != devise:
928 return None
929 total += float(r.montant)
930 return total
931
932 def get_total_local_charges_patronales(self):
933 devise = self.poste.get_devise()
934 total = 0.0
935 for r in self.get_charges_patronales():
936 if r.devise != devise:
937 return None
938 total += float(r.montant)
939 return total
940
941 def get_local_salaire_brut(self):
942 """
943 somme des rémuérations brutes
944 """
945 devise = self.poste.get_devise()
946 total = 0.0
947 for r in self.get_remunerations_brutes():
948 if r.devise != devise:
949 return None
950 total += float(r.montant)
951 return total
952
953 def get_local_salaire_net(self):
954 """
955 salaire brut - charges salariales
956 """
957 devise = self.poste.get_devise()
958 total_charges = 0.0
959 for r in self.get_charges_salariales():
960 if r.devise != devise:
961 return None
962 total_charges += float(r.montant)
963 return self.get_local_salaire_brut() - total_charges
964
965 def get_local_couts_auf(self):
966 """
967 salaire net + charges patronales
968 """
969 devise = self.poste.get_devise()
970 total_charges = 0.0
971 for r in self.get_charges_patronales():
972 if r.devise != devise:
973 return None
974 total_charges += float(r.montant)
975 return self.get_local_salaire_net() + total_charges
976
977 def get_total_local_remunerations_tierces(self):
978 devise = self.poste.get_devise()
979 total = 0.0
980 for r in self.get_remunerations_tierces():
981 if r.devise != devise:
982 return None
983 total += float(r.montant)
984 return total
985
986 # DEVISE EURO
987
988 def get_total_charges_salariales(self):
989 total = 0.0
990 for r in self.get_charges_salariales():
991 total += r.montant_euros()
992 return total
993
994 def get_total_charges_patronales(self):
995 total = 0.0
996 for r in self.get_charges_patronales():
997 total += r.montant_euros()
998 return total
999
1000 def get_salaire_brut(self):
1001 """
1002 somme des rémuérations brutes
1003 """
1004 total = 0.0
1005 for r in self.get_remunerations_brutes():
1006 total += r.montant_euros()
1007 return total
1008
1009 def get_salaire_net(self):
1010 """
1011 salaire brut - charges salariales
1012 """
1013 total_charges = 0.0
1014 for r in self.get_charges_salariales():
1015 total_charges += r.montant_euros()
1016 return self.get_salaire_brut() - total_charges
1017
1018 def get_couts_auf(self):
1019 """
1020 salaire net + charges patronales
1021 """
1022 total_charges = 0.0
1023 for r in self.get_charges_patronales():
1024 total_charges += r.montant_euros()
1025 return self.get_salaire_net() + total_charges
1026
1027 def get_total_remunerations_tierces(self):
1028 total = 0.0
1029 for r in self.get_remunerations_tierces():
1030 total += r.montant_euros()
1031 return total
1032
1033 def premier_contrat(self):
1034 """contrat avec plus petite date de début"""
1035 try:
1036 contrat = self.rh_contrats.exclude(date_debut=None) \
1037 .order_by('date_debut')[0]
1038 except IndexError, Contrat.DoesNotExist:
1039 contrat = None
1040 return contrat
1041
1042 def dernier_contrat(self):
1043 """contrat avec plus grande date de fin"""
1044 try:
1045 contrat = self.rh_contrats.exclude(date_debut=None) \
1046 .order_by('-date_debut')[0]
1047 except IndexError, Contrat.DoesNotExist:
1048 contrat = None
1049 return contrat
1050
1051 def actif(self):
1052 today = date.today()
1053 return (self.date_debut is None or self.date_debut <= today) \
1054 and (self.date_fin is None or self.date_fin >= today) \
1055 and not (self.date_fin is None and self.date_debut is None)
1056
1057
1058 class Dossier(Dossier_):
1059 __doc__ = Dossier_.__doc__
1060 poste = models.ForeignKey(
1061 Poste, db_column='poste', related_name='rh_dossiers',
1062 help_text=u"Taper le nom du poste ou du type de poste",
1063 )
1064 employe = models.ForeignKey(
1065 'Employe', db_column='employe',
1066 help_text=u"Taper le nom de l'employé",
1067 related_name='rh_dossiers', verbose_name=u"employé"
1068 )
1069 principal = models.BooleanField(
1070 u"dossier principal", default=True,
1071 help_text=(
1072 u"Ce dossier est pour le principal poste occupé par l'employé"
1073 )
1074 )
1075
1076 reversion.register(Dossier, format='xml', follow=[
1077 'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
1078 'rh_contrats', 'commentaires'
1079 ])
1080
1081
1082 class DossierPiece_(models.Model):
1083 """
1084 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1085 Ex.: Lettre de motivation.
1086 """
1087 nom = models.CharField(max_length=255)
1088 fichier = models.FileField(
1089 upload_to=dossier_piece_dispatch, storage=storage_prive
1090 )
1091
1092 class Meta:
1093 abstract = True
1094 ordering = ['nom']
1095
1096 def __unicode__(self):
1097 return u'%s' % (self.nom)
1098
1099
1100 class DossierPiece(DossierPiece_):
1101 dossier = models.ForeignKey(
1102 Dossier, db_column='dossier', related_name='rh_dossierpieces'
1103 )
1104
1105 reversion.register(DossierPiece, format='xml')
1106
1107 class DossierCommentaire(Commentaire):
1108 dossier = models.ForeignKey(
1109 Dossier, db_column='dossier', related_name='commentaires'
1110 )
1111
1112 reversion.register(DossierCommentaire, format='xml')
1113
1114
1115 class DossierComparaison_(models.Model, DevisableMixin):
1116 """
1117 Photo d'une comparaison salariale au moment de l'embauche.
1118 """
1119 objects = DossierComparaisonManager()
1120
1121 implantation = models.ForeignKey(
1122 ref.Implantation, related_name="+", null=True, blank=True
1123 )
1124 poste = models.CharField(max_length=255, null=True, blank=True)
1125 personne = models.CharField(max_length=255, null=True, blank=True)
1126 montant = models.IntegerField(null=True)
1127 devise = models.ForeignKey(
1128 'Devise', related_name='+', null=True, blank=True
1129 )
1130
1131 class Meta:
1132 abstract = True
1133
1134 def __unicode__(self):
1135 return "%s (%s)" % (self.poste, self.personne)
1136
1137
1138 class DossierComparaison(DossierComparaison_):
1139 dossier = models.ForeignKey(
1140 Dossier, related_name='rh_comparaisons'
1141 )
1142
1143 reversion.register(DossierComparaison, format='xml')
1144
1145
1146 ### RÉMUNÉRATION
1147
1148 class RemunerationMixin(models.Model):
1149
1150 # Identification
1151 type = models.ForeignKey(
1152 'TypeRemuneration', db_column='type', related_name='+',
1153 verbose_name=u"type de rémunération"
1154 )
1155 type_revalorisation = models.ForeignKey(
1156 'TypeRevalorisation', db_column='type_revalorisation',
1157 related_name='+', verbose_name=u"type de revalorisation",
1158 null=True, blank=True
1159 )
1160 montant = models.DecimalField(
1161 null=True, blank=True, max_digits=12, decimal_places=2
1162 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1163 devise = models.ForeignKey('Devise', db_column='devise', related_name='+')
1164
1165 # commentaire = precision
1166 commentaire = models.CharField(max_length=255, null=True, blank=True)
1167
1168 # date_debut = anciennement date_effectif
1169 date_debut = models.DateField(
1170 u"date de début", null=True, blank=True, db_index=True
1171 )
1172 date_fin = models.DateField(
1173 u"date de fin", null=True, blank=True, db_index=True
1174 )
1175
1176 class Meta:
1177 abstract = True
1178 ordering = ['type__nom', '-date_fin']
1179
1180 def __unicode__(self):
1181 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
1182
1183
1184 class Remuneration_(RemunerationMixin, DevisableMixin):
1185 """
1186 Structure de rémunération (données budgétaires) en situation normale
1187 pour un Dossier. Si un Evenement existe, utiliser la structure de
1188 rémunération EvenementRemuneration de cet événement.
1189 """
1190 objects = RemunerationManager()
1191
1192 def montant_mois(self):
1193 return round(self.montant / 12, 2)
1194
1195 def montant_avec_regime(self):
1196 return round(self.montant * (self.dossier.regime_travail / 100), 2)
1197
1198 def montant_euro_mois(self):
1199 return round(self.montant_euros() / 12, 2)
1200
1201 def __unicode__(self):
1202 try:
1203 devise = self.devise.code
1204 except:
1205 devise = "???"
1206 return "%s %s" % (self.montant, devise)
1207
1208 class Meta:
1209 abstract = True
1210 verbose_name = u"Rémunération"
1211 verbose_name_plural = u"Rémunérations"
1212
1213
1214 class Remuneration(Remuneration_):
1215 dossier = models.ForeignKey(
1216 Dossier, db_column='dossier', related_name='rh_remunerations'
1217 )
1218
1219 reversion.register(Remuneration, format='xml')
1220
1221
1222 ### CONTRATS
1223
1224 class Contrat_(models.Model):
1225 """
1226 Document juridique qui encadre la relation de travail d'un Employe
1227 pour un Poste particulier. Pour un Dossier (qui documente cette
1228 relation de travail) plusieurs contrats peuvent être associés.
1229 """
1230 objects = ContratManager()
1231 type_contrat = models.ForeignKey(
1232 'TypeContrat', db_column='type_contrat',
1233 verbose_name=u'type de contrat', related_name='+'
1234 )
1235 date_debut = models.DateField(
1236 u"date de début", db_index=True
1237 )
1238 date_fin = models.DateField(
1239 u"date de fin", null=True, blank=True, db_index=True
1240 )
1241 fichier = models.FileField(
1242 upload_to=contrat_dispatch, storage=storage_prive, null=True,
1243 blank=True
1244 )
1245
1246 class Meta:
1247 abstract = True
1248 ordering = ['dossier__employe__nom']
1249 verbose_name = u"Contrat"
1250 verbose_name_plural = u"Contrats"
1251
1252 def __unicode__(self):
1253 return u'%s - %s' % (self.dossier, self.id)
1254
1255
1256 class Contrat(Contrat_):
1257 dossier = models.ForeignKey(
1258 Dossier, db_column='dossier', related_name='rh_contrats'
1259 )
1260
1261 reversion.register(Contrat, format='xml')
1262
1263
1264 ### RÉFÉRENCES RH
1265
1266 class CategorieEmploi(models.Model):
1267 """
1268 Catégorie utilisée dans la gestion des Postes.
1269 Catégorie supérieure à TypePoste.
1270 """
1271 nom = models.CharField(max_length=255)
1272
1273 class Meta:
1274 ordering = ('nom',)
1275 verbose_name = u"catégorie d'emploi"
1276 verbose_name_plural = u"catégories d'emploi"
1277
1278 def __unicode__(self):
1279 return self.nom
1280
1281 reversion.register(CategorieEmploi, format='xml')
1282
1283
1284 class FamilleProfessionnelle(models.Model):
1285 """
1286 Famille professionnelle d'un poste.
1287 """
1288 nom = models.CharField(max_length=100)
1289
1290 class Meta:
1291 ordering = ('nom',)
1292 verbose_name = u'famille professionnelle'
1293 verbose_name_plural = u'familles professionnelles'
1294
1295 def __unicode__(self):
1296 return self.nom
1297
1298 reversion.register(FamilleProfessionnelle, format='xml')
1299
1300
1301 class TypePoste(models.Model):
1302 """
1303 Catégorie de Poste.
1304 """
1305 nom = models.CharField(max_length=255)
1306 nom_feminin = models.CharField(u"nom féminin", max_length=255)
1307 is_responsable = models.BooleanField(
1308 u"poste de responsabilité", default=False
1309 )
1310 categorie_emploi = models.ForeignKey(
1311 CategorieEmploi, db_column='categorie_emploi', related_name='+',
1312 verbose_name=u"catégorie d'emploi"
1313 )
1314 famille_professionnelle = models.ForeignKey(
1315 FamilleProfessionnelle, related_name='types_de_poste',
1316 verbose_name=u"famille professionnelle", blank=True, null=True
1317 )
1318
1319 class Meta:
1320 ordering = ['nom']
1321 verbose_name = u"Type de poste"
1322 verbose_name_plural = u"Types de poste"
1323
1324 def __unicode__(self):
1325 return u'%s' % (self.nom)
1326
1327 reversion.register(TypePoste, format='xml')
1328
1329
1330 TYPE_PAIEMENT_CHOICES = (
1331 (u'Régulier', u'Régulier'),
1332 (u'Ponctuel', u'Ponctuel'),
1333 )
1334
1335 NATURE_REMUNERATION_CHOICES = (
1336 (u'Accessoire', u'Accessoire'),
1337 (u'Charges', u'Charges'),
1338 (u'Indemnité', u'Indemnité'),
1339 (u'RAS', u'Rémunération autre source'),
1340 (u'Traitement', u'Traitement'),
1341 )
1342
1343
1344 class TypeRemuneration(Archivable):
1345 """
1346 Catégorie de Remuneration.
1347 """
1348 objects = TypeRemunerationManager()
1349
1350 nom = models.CharField(max_length=255)
1351 type_paiement = models.CharField(
1352 u"type de paiement", max_length=30, choices=TYPE_PAIEMENT_CHOICES
1353 )
1354 nature_remuneration = models.CharField(
1355 u"nature de la rémunération", max_length=30,
1356 choices=NATURE_REMUNERATION_CHOICES
1357 )
1358
1359 class Meta:
1360 ordering = ['nom']
1361 verbose_name = u"Type de rémunération"
1362 verbose_name_plural = u"Types de rémunération"
1363
1364 def __unicode__(self):
1365 return self.nom
1366
1367 reversion.register(TypeRemuneration, format='xml')
1368
1369
1370 class TypeRevalorisation(models.Model):
1371 """
1372 Justification du changement de la Remuneration.
1373 (Actuellement utilisé dans aucun traitement informatique.)
1374 """
1375 nom = models.CharField(max_length=255)
1376
1377 class Meta:
1378 ordering = ['nom']
1379 verbose_name = u"Type de revalorisation"
1380 verbose_name_plural = u"Types de revalorisation"
1381
1382 def __unicode__(self):
1383 return u'%s' % (self.nom)
1384
1385 reversion.register(TypeRevalorisation, format='xml')
1386
1387
1388 class Service(Archivable):
1389 """
1390 Unité administrative où les Postes sont rattachés.
1391 """
1392 nom = models.CharField(max_length=255)
1393
1394 class Meta:
1395 ordering = ['nom']
1396 verbose_name = u"service"
1397 verbose_name_plural = u"services"
1398
1399 def __unicode__(self):
1400 return self.nom
1401
1402 reversion.register(Service, format='xml')
1403
1404
1405 TYPE_ORGANISME_CHOICES = (
1406 ('MAD', 'Mise à disposition'),
1407 ('DET', 'Détachement'),
1408 )
1409
1410
1411 class OrganismeBstg(models.Model):
1412 """
1413 Organisation d'où provient un Employe mis à disposition (MAD) de
1414 ou détaché (DET) à l'AUF à titre gratuit.
1415
1416 (BSTG = bien et service à titre gratuit.)
1417 """
1418 nom = models.CharField(max_length=255)
1419 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
1420 pays = models.ForeignKey(ref.Pays, to_field='code',
1421 db_column='pays',
1422 related_name='organismes_bstg',
1423 null=True, blank=True)
1424
1425 class Meta:
1426 ordering = ['type', 'nom']
1427 verbose_name = u"Organisme BSTG"
1428 verbose_name_plural = u"Organismes BSTG"
1429
1430 def __unicode__(self):
1431 return u'%s (%s)' % (self.nom, self.get_type_display())
1432
1433 reversion.register(OrganismeBstg, format='xml')
1434
1435
1436 class Statut(models.Model):
1437 """
1438 Statut de l'Employe dans le cadre d'un Dossier particulier.
1439 """
1440 # Identification
1441 code = models.CharField(
1442 max_length=25, unique=True,
1443 help_text=(
1444 u"Saisir un code court mais lisible pour ce statut : "
1445 u"le code est utilisé pour associer les statuts aux autres "
1446 u"données tout en demeurant plus lisible qu'un identifiant "
1447 u"numérique."
1448 )
1449 )
1450 nom = models.CharField(max_length=255)
1451
1452 class Meta:
1453 ordering = ['code']
1454 verbose_name = u"Statut d'employé"
1455 verbose_name_plural = u"Statuts d'employé"
1456
1457 def __unicode__(self):
1458 return u'%s : %s' % (self.code, self.nom)
1459
1460 reversion.register(Statut, format='xml')
1461
1462
1463 TYPE_CLASSEMENT_CHOICES = (
1464 ('S', 'S -Soutien'),
1465 ('T', 'T - Technicien'),
1466 ('P', 'P - Professionel'),
1467 ('C', 'C - Cadre'),
1468 ('D', 'D - Direction'),
1469 ('SO', 'SO - Sans objet [expatriés]'),
1470 ('HG', 'HG - Hors grille [direction]'),
1471 )
1472
1473
1474 class ClassementManager(models.Manager):
1475 """
1476 Ordonner les spcéfiquement les classements.
1477 """
1478 def get_query_set(self):
1479 qs = super(self.__class__, self).get_query_set()
1480 qs = qs.extra(select={
1481 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1482 })
1483 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
1484 return qs.all()
1485
1486
1487 class Classement_(models.Model):
1488 """
1489 Éléments de classement de la
1490 "Grille générique de classement hiérarchique".
1491
1492 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1493 classement dans la grille. Le classement donne le coefficient utilisé dans:
1494
1495 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1496 """
1497 objects = ClassementManager()
1498
1499 # Identification
1500 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
1501 echelon = models.IntegerField(u"échelon", blank=True, default=0)
1502 degre = models.IntegerField(u"degré", blank=True, default=0)
1503 coefficient = models.FloatField(u"coefficient", blank=True, null=True)
1504
1505 # Méta
1506 # annee # au lieu de date_debut et date_fin
1507 commentaire = models.TextField(null=True, blank=True)
1508
1509 class Meta:
1510 abstract = True
1511 ordering = ['type', 'echelon', 'degre', 'coefficient']
1512 verbose_name = u"Classement"
1513 verbose_name_plural = u"Classements"
1514
1515 def __unicode__(self):
1516 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
1517
1518
1519 class Classement(Classement_):
1520 __doc__ = Classement_.__doc__
1521
1522 reversion.register(Classement, format='xml')
1523
1524
1525 class TauxChange_(models.Model):
1526 """
1527 Taux de change de la devise vers l'euro (EUR)
1528 pour chaque année budgétaire.
1529 """
1530 # Identification
1531 devise = models.ForeignKey('Devise', db_column='devise')
1532 annee = models.IntegerField(u"année")
1533 taux = models.FloatField(u"taux vers l'euro")
1534
1535 class Meta:
1536 abstract = True
1537 ordering = ['-annee', 'devise__code']
1538 verbose_name = u"Taux de change"
1539 verbose_name_plural = u"Taux de change"
1540 unique_together = ('devise', 'annee')
1541
1542 def __unicode__(self):
1543 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
1544
1545
1546 class TauxChange(TauxChange_):
1547 __doc__ = TauxChange_.__doc__
1548
1549 reversion.register(TauxChange, format='xml')
1550
1551
1552 class ValeurPointManager(models.Manager):
1553
1554 def get_query_set(self):
1555 return super(ValeurPointManager, self).get_query_set() \
1556 .select_related('devise', 'implantation')
1557
1558
1559 class ValeurPoint_(models.Model):
1560 """
1561 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1562 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1563 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1564
1565 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1566 """
1567
1568 objects = models.Manager()
1569 actuelles = ValeurPointManager()
1570
1571 valeur = models.FloatField(null=True)
1572 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
1573 implantation = models.ForeignKey(ref.Implantation,
1574 db_column='implantation',
1575 related_name='%(app_label)s_valeur_point')
1576 # Méta
1577 annee = models.IntegerField()
1578
1579 class Meta:
1580 ordering = ['-annee', 'implantation__nom']
1581 abstract = True
1582 verbose_name = u"Valeur du point"
1583 verbose_name_plural = u"Valeurs du point"
1584 unique_together = ('implantation', 'annee')
1585
1586 def __unicode__(self):
1587 return u'%s %s %s [%s] %s' % (
1588 self.devise.code, self.annee, self.valeur,
1589 self.implantation.nom_court, self.devise.nom
1590 )
1591
1592
1593 class ValeurPoint(ValeurPoint_):
1594 __doc__ = ValeurPoint_.__doc__
1595
1596 reversion.register(ValeurPoint, format='xml')
1597
1598
1599 class Devise(Archivable):
1600 """
1601 Devise monétaire.
1602 """
1603 code = models.CharField(max_length=10, unique=True)
1604 nom = models.CharField(max_length=255)
1605
1606 class Meta:
1607 ordering = ['code']
1608 verbose_name = u"devise"
1609 verbose_name_plural = u"devises"
1610
1611 def __unicode__(self):
1612 return u'%s - %s' % (self.code, self.nom)
1613
1614 reversion.register(Devise, format='xml')
1615
1616
1617 class TypeContrat(models.Model):
1618 """
1619 Type de contrat.
1620 """
1621 nom = models.CharField(max_length=255)
1622 nom_long = models.CharField(max_length=255)
1623
1624 class Meta:
1625 ordering = ['nom']
1626 verbose_name = u"Type de contrat"
1627 verbose_name_plural = u"Types de contrat"
1628
1629 def __unicode__(self):
1630 return u'%s' % (self.nom)
1631
1632 reversion.register(TypeContrat, format='xml')
1633
1634
1635 ### AUTRES
1636
1637 class ResponsableImplantationProxy(ref.Implantation):
1638
1639 def save(self):
1640 pass
1641
1642 class Meta:
1643 managed = False
1644 proxy = True
1645 verbose_name = u"Responsable d'implantation"
1646 verbose_name_plural = u"Responsables d'implantation"
1647
1648
1649 class ResponsableImplantation(models.Model):
1650 """
1651 Le responsable d'une implantation.
1652 Anciennement géré sur le Dossier du responsable.
1653 """
1654 employe = models.ForeignKey(
1655 'Employe', db_column='employe', related_name='+', null=True,
1656 blank=True
1657 )
1658 implantation = models.OneToOneField(
1659 "ResponsableImplantationProxy", db_column='implantation',
1660 related_name='responsable', unique=True
1661 )
1662
1663 def __unicode__(self):
1664 return u'%s : %s' % (self.implantation, self.employe)
1665
1666 class Meta:
1667 ordering = ['implantation__nom']
1668 verbose_name = "Responsable d'implantation"
1669 verbose_name_plural = "Responsables d'implantation"
1670
1671 reversion.register(ResponsableImplantation, format='xml')
1672
1673