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