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