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