Ticket 5454
[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.contrib.auth.models import User
12 from django.core.files.storage import FileSystemStorage
13 from django.db import models
14 from django.db.models import Q
15 from django.conf import settings
16
17 from project.rh.change_list import \
18 RechercheTemporelle, KEY_STATUT, STATUT_ACTIF, STATUT_INACTIF, \
19 STATUT_FUTUR
20 from project import groups
21 from project.rh.managers import (
22 PosteManager,
23 DossierManager,
24 EmployeManager,
25 DossierComparaisonManager,
26 PosteComparaisonManager,
27 ContratManager,
28 RemunerationManager,
29 ArchivableManager,
30 )
31
32
33 from project.rh.validators import validate_date_passee
34
35 # import pour relocaliser le modèle selon la convention (models.py pour
36 # introspection)
37 from project.rh.historique import ModificationTraite
38
39 # Constantes
40 HELP_TEXT_DATE = "format: jj-mm-aaaa"
41 REGIME_TRAVAIL_DEFAULT = Decimal('100.00')
42 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = Decimal('35.00')
43 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT = \
44 "Saisir le nombre d'heure de travail à temps complet (100%), " \
45 "sans tenir compte du régime de travail"
46
47 # Upload de fichiers
48 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
49 base_url=settings.PRIVE_MEDIA_URL)
50
51
52 class RemunIntegrityException(Exception):
53 pass
54
55 def poste_piece_dispatch(instance, filename):
56 path = "%s/poste/%s/%s" % (
57 instance._meta.app_label, instance.poste_id, filename
58 )
59 return path
60
61
62 def dossier_piece_dispatch(instance, filename):
63 path = "%s/dossier/%s/%s" % (
64 instance._meta.app_label, instance.dossier_id, filename
65 )
66 return path
67
68
69 def employe_piece_dispatch(instance, filename):
70 path = "%s/employe/%s/%s" % (
71 instance._meta.app_label, instance.employe_id, filename
72 )
73 return path
74
75
76 def contrat_dispatch(instance, filename):
77 path = "%s/contrat/%s/%s" % (
78 instance._meta.app_label, instance.dossier_id, filename
79 )
80 return path
81
82
83 class DateActiviteMixin(models.Model):
84 """
85 Mixin pour mettre à jour l'activité d'un modèle
86 """
87 class Meta:
88 abstract = True
89 date_creation = models.DateTimeField(auto_now_add=True,
90 null=True, blank=True,
91 verbose_name=u"Date de création",)
92 date_modification = models.DateTimeField(auto_now=True,
93 null=True, blank=True,
94 verbose_name=u"Date de modification",)
95
96
97 class Archivable(models.Model):
98 archive = models.BooleanField(u'archivé', default=False)
99
100 objects = ArchivableManager()
101 avec_archives = models.Manager()
102
103 class Meta:
104 abstract = True
105
106
107 class DevisableMixin(object):
108
109 def get_annee_pour_taux_devise(self):
110 return datetime.datetime.now().year
111
112 def taux_devise(self, devise=None):
113 if devise is None:
114 devise = self.devise
115
116 if devise is None:
117 return None
118 if devise.code == "EUR":
119 return 1
120
121 annee = self.get_annee_pour_taux_devise()
122 taux = TauxChange.objects.filter(devise=devise, annee__lte=annee) \
123 .order_by('-annee')
124 return taux[0].taux
125
126 def montant_euros(self):
127 try:
128 taux = self.taux_devise()
129 except Exception, e:
130 return e
131 if not taux:
132 return None
133 return int(round(float(self.montant) * float(taux), 2))
134
135
136 class Commentaire(models.Model):
137 texte = models.TextField()
138 owner = models.ForeignKey(
139 'auth.User', db_column='owner', related_name='+',
140 verbose_name=u"Commentaire de"
141 )
142 date_creation = models.DateTimeField(
143 u'date', auto_now_add=True, blank=True, null=True
144 )
145
146 class Meta:
147 abstract = True
148 ordering = ['-date_creation']
149
150 def __unicode__(self):
151 return u'%s' % (self.texte)
152
153
154 ### POSTE
155
156 POSTE_APPEL_CHOICES = (
157 ('interne', 'Interne'),
158 ('externe', 'Externe'),
159 )
160
161
162 class Poste_( DateActiviteMixin, models.Model,):
163 """
164 Un Poste est un emploi (job) à combler dans une implantation.
165 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
166 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
167 """
168
169 objects = PosteManager()
170
171 # Identification
172 nom = models.CharField(u"Titre du poste", max_length=255)
173 nom_feminin = models.CharField(
174 u"Titre du poste (au féminin)", max_length=255, null=True
175 )
176 implantation = models.ForeignKey(
177 ref.Implantation,
178 help_text=u"Taper le nom de l'implantation ou sa région",
179 db_column='implantation', related_name='+'
180 )
181 type_poste = models.ForeignKey(
182 'TypePoste', db_column='type_poste',
183 help_text=u"Taper le nom du type de poste", related_name='+',
184 null=True, verbose_name=u"type de poste"
185 )
186 service = models.ForeignKey(
187 'Service', db_column='service', related_name='%(app_label)s_postes',
188 verbose_name=u"direction/service/pôle support", null=True
189 )
190 responsable = models.ForeignKey(
191 'Poste', db_column='responsable',
192 related_name='+', null=True,
193 help_text=u"Taper le nom du poste ou du type de poste",
194 verbose_name=u"Poste du responsable"
195 )
196
197 # Contrat
198 regime_travail = models.DecimalField(
199 u"temps de travail", max_digits=12, decimal_places=2,
200 default=REGIME_TRAVAIL_DEFAULT, null=True,
201 help_text="% du temps complet"
202 )
203 regime_travail_nb_heure_semaine = models.DecimalField(
204 u"nb. heures par semaine", max_digits=12, decimal_places=2,
205 null=True, default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
206 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
207 )
208
209 # Recrutement
210 local = models.NullBooleanField(
211 u"local", default=True, null=True, blank=True
212 )
213 expatrie = models.NullBooleanField(
214 u"expatrié", default=False, null=True, blank=True
215 )
216 mise_a_disposition = models.NullBooleanField(
217 u"mise à disposition", null=True, default=False
218 )
219 appel = models.CharField(
220 u"Appel à candidature", max_length=10, null=True,
221 choices=POSTE_APPEL_CHOICES, default='interne'
222 )
223
224 # Rémunération
225 classement_min = models.ForeignKey(
226 'Classement', db_column='classement_min', related_name='+',
227 null=True, blank=True
228 )
229 classement_max = models.ForeignKey(
230 'Classement', db_column='classement_max', related_name='+',
231 null=True, blank=True
232 )
233 valeur_point_min = models.ForeignKey(
234 'ValeurPoint',
235 help_text=u"Taper le code ou le nom de l'implantation",
236 db_column='valeur_point_min', related_name='+', null=True,
237 blank=True
238 )
239 valeur_point_max = models.ForeignKey(
240 'ValeurPoint',
241 help_text=u"Taper le code ou le nom de l'implantation",
242 db_column='valeur_point_max', related_name='+', null=True,
243 blank=True
244 )
245 devise_min = models.ForeignKey(
246 'Devise', db_column='devise_min', null=True, related_name='+'
247 )
248 devise_max = models.ForeignKey(
249 'Devise', db_column='devise_max', null=True, related_name='+'
250 )
251 salaire_min = models.DecimalField(
252 max_digits=12, decimal_places=2, default=0,
253 )
254 salaire_max = models.DecimalField(
255 max_digits=12, decimal_places=2, default=0,
256 )
257 indemn_min = models.DecimalField(
258 max_digits=12, decimal_places=2, default=0,
259 )
260 indemn_max = models.DecimalField(
261 max_digits=12, decimal_places=2, default=0,
262 )
263 autre_min = models.DecimalField(
264 max_digits=12, decimal_places=2, default=0,
265 )
266 autre_max = models.DecimalField(
267 max_digits=12, decimal_places=2, default=0,
268 )
269
270 # Comparatifs de rémunération
271 devise_comparaison = models.ForeignKey(
272 'Devise', null=True, blank=True, db_column='devise_comparaison',
273 related_name='+'
274 )
275 comp_locale_min = models.DecimalField(
276 max_digits=12, decimal_places=2, null=True, blank=True
277 )
278 comp_locale_max = models.DecimalField(
279 max_digits=12, decimal_places=2, null=True, blank=True
280 )
281 comp_universite_min = models.DecimalField(
282 max_digits=12, decimal_places=2, null=True, blank=True
283 )
284 comp_universite_max = models.DecimalField(
285 max_digits=12, decimal_places=2, null=True, blank=True
286 )
287 comp_fonctionpub_min = models.DecimalField(
288 max_digits=12, decimal_places=2, null=True, blank=True
289 )
290 comp_fonctionpub_max = models.DecimalField(
291 max_digits=12, decimal_places=2, null=True, blank=True
292 )
293 comp_ong_min = models.DecimalField(
294 max_digits=12, decimal_places=2, null=True, blank=True
295 )
296 comp_ong_max = models.DecimalField(
297 max_digits=12, decimal_places=2, null=True, blank=True
298 )
299 comp_autre_min = models.DecimalField(
300 max_digits=12, decimal_places=2, null=True, blank=True
301 )
302 comp_autre_max = models.DecimalField(
303 max_digits=12, decimal_places=2, null=True, blank=True
304 )
305
306 # Justification
307 justification = models.TextField(null=True, blank=True)
308
309 # Autres Metadata
310 date_debut = models.DateField(
311 u"date de début", help_text=HELP_TEXT_DATE, null=True, blank=True,
312 db_index=True
313 )
314 date_fin = models.DateField(
315 u"date de fin", help_text=HELP_TEXT_DATE, null=True, blank=True,
316 db_index=True
317 )
318
319 class Meta:
320 abstract = True
321 ordering = ['implantation__nom', 'nom']
322 verbose_name = u"Poste"
323 verbose_name_plural = u"Postes"
324 ordering = ["nom"]
325
326 def __unicode__(self):
327 representation = u'%s - %s [%s]' % (
328 self.implantation, self.nom, self.id
329 )
330 return representation
331
332 prefix_implantation = "implantation__zone_administrative"
333
334 def get_zones_administratives(self):
335 return [self.implantation.zone_administrative]
336
337 def get_devise(self):
338 vp = ValeurPoint.objects.filter(
339 implantation=self.implantation, devise__archive=False
340 ).order_by('annee')
341 if len(vp) > 0:
342 return vp[0].devise
343 else:
344 return Devise.objects.get(code='EUR')
345
346
347 class Poste(Poste_):
348 __doc__ = Poste_.__doc__
349
350 # meta dématérialisation : pour permettre le filtrage
351 vacant = models.NullBooleanField(u"vacant", null=True, blank=True)
352
353 def is_vacant(self):
354 vacant = True
355 if self.occupe_par():
356 vacant = False
357 return vacant
358
359 def occupe_par(self):
360 """
361 Retourne la liste d'employé occupant ce poste.
362 Généralement, retourne une liste d'un élément.
363 Si poste inoccupé, retourne liste vide.
364 UTILISE pour mettre a jour le flag vacant
365 """
366 return [
367 d.employe
368 for d in self.rh_dossiers.exclude(date_fin__lt=date.today())
369 ]
370
371 reversion.register(Poste, format='xml', follow=[
372 'rh_financements', 'rh_pieces', 'rh_comparaisons_internes',
373 'commentaires'
374 ])
375
376
377 POSTE_FINANCEMENT_CHOICES = (
378 ('A', 'A - Frais de personnel'),
379 ('B', 'B - Projet(s)-Titre(s)'),
380 ('C', 'C - Autre')
381 )
382
383
384 class PosteFinancement_(models.Model):
385 """
386 Pour un Poste, structure d'informations décrivant comment on prévoit
387 financer ce Poste.
388 """
389 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
390 pourcentage = models.DecimalField(
391 max_digits=12, decimal_places=2,
392 help_text="ex.: 33.33 % (décimale avec point)"
393 )
394 commentaire = models.TextField(
395 help_text="Spécifiez la source de financement."
396 )
397
398 class Meta:
399 abstract = True
400 ordering = ['type']
401
402 def __unicode__(self):
403 return u'%s : %s %%' % (self.type, self.pourcentage)
404
405 def choix(self):
406 return u"%s" % dict(POSTE_FINANCEMENT_CHOICES)[self.type]
407
408
409 class PosteFinancement(PosteFinancement_):
410 poste = models.ForeignKey(
411 Poste, db_column='poste', related_name='rh_financements'
412 )
413
414 reversion.register(PosteFinancement, format='xml')
415
416
417 class PostePiece_(models.Model):
418 """
419 Documents relatifs au Poste.
420 Ex.: Description de poste
421 """
422 nom = models.CharField(u"Nom", max_length=255)
423 fichier = models.FileField(
424 u"Fichier", upload_to=poste_piece_dispatch, storage=storage_prive
425 )
426
427 class Meta:
428 abstract = True
429 ordering = ['nom']
430
431 def __unicode__(self):
432 return u'%s' % (self.nom)
433
434
435 class PostePiece(PostePiece_):
436 poste = models.ForeignKey(
437 Poste, db_column='poste', related_name='rh_pieces'
438 )
439
440 reversion.register(PostePiece, format='xml')
441
442
443 class PosteComparaison_(models.Model, DevisableMixin):
444 """
445 De la même manière qu'un dossier, un poste peut-être comparé à un autre
446 poste.
447 """
448 objects = PosteComparaisonManager()
449
450 implantation = models.ForeignKey(
451 ref.Implantation, null=True, blank=True, related_name="+"
452 )
453 nom = models.CharField(u"Poste", max_length=255, null=True, blank=True)
454 montant = models.IntegerField(null=True)
455 devise = models.ForeignKey(
456 "Devise", related_name='+', null=True, blank=True
457 )
458
459 class Meta:
460 abstract = True
461
462 def __unicode__(self):
463 return self.nom
464
465
466 class PosteComparaison(PosteComparaison_):
467 poste = models.ForeignKey(
468 Poste, related_name='rh_comparaisons_internes'
469 )
470
471 reversion.register(PosteComparaison, format='xml')
472
473
474 class PosteCommentaire(Commentaire):
475 poste = models.ForeignKey(
476 Poste, db_column='poste', related_name='commentaires'
477 )
478
479 reversion.register(PosteCommentaire, format='xml')
480
481 ### EMPLOYÉ/PERSONNE
482
483 class Employe(models.Model):
484 """
485 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
486 Dossiers qu'il occupe ou a occupé de Postes.
487
488 Cette classe aurait pu avantageusement s'appeler Personne car la notion
489 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
490 """
491
492 objects = EmployeManager()
493
494 # Identification
495 nom = models.CharField(max_length=255)
496 prenom = models.CharField(u"prénom", max_length=255)
497 nom_affichage = models.CharField(
498 u"nom d'affichage", max_length=255, null=True, blank=True
499 )
500 nationalite = models.ForeignKey(
501 ref.Pays, to_field='code', db_column='nationalite',
502 related_name='employes_nationalite', verbose_name=u"nationalité",
503 blank=True, null=True
504 )
505 date_naissance = models.DateField(
506 u"date de naissance", help_text=HELP_TEXT_DATE,
507 validators=[validate_date_passee], null=True, blank=True
508 )
509 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
510
511 # Infos personnelles
512 situation_famille = models.CharField(
513 u"situation familiale", max_length=1, choices=SITUATION_CHOICES,
514 null=True, blank=True
515 )
516 date_entree = models.DateField(
517 u"date d'entrée à l'AUF", help_text=HELP_TEXT_DATE, null=True,
518 blank=True
519 )
520
521 # Coordonnées
522 tel_domicile = models.CharField(
523 u"tél. domicile", max_length=255, null=True, blank=True
524 )
525 tel_cellulaire = models.CharField(
526 u"tél. cellulaire", max_length=255, null=True, blank=True
527 )
528 adresse = models.CharField(max_length=255, null=True, blank=True)
529 ville = models.CharField(max_length=255, null=True, blank=True)
530 province = models.CharField(max_length=255, null=True, blank=True)
531 code_postal = models.CharField(max_length=255, null=True, blank=True)
532 pays = models.ForeignKey(
533 ref.Pays, to_field='code', db_column='pays',
534 related_name='employes', null=True, blank=True
535 )
536 courriel_perso = models.EmailField(
537 u'adresse courriel personnelle', blank=True
538 )
539
540 # meta dématérialisation : pour permettre le filtrage
541 nb_postes = models.IntegerField(u"nombre de postes", null=True, blank=True)
542
543 class Meta:
544 ordering = ['nom', 'prenom']
545 verbose_name = u"Employé"
546 verbose_name_plural = u"Employés"
547
548 def __unicode__(self):
549 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
550
551 def civilite(self):
552 civilite = u''
553 if self.genre.upper() == u'M':
554 civilite = u'M.'
555 elif self.genre.upper() == u'F':
556 civilite = u'Mme'
557 return civilite
558
559 def url_photo(self):
560 """
561 Retourne l'URL du service retournant la photo de l'Employe.
562 Équivalent reverse url 'rh_photo' avec id en param.
563 """
564 from django.core.urlresolvers import reverse
565 return reverse('rh_photo', kwargs={'id': self.id})
566
567 def dossiers_passes(self):
568 params = {KEY_STATUT: STATUT_INACTIF, }
569 search = RechercheTemporelle(params, Dossier)
570 search.purge_params(params)
571 q = search.get_q_temporel(self.rh_dossiers)
572 return self.rh_dossiers.filter(q)
573
574 def dossiers_futurs(self):
575 params = {KEY_STATUT: STATUT_FUTUR, }
576 search = RechercheTemporelle(params, Dossier)
577 search.purge_params(params)
578 q = search.get_q_temporel(self.rh_dossiers)
579 return self.rh_dossiers.filter(q)
580
581 def dossiers_encours(self):
582 params = {KEY_STATUT: STATUT_ACTIF, }
583 search = RechercheTemporelle(params, Dossier)
584 search.purge_params(params)
585 q = search.get_q_temporel(self.rh_dossiers)
586 return self.rh_dossiers.filter(q)
587
588 def dossier_principal(self):
589 """
590 Retourne le dossier principal (ou le plus ancien si il y en a
591 plusieurs)
592 """
593 try:
594 dossier = self.rh_dossiers \
595 .filter(principal=True).order_by('date_debut')[0]
596 except IndexError, Dossier.DoesNotExist:
597 dossier = None
598 return dossier
599
600 def postes_encours(self):
601 postes_encours = set()
602 for d in self.dossiers_encours():
603 postes_encours.add(d.poste)
604 return postes_encours
605
606 def poste_principal(self):
607 """
608 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
609 Idée derrière :
610 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
611 """
612 # DEPRECATED : on a maintenant Dossier.principal
613 poste = Poste.objects.none()
614 try:
615 poste = self.dossiers_encours().order_by('date_debut')[0].poste
616 except:
617 pass
618 return poste
619
620 prefix_implantation = \
621 "rh_dossiers__poste__implantation__zone_administrative"
622
623 def get_zones_administratives(self):
624 return [
625 d.poste.implantation.zone_administrative
626 for d in self.dossiers.all()
627 ]
628
629 reversion.register(Employe, format='xml', follow=[
630 'pieces', 'commentaires', 'ayantdroits'
631 ])
632
633
634 class EmployePiece(models.Model):
635 """
636 Documents relatifs à un employé.
637 Ex.: CV...
638 """
639 employe = models.ForeignKey(
640 'Employe', db_column='employe', related_name="pieces",
641 verbose_name=u"employé"
642 )
643 nom = models.CharField(max_length=255)
644 fichier = models.FileField(
645 u"fichier", upload_to=employe_piece_dispatch, storage=storage_prive
646 )
647
648 class Meta:
649 ordering = ['nom']
650 verbose_name = u"Employé pièce"
651 verbose_name_plural = u"Employé pièces"
652
653 def __unicode__(self):
654 return u'%s' % (self.nom)
655
656 reversion.register(EmployePiece, format='xml')
657
658
659 class EmployeCommentaire(Commentaire):
660 employe = models.ForeignKey(
661 'Employe', db_column='employe', related_name='commentaires'
662 )
663
664 class Meta:
665 verbose_name = u"Employé commentaire"
666 verbose_name_plural = u"Employé commentaires"
667
668 reversion.register(EmployeCommentaire, format='xml')
669
670
671 LIEN_PARENTE_CHOICES = (
672 ('Conjoint', 'Conjoint'),
673 ('Conjointe', 'Conjointe'),
674 ('Fille', 'Fille'),
675 ('Fils', 'Fils'),
676 )
677
678
679 class AyantDroit(models.Model):
680 """
681 Personne en relation avec un Employe.
682 """
683 # Identification
684 nom = models.CharField(max_length=255)
685 prenom = models.CharField(u"prénom", max_length=255)
686 nom_affichage = models.CharField(
687 u"nom d'affichage", max_length=255, null=True, blank=True
688 )
689 nationalite = models.ForeignKey(
690 ref.Pays, to_field='code', db_column='nationalite',
691 related_name='ayantdroits_nationalite',
692 verbose_name=u"nationalité", null=True, blank=True
693 )
694 date_naissance = models.DateField(
695 u"Date de naissance", help_text=HELP_TEXT_DATE,
696 validators=[validate_date_passee], null=True, blank=True
697 )
698 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
699
700 # Relation
701 employe = models.ForeignKey(
702 'Employe', db_column='employe', related_name='ayantdroits',
703 verbose_name=u"Employé"
704 )
705 lien_parente = models.CharField(
706 u"lien de parenté", max_length=10, choices=LIEN_PARENTE_CHOICES,
707 null=True, blank=True
708 )
709
710 class Meta:
711 ordering = ['nom', ]
712 verbose_name = u"Ayant droit"
713 verbose_name_plural = u"Ayants droit"
714
715 def __unicode__(self):
716 return u'%s %s' % (self.nom.upper(), self.prenom, )
717
718 prefix_implantation = \
719 "employe__dossiers__poste__implantation__zone_administrative"
720
721 def get_zones_administratives(self):
722 return [
723 d.poste.implantation.zone_administrative
724 for d in self.employe.dossiers.all()
725 ]
726
727 reversion.register(AyantDroit, format='xml', follow=['commentaires'])
728
729
730 class AyantDroitCommentaire(Commentaire):
731 ayant_droit = models.ForeignKey(
732 'AyantDroit', db_column='ayant_droit', related_name='commentaires'
733 )
734
735 reversion.register(AyantDroitCommentaire, format='xml')
736
737
738 ### DOSSIER
739
740 STATUT_RESIDENCE_CHOICES = (
741 ('local', 'Local'),
742 ('expat', 'Expatrié'),
743 )
744
745 COMPTE_COMPTA_CHOICES = (
746 ('coda', 'CODA'),
747 ('scs', 'SCS'),
748 ('aucun', 'Aucun'),
749 )
750
751
752 class Dossier_(DateActiviteMixin, models.Model, DevisableMixin,):
753 """
754 Le Dossier regroupe les informations relatives à l'occupation
755 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
756 par un Employe.
757
758 Plusieurs Contrats peuvent être associés au Dossier.
759 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
760 lequel aucun Dossier n'existe est un poste vacant.
761 """
762
763 objects = DossierManager()
764
765 # TODO: OneToOne ??
766 statut = models.ForeignKey('Statut', related_name='+', null=True)
767 organisme_bstg = models.ForeignKey(
768 'OrganismeBstg', db_column='organisme_bstg', related_name='+',
769 verbose_name=u"organisme",
770 help_text=(
771 u"Si détaché (DET) ou mis à disposition (MAD), "
772 u"préciser l'organisme."
773 ), null=True, blank=True
774 )
775
776 # Recrutement
777 remplacement = models.BooleanField(default=False)
778 remplacement_de = models.ForeignKey(
779 'self', related_name='+', help_text=u"Taper le nom de l'employé",
780 null=True, blank=True
781 )
782 statut_residence = models.CharField(
783 u"statut", max_length=10, default='local', null=True,
784 choices=STATUT_RESIDENCE_CHOICES
785 )
786
787 # Rémunération
788 classement = models.ForeignKey(
789 'Classement', db_column='classement', related_name='+', null=True,
790 blank=True
791 )
792 regime_travail = models.DecimalField(
793 u"régime de travail", max_digits=12, null=True, decimal_places=2,
794 default=REGIME_TRAVAIL_DEFAULT, help_text="% du temps complet"
795 )
796 regime_travail_nb_heure_semaine = models.DecimalField(
797 u"nb. heures par semaine", max_digits=12,
798 decimal_places=2, null=True,
799 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
800 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
801 )
802
803 # Occupation du Poste par cet Employe (anciennement "mandat")
804 date_debut = models.DateField(
805 u"date de début d'occupation de poste", db_index=True
806 )
807 date_fin = models.DateField(
808 u"Date de fin d'occupation de poste", null=True, blank=True,
809 db_index=True
810 )
811
812 # Meta-data:
813 est_cadre = models.BooleanField(
814 u"Est un câdre?",
815 default=False,
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__zone_administrative"
844
845 def get_zones_administratives(self):
846 return [self.poste.implantation.zone_administrative]
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 Contrat_(models.Model):
1219 """
1220 Document juridique qui encadre la relation de travail d'un Employe
1221 pour un Poste particulier. Pour un Dossier (qui documente cette
1222 relation de travail) plusieurs contrats peuvent être associés.
1223 """
1224 objects = ContratManager()
1225 type_contrat = models.ForeignKey(
1226 'TypeContrat', db_column='type_contrat',
1227 verbose_name=u'type de contrat', related_name='+'
1228 )
1229 date_debut = models.DateField(
1230 u"date de début", db_index=True
1231 )
1232 date_fin = models.DateField(
1233 u"date de fin", null=True, blank=True, db_index=True
1234 )
1235 fichier = models.FileField(
1236 upload_to=contrat_dispatch, storage=storage_prive, null=True,
1237 blank=True
1238 )
1239
1240 class Meta:
1241 abstract = True
1242 ordering = ['dossier__employe__nom']
1243 verbose_name = u"Contrat"
1244 verbose_name_plural = u"Contrats"
1245
1246 def __unicode__(self):
1247 return u'%s - %s' % (self.dossier, self.id)
1248
1249
1250 class Contrat(Contrat_):
1251 dossier = models.ForeignKey(
1252 Dossier, db_column='dossier', related_name='rh_contrats'
1253 )
1254
1255 reversion.register(Contrat, format='xml')
1256
1257
1258 ### RÉFÉRENCES RH
1259
1260 class CategorieEmploi(models.Model):
1261 """
1262 Catégorie utilisée dans la gestion des Postes.
1263 Catégorie supérieure à TypePoste.
1264 """
1265 nom = models.CharField(max_length=255)
1266
1267 class Meta:
1268 ordering = ('nom',)
1269 verbose_name = u"catégorie d'emploi"
1270 verbose_name_plural = u"catégories d'emploi"
1271
1272 def __unicode__(self):
1273 return self.nom
1274
1275 reversion.register(CategorieEmploi, format='xml')
1276
1277
1278 class FamilleProfessionnelle(models.Model):
1279 """
1280 Famille professionnelle d'un poste.
1281 """
1282 nom = models.CharField(max_length=100)
1283
1284 class Meta:
1285 ordering = ('nom',)
1286 verbose_name = u'famille professionnelle'
1287 verbose_name_plural = u'familles professionnelles'
1288
1289 def __unicode__(self):
1290 return self.nom
1291
1292 reversion.register(FamilleProfessionnelle, format='xml')
1293
1294
1295 class TypePoste(Archivable):
1296 """
1297 Catégorie de Poste.
1298 """
1299 nom = models.CharField(max_length=255)
1300 nom_feminin = models.CharField(u"nom féminin", max_length=255)
1301 is_responsable = models.BooleanField(
1302 u"poste de responsabilité", default=False
1303 )
1304 categorie_emploi = models.ForeignKey(
1305 CategorieEmploi, db_column='categorie_emploi', related_name='+',
1306 verbose_name=u"catégorie d'emploi"
1307 )
1308 famille_professionnelle = models.ForeignKey(
1309 FamilleProfessionnelle, related_name='types_de_poste',
1310 verbose_name=u"famille professionnelle", blank=True, null=True
1311 )
1312
1313 class Meta:
1314 ordering = ['nom']
1315 verbose_name = u"Type de poste"
1316 verbose_name_plural = u"Types de poste"
1317
1318 def __unicode__(self):
1319 return u'%s' % (self.nom)
1320
1321 reversion.register(TypePoste, format='xml')
1322
1323
1324 TYPE_PAIEMENT_CHOICES = (
1325 (u'Régulier', u'Régulier'),
1326 (u'Ponctuel', u'Ponctuel'),
1327 )
1328
1329 NATURE_REMUNERATION_CHOICES = (
1330 (u'Accessoire', u'Traitement ponctuel'),
1331 (u'Charges', u'Charges patronales'),
1332 (u'Indemnité', u'Indemnité'),
1333 (u'RAS', u'Rémunération autre source'),
1334 (u'Traitement', u'Traitement'),
1335 )
1336
1337
1338 class TypeRemuneration(Archivable):
1339 """
1340 Catégorie de Remuneration.
1341 """
1342
1343 nom = models.CharField(max_length=255)
1344 type_paiement = models.CharField(
1345 u"type de paiement", max_length=30, choices=TYPE_PAIEMENT_CHOICES
1346 )
1347
1348 nature_remuneration = models.CharField(
1349 u"nature de la rémunération", max_length=30,
1350 choices=NATURE_REMUNERATION_CHOICES
1351 )
1352
1353 class Meta:
1354 ordering = ['nom']
1355 verbose_name = u"Type de rémunération"
1356 verbose_name_plural = u"Types de rémunération"
1357
1358 def __unicode__(self):
1359 return self.nom
1360
1361 reversion.register(TypeRemuneration, format='xml')
1362
1363
1364 class TypeRevalorisation(Archivable):
1365 """
1366 Justification du changement de la Remuneration.
1367 (Actuellement utilisé dans aucun traitement informatique.)
1368 """
1369 nom = models.CharField(max_length=255)
1370
1371 class Meta:
1372 ordering = ['nom']
1373 verbose_name = u"Type de revalorisation"
1374 verbose_name_plural = u"Types de revalorisation"
1375
1376 def __unicode__(self):
1377 return u'%s' % (self.nom)
1378
1379 reversion.register(TypeRevalorisation, format='xml')
1380
1381
1382 class Service(Archivable):
1383 """
1384 Unité administrative où les Postes sont rattachés.
1385 """
1386 nom = models.CharField(max_length=255)
1387
1388 class Meta:
1389 ordering = ['nom']
1390 verbose_name = u"service"
1391 verbose_name_plural = u"services"
1392
1393 def __unicode__(self):
1394 return self.nom
1395
1396 reversion.register(Service, format='xml')
1397
1398
1399 TYPE_ORGANISME_CHOICES = (
1400 ('MAD', 'Mise à disposition'),
1401 ('DET', 'Détachement'),
1402 )
1403
1404
1405 class OrganismeBstg(models.Model):
1406 """
1407 Organisation d'où provient un Employe mis à disposition (MAD) de
1408 ou détaché (DET) à l'AUF à titre gratuit.
1409
1410 (BSTG = bien et service à titre gratuit.)
1411 """
1412 nom = models.CharField(max_length=255)
1413 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
1414 pays = models.ForeignKey(ref.Pays, to_field='code',
1415 db_column='pays',
1416 related_name='organismes_bstg',
1417 null=True, blank=True)
1418
1419 class Meta:
1420 ordering = ['type', 'nom']
1421 verbose_name = u"Organisme BSTG"
1422 verbose_name_plural = u"Organismes BSTG"
1423
1424 def __unicode__(self):
1425 return u'%s (%s)' % (self.nom, self.get_type_display())
1426
1427 reversion.register(OrganismeBstg, format='xml')
1428
1429
1430 class Statut(Archivable):
1431 """
1432 Statut de l'Employe dans le cadre d'un Dossier particulier.
1433 """
1434 # Identification
1435 code = models.CharField(
1436 max_length=25, unique=True,
1437 help_text=(
1438 u"Saisir un code court mais lisible pour ce statut : "
1439 u"le code est utilisé pour associer les statuts aux autres "
1440 u"données tout en demeurant plus lisible qu'un identifiant "
1441 u"numérique."
1442 )
1443 )
1444 nom = models.CharField(max_length=255)
1445
1446 class Meta:
1447 ordering = ['code']
1448 verbose_name = u"Statut d'employé"
1449 verbose_name_plural = u"Statuts d'employé"
1450
1451 def __unicode__(self):
1452 return u'%s : %s' % (self.code, self.nom)
1453
1454 reversion.register(Statut, format='xml')
1455
1456
1457 TYPE_CLASSEMENT_CHOICES = (
1458 ('S', 'S -Soutien'),
1459 ('T', 'T - Technicien'),
1460 ('P', 'P - Professionel'),
1461 ('C', 'C - Cadre'),
1462 ('D', 'D - Direction'),
1463 ('SO', 'SO - Sans objet [expatriés]'),
1464 ('HG', 'HG - Hors grille [direction]'),
1465 )
1466
1467
1468 class ClassementManager(ArchivableManager):
1469 """
1470 Ordonner les spcéfiquement les classements.
1471 """
1472 def get_query_set(self):
1473 qs = super(self.__class__, self).get_query_set()
1474 qs = qs.extra(select={
1475 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1476 })
1477 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
1478 return qs.all()
1479
1480
1481 class Classement_(Archivable):
1482 """
1483 Éléments de classement de la
1484 "Grille générique de classement hiérarchique".
1485
1486 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1487 classement dans la grille. Le classement donne le coefficient utilisé dans:
1488
1489 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1490 """
1491 objects = ClassementManager()
1492
1493 # Identification
1494 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
1495 echelon = models.IntegerField(u"échelon", blank=True, default=0)
1496 degre = models.IntegerField(u"degré", blank=True, default=0)
1497 coefficient = models.FloatField(u"coefficient", blank=True, null=True)
1498
1499 # Méta
1500 # annee # au lieu de date_debut et date_fin
1501 commentaire = models.TextField(null=True, blank=True)
1502
1503 class Meta:
1504 abstract = True
1505 ordering = ['type', 'echelon', 'degre', 'coefficient']
1506 verbose_name = u"Classement"
1507 verbose_name_plural = u"Classements"
1508
1509 def __unicode__(self):
1510 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
1511
1512
1513 class Classement(Classement_):
1514 __doc__ = Classement_.__doc__
1515
1516 reversion.register(Classement, format='xml')
1517
1518
1519 class TauxChange_(models.Model):
1520 """
1521 Taux de change de la devise vers l'euro (EUR)
1522 pour chaque année budgétaire.
1523 """
1524 # Identification
1525 devise = models.ForeignKey('Devise', db_column='devise')
1526 annee = models.IntegerField(u"année")
1527 taux = models.FloatField(u"taux vers l'euro")
1528
1529 class Meta:
1530 abstract = True
1531 ordering = ['-annee', 'devise__code']
1532 verbose_name = u"Taux de change"
1533 verbose_name_plural = u"Taux de change"
1534 unique_together = ('devise', 'annee')
1535
1536 def __unicode__(self):
1537 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
1538
1539
1540 class TauxChange(TauxChange_):
1541 __doc__ = TauxChange_.__doc__
1542
1543 reversion.register(TauxChange, format='xml')
1544
1545
1546 class ValeurPointManager(models.Manager):
1547
1548 def get_query_set(self):
1549 return super(ValeurPointManager, self).get_query_set() \
1550 .select_related('devise', 'implantation')
1551
1552
1553 class ValeurPoint_(models.Model):
1554 """
1555 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1556 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1557 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1558
1559 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1560 """
1561
1562 objects = models.Manager()
1563 actuelles = ValeurPointManager()
1564
1565 valeur = models.FloatField(null=True)
1566 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
1567 implantation = models.ForeignKey(ref.Implantation,
1568 db_column='implantation',
1569 related_name='%(app_label)s_valeur_point')
1570 # Méta
1571 annee = models.IntegerField()
1572
1573 class Meta:
1574 ordering = ['-annee', 'implantation__nom']
1575 abstract = True
1576 verbose_name = u"Valeur du point"
1577 verbose_name_plural = u"Valeurs du point"
1578 unique_together = ('implantation', 'annee')
1579
1580 def __unicode__(self):
1581 return u'%s %s %s [%s] %s' % (
1582 self.devise.code, self.annee, self.valeur,
1583 self.implantation.nom_court, self.devise.nom
1584 )
1585
1586
1587 class ValeurPoint(ValeurPoint_):
1588 __doc__ = ValeurPoint_.__doc__
1589
1590 reversion.register(ValeurPoint, format='xml')
1591
1592
1593 class Devise(Archivable):
1594 """
1595 Devise monétaire.
1596 """
1597 code = models.CharField(max_length=10, unique=True)
1598 nom = models.CharField(max_length=255)
1599
1600 class Meta:
1601 ordering = ['code']
1602 verbose_name = u"devise"
1603 verbose_name_plural = u"devises"
1604
1605 def __unicode__(self):
1606 return u'%s - %s' % (self.code, self.nom)
1607
1608 reversion.register(Devise, format='xml')
1609
1610
1611 class TypeContrat(Archivable):
1612 """
1613 Type de contrat.
1614 """
1615 nom = models.CharField(max_length=255)
1616 nom_long = models.CharField(max_length=255)
1617
1618 class Meta:
1619 ordering = ['nom']
1620 verbose_name = u"Type de contrat"
1621 verbose_name_plural = u"Types de contrat"
1622
1623 def __unicode__(self):
1624 return u'%s' % (self.nom)
1625
1626 reversion.register(TypeContrat, format='xml')
1627
1628
1629 ### AUTRES
1630
1631 class ResponsableImplantationProxy(ref.Implantation):
1632
1633 def save(self):
1634 pass
1635
1636 class Meta:
1637 managed = False
1638 proxy = True
1639 verbose_name = u"Responsable d'implantation"
1640 verbose_name_plural = u"Responsables d'implantation"
1641
1642
1643 class ResponsableImplantation(models.Model):
1644 """
1645 Le responsable d'une implantation.
1646 Anciennement géré sur le Dossier du responsable.
1647 """
1648 employe = models.ForeignKey(
1649 'Employe', db_column='employe', related_name='+', null=True,
1650 blank=True
1651 )
1652 implantation = models.OneToOneField(
1653 "ResponsableImplantationProxy", db_column='implantation',
1654 related_name='responsable', unique=True
1655 )
1656
1657 def __unicode__(self):
1658 return u'%s : %s' % (self.implantation, self.employe)
1659
1660 class Meta:
1661 ordering = ['implantation__nom']
1662 verbose_name = "Responsable d'implantation"
1663 verbose_name_plural = "Responsables d'implantation"
1664
1665 reversion.register(ResponsableImplantation, format='xml')
1666
1667
1668 class UserProfile(models.Model):
1669 user = models.OneToOneField(User, related_name='profile')
1670 zones_administratives = models.ManyToManyField(
1671 ref.ZoneAdministrative,
1672 related_name='profiles'
1673 )
1674 class Meta:
1675 verbose_name = "Permissions sur zones administratives"
1676 verbose_name_plural = "Permissions sur zones administratives"
1677
1678 def __unicode__(self):
1679 return self.user.__unicode__()
1680
1681 reversion.register(UserProfile, format='xml')