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