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