[#3145] Changer les familles d'emploi pour des catégories d'emploi
[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='+',
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
516 # meta dématérialisation : pour permettre le filtrage
517 nb_postes = models.IntegerField(u"nombre de postes", null=True, blank=True)
518
519 class Meta:
520 ordering = ['nom', 'prenom']
521 verbose_name = u"Employé"
522 verbose_name_plural = u"Employés"
523
524 def __unicode__(self):
525 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
526
527 def civilite(self):
528 civilite = u''
529 if self.genre.upper() == u'M':
530 civilite = u'M.'
531 elif self.genre.upper() == u'F':
532 civilite = u'Mme'
533 return civilite
534
535 def url_photo(self):
536 """
537 Retourne l'URL du service retournant la photo de l'Employe.
538 Équivalent reverse url 'rh_photo' avec id en param.
539 """
540 from django.core.urlresolvers import reverse
541 return reverse('rh_photo', kwargs={'id': self.id})
542
543 def dossiers_passes(self):
544 params = {KEY_STATUT: STATUT_INACTIF, }
545 search = RechercheTemporelle(params, self.__class__)
546 search.purge_params(params)
547 q = search.get_q_temporel(self.rh_dossiers)
548 return self.rh_dossiers.filter(q)
549
550 def dossiers_futurs(self):
551 params = {KEY_STATUT: STATUT_FUTUR, }
552 search = RechercheTemporelle(params, self.__class__)
553 search.purge_params(params)
554 q = search.get_q_temporel(self.rh_dossiers)
555 return self.rh_dossiers.filter(q)
556
557 def dossiers_encours(self):
558 params = {KEY_STATUT: STATUT_ACTIF, }
559 search = RechercheTemporelle(params, self.__class__)
560 search.purge_params(params)
561 q = search.get_q_temporel(self.rh_dossiers)
562 return self.rh_dossiers.filter(q)
563
564 def postes_encours(self):
565 postes_encours = set()
566 for d in self.dossiers_encours():
567 postes_encours.add(d.poste)
568 return postes_encours
569
570 def poste_principal(self):
571 """
572 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
573 Idée derrière :
574 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
575 """
576 poste = Poste.objects.none()
577 try:
578 poste = self.dossiers_encours().order_by('date_debut')[0].poste
579 except:
580 pass
581 return poste
582
583 prefix_implantation = "rh_dossiers__poste__implantation__region"
584
585 def get_regions(self):
586 regions = []
587 for d in self.dossiers.all():
588 regions.append(d.poste.implantation.region)
589 return regions
590
591
592 class EmployePiece(models.Model):
593 """
594 Documents relatifs à un employé.
595 Ex.: CV...
596 """
597 employe = models.ForeignKey(
598 'Employe', db_column='employe', related_name="pieces",
599 verbose_name=u"employé"
600 )
601 nom = models.CharField(max_length=255)
602 fichier = models.FileField(
603 u"fichier", upload_to=employe_piece_dispatch, storage=storage_prive
604 )
605
606 class Meta:
607 ordering = ['nom']
608 verbose_name = u"Employé pièce"
609 verbose_name_plural = u"Employé pièces"
610
611 def __unicode__(self):
612 return u'%s' % (self.nom)
613
614
615 class EmployeCommentaire(Commentaire):
616 employe = models.ForeignKey(
617 'Employe', db_column='employe', related_name='+'
618 )
619
620 class Meta:
621 verbose_name = u"Employé commentaire"
622 verbose_name_plural = u"Employé commentaires"
623
624
625 LIEN_PARENTE_CHOICES = (
626 ('Conjoint', 'Conjoint'),
627 ('Conjointe', 'Conjointe'),
628 ('Fille', 'Fille'),
629 ('Fils', 'Fils'),
630 )
631
632
633 class AyantDroit(AUFMetadata):
634 """
635 Personne en relation avec un Employe.
636 """
637 # Identification
638 nom = models.CharField(max_length=255)
639 prenom = models.CharField(u"prénom", max_length=255)
640 nom_affichage = models.CharField(
641 u"nom d'affichage", max_length=255, null=True, blank=True
642 )
643 nationalite = models.ForeignKey(
644 ref.Pays, to_field='code', db_column='nationalite',
645 related_name='ayantdroits_nationalite',
646 verbose_name=u"nationalité", null=True, blank=True
647 )
648 date_naissance = models.DateField(
649 u"Date de naissance", help_text=HELP_TEXT_DATE,
650 validators=[validate_date_passee], null=True, blank=True
651 )
652 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
653
654 # Relation
655 employe = models.ForeignKey(
656 'Employe', db_column='employe', related_name='ayantdroits',
657 verbose_name=u"Employé"
658 )
659 lien_parente = models.CharField(
660 u"lien de parenté", max_length=10, choices=LIEN_PARENTE_CHOICES,
661 null=True, blank=True
662 )
663
664 class Meta:
665 ordering = ['nom', ]
666 verbose_name = u"Ayant droit"
667 verbose_name_plural = u"Ayants droit"
668
669 def __unicode__(self):
670 return u'%s %s' % (self.nom.upper(), self.prenom, )
671
672 prefix_implantation = "employe__dossiers__poste__implantation__region"
673
674 def get_regions(self):
675 regions = []
676 for d in self.employe.dossiers.all():
677 regions.append(d.poste.implantation.region)
678 return regions
679
680
681 class AyantDroitCommentaire(Commentaire):
682 ayant_droit = models.ForeignKey(
683 'AyantDroit', db_column='ayant_droit', related_name='+'
684 )
685
686
687 ### DOSSIER
688
689 STATUT_RESIDENCE_CHOICES = (
690 ('local', 'Local'),
691 ('expat', 'Expatrié'),
692 )
693
694 COMPTE_COMPTA_CHOICES = (
695 ('coda', 'CODA'),
696 ('scs', 'SCS'),
697 ('aucun', 'Aucun'),
698 )
699
700
701 class Dossier_(AUFMetadata, DevisableMixin):
702 """
703 Le Dossier regroupe les informations relatives à l'occupation
704 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
705 par un Employe.
706
707 Plusieurs Contrats peuvent être associés au Dossier.
708 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
709 lequel aucun Dossier n'existe est un poste vacant.
710 """
711
712 objects = DossierManager()
713
714 # TODO: OneToOne ??
715 statut = models.ForeignKey('Statut', related_name='+', null=True)
716 organisme_bstg = models.ForeignKey(
717 'OrganismeBstg', db_column='organisme_bstg', related_name='+',
718 verbose_name=u"organisme",
719 help_text=(
720 u"Si détaché (DET) ou mis à disposition (MAD), "
721 u"préciser l'organisme."
722 ), null=True, blank=True
723 )
724
725 # Recrutement
726 remplacement = models.BooleanField(default=False)
727 remplacement_de = models.ForeignKey(
728 'self', related_name='+', help_text=u"Taper le nom de l'employé",
729 null=True, blank=True
730 )
731 statut_residence = models.CharField(
732 u"statut", max_length=10, default='local', null=True,
733 choices=STATUT_RESIDENCE_CHOICES
734 )
735
736 # Rémunération
737 classement = models.ForeignKey(
738 'Classement', db_column='classement', related_name='+', null=True,
739 blank=True
740 )
741 regime_travail = models.DecimalField(
742 u"régime de travail", max_digits=12, null=True, decimal_places=2,
743 default=REGIME_TRAVAIL_DEFAULT, help_text="% du temps complet"
744 )
745 regime_travail_nb_heure_semaine = models.DecimalField(
746 u"nb. heures par semaine", max_digits=12,
747 decimal_places=2, null=True,
748 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
749 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
750 )
751
752 # Occupation du Poste par cet Employe (anciennement "mandat")
753 date_debut = models.DateField(u"date de début d'occupation de poste")
754 date_fin = models.DateField(
755 u"Date de fin d'occupation de poste", null=True, blank=True
756 )
757
758 # Comptes
759 # TODO?
760
761 class Meta:
762 abstract = True
763 ordering = ['employe__nom', ]
764 verbose_name = u"Dossier"
765 verbose_name_plural = "Dossiers"
766
767 def salaire_theorique(self):
768 annee = date.today().year
769 coeff = self.classement.coefficient
770 implantation = self.poste.implantation
771 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
772
773 montant = coeff * point.valeur
774 devise = point.devise
775 return {'montant': montant, 'devise': devise}
776
777 def __unicode__(self):
778 poste = self.poste.nom
779 if self.employe.genre == 'F':
780 poste = self.poste.nom_feminin
781 return u'%s - %s' % (self.employe, poste)
782
783 prefix_implantation = "poste__implantation__region"
784
785 def get_regions(self):
786 return [self.poste.implantation.region]
787
788 def remunerations(self):
789 key = "%s_remunerations" % self._meta.app_label
790 remunerations = getattr(self, key)
791 return remunerations.all().order_by('-date_debut')
792
793 def remunerations_en_cours(self):
794 q = Q(date_fin__exact=None) | Q(date_fin__gt=datetime.date.today())
795 return self.remunerations().all().filter(q).order_by('date_debut')
796
797 def get_salaire(self):
798 try:
799 return [r for r in self.remunerations().order_by('-date_debut')
800 if r.type_id == 1][0]
801 except:
802 return None
803
804 def get_salaire_euros(self):
805 tx = self.taux_devise()
806 return (float)(tx) * (float)(self.salaire)
807
808 def get_remunerations_brutes(self):
809 """
810 1 Salaire de base
811 3 Indemnité de base
812 4 Indemnité d'expatriation
813 5 Indemnité pour frais
814 6 Indemnité de logement
815 7 Indemnité de fonction
816 8 Indemnité de responsabilité
817 9 Indemnité de transport
818 10 Indemnité compensatrice
819 11 Indemnité de subsistance
820 12 Indemnité différentielle
821 13 Prime d'installation
822 14 Billet d'avion
823 15 Déménagement
824 16 Indemnité de départ
825 18 Prime de 13ième mois
826 19 Prime d'intérim
827 """
828 ids = [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
829 return [r for r in self.remunerations_en_cours().all()
830 if r.type_id in ids]
831
832 def get_charges_salariales(self):
833 """
834 20 Charges salariales ?
835 """
836 ids = [20]
837 return [r for r in self.remunerations_en_cours().all()
838 if r.type_id in ids]
839
840 def get_charges_patronales(self):
841 """
842 17 Charges patronales
843 """
844 ids = [17]
845 return [r for r in self.remunerations_en_cours().all()
846 if r.type_id in ids]
847
848 def get_remunerations_tierces(self):
849 """
850 2 Salaire MAD
851 """
852 return [r for r in self.remunerations_en_cours().all()
853 if r.type_id in (2,)]
854
855 # DEVISE LOCALE
856
857 def get_total_local_charges_salariales(self):
858 devise = self.poste.get_devise()
859 total = 0.0
860 for r in self.get_charges_salariales():
861 if r.devise != devise:
862 return None
863 total += float(r.montant)
864 return total
865
866 def get_total_local_charges_patronales(self):
867 devise = self.poste.get_devise()
868 total = 0.0
869 for r in self.get_charges_patronales():
870 if r.devise != devise:
871 return None
872 total += float(r.montant)
873 return total
874
875 def get_local_salaire_brut(self):
876 """
877 somme des rémuérations brutes
878 """
879 devise = self.poste.get_devise()
880 total = 0.0
881 for r in self.get_remunerations_brutes():
882 if r.devise != devise:
883 return None
884 total += float(r.montant)
885 return total
886
887 def get_local_salaire_net(self):
888 """
889 salaire brut - charges salariales
890 """
891 devise = self.poste.get_devise()
892 total_charges = 0.0
893 for r in self.get_charges_salariales():
894 if r.devise != devise:
895 return None
896 total_charges += float(r.montant)
897 return self.get_local_salaire_brut() - total_charges
898
899 def get_local_couts_auf(self):
900 """
901 salaire net + charges patronales
902 """
903 devise = self.poste.get_devise()
904 total_charges = 0.0
905 for r in self.get_charges_patronales():
906 if r.devise != devise:
907 return None
908 total_charges += float(r.montant)
909 return self.get_local_salaire_net() + total_charges
910
911 def get_total_local_remunerations_tierces(self):
912 devise = self.poste.get_devise()
913 total = 0.0
914 for r in self.get_remunerations_tierces():
915 if r.devise != devise:
916 return None
917 total += float(r.montant)
918 return total
919
920 # DEVISE EURO
921
922 def get_total_charges_salariales(self):
923 total = 0.0
924 for r in self.get_charges_salariales():
925 total += r.montant_euros()
926 return total
927
928 def get_total_charges_patronales(self):
929 total = 0.0
930 for r in self.get_charges_patronales():
931 total += r.montant_euros()
932 return total
933
934 def get_salaire_brut(self):
935 """
936 somme des rémuérations brutes
937 """
938 total = 0.0
939 for r in self.get_remunerations_brutes():
940 total += r.montant_euros()
941 return total
942
943 def get_salaire_net(self):
944 """
945 salaire brut - charges salariales
946 """
947 total_charges = 0.0
948 for r in self.get_charges_salariales():
949 total_charges += r.montant_euros()
950 return self.get_salaire_brut() - total_charges
951
952 def get_couts_auf(self):
953 """
954 salaire net + charges patronales
955 """
956 total_charges = 0.0
957 for r in self.get_charges_patronales():
958 total_charges += r.montant_euros()
959 return self.get_salaire_net() + total_charges
960
961 def get_total_remunerations_tierces(self):
962 total = 0.0
963 for r in self.get_remunerations_tierces():
964 total += r.montant_euros()
965 return total
966
967
968 class Dossier(Dossier_):
969 __doc__ = Dossier_.__doc__
970 poste = models.ForeignKey('%s.Poste' % app_context(),
971 db_column='poste',
972 related_name='%(app_label)s_dossiers',
973 help_text=u"Taper le nom du poste ou du type de poste",
974 )
975 employe = models.ForeignKey(
976 'Employe', db_column='employe',
977 help_text=u"Taper le nom de l'employé",
978 related_name='%(app_label)s_dossiers', verbose_name=u"employé")
979 principal = models.BooleanField(
980 u"Principal?", default=True,
981 help_text=(
982 u"Ce dossier est pour le principal poste occupé par l'employé"
983 )
984 )
985
986
987 class DossierPiece_(models.Model):
988 """
989 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
990 Ex.: Lettre de motivation.
991 """
992 dossier = models.ForeignKey(
993 '%s.Dossier' % app_context(),
994 db_column='dossier', related_name='%(app_label)s_dossierpieces'
995 )
996 nom = models.CharField(max_length=255)
997 fichier = models.FileField(
998 upload_to=dossier_piece_dispatch, storage=storage_prive
999 )
1000
1001 class Meta:
1002 abstract = True
1003 ordering = ['nom']
1004
1005 def __unicode__(self):
1006 return u'%s' % (self.nom)
1007
1008
1009 class DossierPiece(DossierPiece_):
1010 pass
1011
1012
1013 class DossierCommentaire_(Commentaire):
1014 dossier = models.ForeignKey(
1015 '%s.Dossier' % app_context(), db_column='dossier', related_name='+'
1016 )
1017
1018 class Meta:
1019 abstract = True
1020
1021
1022 class DossierCommentaire(DossierCommentaire_):
1023 pass
1024
1025
1026 class DossierComparaison_(models.Model, DevisableMixin):
1027 """
1028 Photo d'une comparaison salariale au moment de l'embauche.
1029 """
1030 dossier = models.ForeignKey(
1031 '%s.Dossier' % app_context(), related_name='%(app_label)s_comparaisons'
1032 )
1033 objects = DossierComparaisonManager()
1034
1035 implantation = models.ForeignKey(
1036 ref.Implantation, related_name="+", null=True, blank=True
1037 )
1038 poste = models.CharField(max_length=255, null=True, blank=True)
1039 personne = models.CharField(max_length=255, null=True, blank=True)
1040 montant = models.IntegerField(null=True)
1041 devise = models.ForeignKey(
1042 'Devise', related_name='+', null=True, blank=True
1043 )
1044
1045 class Meta:
1046 abstract = True
1047
1048 def __unicode__(self):
1049 return "%s (%s)" % (self.poste, self.personne)
1050
1051
1052 class DossierComparaison(DossierComparaison_):
1053 pass
1054
1055
1056 ### RÉMUNÉRATION
1057
1058 class RemunerationMixin(AUFMetadata):
1059 dossier = models.ForeignKey(
1060 '%s.Dossier' % app_context(), db_column='dossier',
1061 related_name='%(app_label)s_remunerations'
1062 )
1063
1064 # Identification
1065 type = models.ForeignKey(
1066 'TypeRemuneration', db_column='type', related_name='+',
1067 verbose_name=u"type de rémunération"
1068 )
1069 type_revalorisation = models.ForeignKey(
1070 'TypeRevalorisation', db_column='type_revalorisation',
1071 related_name='+', verbose_name=u"type de revalorisation",
1072 null=True, blank=True
1073 )
1074 montant = models.DecimalField(
1075 null=True, blank=True,
1076 default=0, max_digits=12, decimal_places=2
1077 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1078 devise = models.ForeignKey('Devise', db_column='devise', related_name='+')
1079
1080 # commentaire = precision
1081 commentaire = models.CharField(max_length=255, null=True, blank=True)
1082
1083 # date_debut = anciennement date_effectif
1084 date_debut = models.DateField(u"date de début", null=True, blank=True)
1085 date_fin = models.DateField(u"date de fin", null=True, blank=True)
1086
1087 class Meta:
1088 abstract = True
1089 ordering = ['type__nom', '-date_fin']
1090
1091 def __unicode__(self):
1092 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
1093
1094
1095 class Remuneration_(RemunerationMixin, DevisableMixin):
1096 """
1097 Structure de rémunération (données budgétaires) en situation normale
1098 pour un Dossier. Si un Evenement existe, utiliser la structure de
1099 rémunération EvenementRemuneration de cet événement.
1100 """
1101
1102 def montant_mois(self):
1103 return round(self.montant / 12, 2)
1104
1105 def montant_avec_regime(self):
1106 return round(self.montant * (self.dossier.regime_travail / 100), 2)
1107
1108 def montant_euro_mois(self):
1109 return round(self.montant_euros() / 12, 2)
1110
1111 def __unicode__(self):
1112 try:
1113 devise = self.devise.code
1114 except:
1115 devise = "???"
1116 return "%s %s" % (self.montant, devise)
1117
1118 class Meta:
1119 abstract = True
1120 verbose_name = u"Rémunération"
1121 verbose_name_plural = u"Rémunérations"
1122
1123
1124 class Remuneration(Remuneration_):
1125 pass
1126
1127
1128 ### CONTRATS
1129
1130 class ContratManager(NoDeleteManager):
1131 def get_query_set(self):
1132 return super(ContratManager, self).get_query_set() \
1133 .select_related('dossier', 'dossier__poste')
1134
1135
1136 class Contrat_(AUFMetadata):
1137 """
1138 Document juridique qui encadre la relation de travail d'un Employe
1139 pour un Poste particulier. Pour un Dossier (qui documente cette
1140 relation de travail) plusieurs contrats peuvent être associés.
1141 """
1142 objects = ContratManager()
1143 dossier = models.ForeignKey(
1144 '%s.Dossier' % app_context(), db_column='dossier',
1145 related_name='%(app_label)s_contrats'
1146 )
1147 type_contrat = models.ForeignKey(
1148 'TypeContrat', db_column='type_contrat',
1149 verbose_name=u'type de contrat', related_name='+'
1150 )
1151 date_debut = models.DateField(u"date de début")
1152 date_fin = models.DateField(u"date de fin", null=True, blank=True)
1153 fichier = models.FileField(
1154 upload_to=contrat_dispatch, storage=storage_prive, null=True,
1155 blank=True
1156 )
1157
1158 class Meta:
1159 abstract = True
1160 ordering = ['dossier__employe__nom']
1161 verbose_name = u"Contrat"
1162 verbose_name_plural = u"Contrats"
1163
1164 def __unicode__(self):
1165 return u'%s - %s' % (self.dossier, self.id)
1166
1167
1168 class Contrat(Contrat_):
1169 pass
1170
1171 ### ÉVÉNEMENTS
1172
1173 #class Evenement_(AUFMetadata):
1174 # """
1175 # Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
1176 # d'un Dossier qui vient altérer des informations normales liées à un
1177 # Dossier (ex.: la Remuneration).
1178 #
1179 # Ex.: congé de maternité, maladie...
1180 #
1181 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
1182 # différent et une rémunération en conséquence. On souhaite toutefois
1183 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
1184 # du retour à la normale.
1185 # """
1186 # dossier = models.ForeignKey(
1187 # '%s.Dossier' % app_context(), db_column='dossier',
1188 # related_name='+'
1189 # )
1190 # nom = models.CharField(max_length=255)
1191 # date_debut = models.DateField(verbose_name = u"Date de début")
1192 # date_fin = models.DateField(verbose_name = u"Date de fin",
1193 # null=True, blank=True)
1194 #
1195 # class Meta:
1196 # abstract = True
1197 # ordering = ['nom']
1198 # verbose_name = u"Évènement"
1199 # verbose_name_plural = u"Évènements"
1200 #
1201 # def __unicode__(self):
1202 # return u'%s' % (self.nom)
1203 #
1204 #
1205 #class Evenement(Evenement_):
1206 # __doc__ = Evenement_.__doc__
1207 #
1208 #
1209 #class EvenementRemuneration_(RemunerationMixin):
1210 # """
1211 # Structure de rémunération liée à un Evenement qui remplace
1212 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
1213 # de l'Evenement.
1214 # """
1215 # evenement = models.ForeignKey("Evenement", db_column='evenement',
1216 # related_name='+',
1217 # verbose_name = u"Évènement")
1218 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
1219 # # de l'Evenement associé
1220 #
1221 # class Meta:
1222 # abstract = True
1223 # ordering = ['evenement', 'type__nom', '-date_fin']
1224 # verbose_name = u"Évènement - rémunération"
1225 # verbose_name_plural = u"Évènements - rémunérations"
1226 #
1227 #
1228 #class EvenementRemuneration(EvenementRemuneration_):
1229 # __doc__ = EvenementRemuneration_.__doc__
1230 #
1231 # class Meta:
1232 # abstract = True
1233 #
1234 #
1235 #class EvenementRemuneration(EvenementRemuneration_):
1236 # __doc__ = EvenementRemuneration_.__doc__
1237 # TODO? class ContratPiece(models.Model):
1238
1239
1240 ### RÉFÉRENCES RH
1241
1242 class CategorieEmploi(AUFMetadata):
1243 """
1244 Catégorie utilisée dans la gestion des Postes.
1245 Catégorie supérieure à TypePoste.
1246 """
1247 nom = models.CharField(max_length=255)
1248
1249 class Meta:
1250 ordering = ['nom']
1251 verbose_name = u"catégorie d'emploi"
1252 verbose_name_plural = u"catégories d'emploi"
1253
1254 def __unicode__(self):
1255 return u'%s' % (self.nom)
1256
1257
1258 class TypePoste(AUFMetadata):
1259 """
1260 Catégorie de Poste.
1261 """
1262 nom = models.CharField(max_length=255)
1263 nom_feminin = models.CharField(u"nom féminin", max_length=255)
1264 is_responsable = models.BooleanField(
1265 u"poste de responsabilité", default=False
1266 )
1267 categorie_emploi = models.ForeignKey(
1268 CategorieEmploi, db_column='categorie_emploi', related_name='+',
1269 verbose_name=u"catégorie d'emploi"
1270 )
1271
1272 class Meta:
1273 ordering = ['nom']
1274 verbose_name = u"Type de poste"
1275 verbose_name_plural = u"Types de poste"
1276
1277 def __unicode__(self):
1278 return u'%s' % (self.nom)
1279
1280 TYPE_PAIEMENT_CHOICES = (
1281 (u'Régulier', u'Régulier'),
1282 (u'Ponctuel', u'Ponctuel'),
1283 )
1284
1285 NATURE_REMUNERATION_CHOICES = (
1286 (u'Accessoire', u'Accessoire'),
1287 (u'Charges', u'Charges'),
1288 (u'Indemnité', u'Indemnité'),
1289 (u'RAS', u'Rémunération autre source'),
1290 (u'Traitement', u'Traitement'),
1291 )
1292
1293
1294 class TypeRemuneration(AUFMetadata):
1295 """
1296 Catégorie de Remuneration.
1297 """
1298 objects = TypeRemunerationManager()
1299
1300 nom = models.CharField(max_length=255)
1301 type_paiement = models.CharField(
1302 u"type de paiement", max_length=30, choices=TYPE_PAIEMENT_CHOICES
1303 )
1304 nature_remuneration = models.CharField(
1305 u"nature de la rémunération", max_length=30,
1306 choices=NATURE_REMUNERATION_CHOICES
1307 )
1308 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
1309
1310 class Meta:
1311 ordering = ['nom']
1312 verbose_name = u"Type de rémunération"
1313 verbose_name_plural = u"Types de rémunération"
1314
1315 def __unicode__(self):
1316 if self.archive:
1317 archive = u"(archivé)"
1318 else:
1319 archive = ""
1320 return u'%s %s' % (self.nom, archive)
1321
1322
1323 class TypeRevalorisation(AUFMetadata):
1324 """
1325 Justification du changement de la Remuneration.
1326 (Actuellement utilisé dans aucun traitement informatique.)
1327 """
1328 nom = models.CharField(max_length=255)
1329
1330 class Meta:
1331 ordering = ['nom']
1332 verbose_name = u"Type de revalorisation"
1333 verbose_name_plural = u"Types de revalorisation"
1334
1335 def __unicode__(self):
1336 return u'%s' % (self.nom)
1337
1338
1339 class Service(AUFMetadata):
1340 """
1341 Unité administrative où les Postes sont rattachés.
1342 """
1343 objects = ServiceManager()
1344
1345 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
1346 nom = models.CharField(max_length=255)
1347
1348 class Meta:
1349 ordering = ['nom']
1350 verbose_name = u"Service"
1351 verbose_name_plural = u"Services"
1352
1353 def __unicode__(self):
1354 if self.archive:
1355 archive = u"(archivé)"
1356 else:
1357 archive = ""
1358 return u'%s %s' % (self.nom, archive)
1359
1360
1361 TYPE_ORGANISME_CHOICES = (
1362 ('MAD', 'Mise à disposition'),
1363 ('DET', 'Détachement'),
1364 )
1365
1366
1367 class OrganismeBstg(AUFMetadata):
1368 """
1369 Organisation d'où provient un Employe mis à disposition (MAD) de
1370 ou détaché (DET) à l'AUF à titre gratuit.
1371
1372 (BSTG = bien et service à titre gratuit.)
1373 """
1374 nom = models.CharField(max_length=255)
1375 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
1376 pays = models.ForeignKey(ref.Pays, to_field='code',
1377 db_column='pays',
1378 related_name='organismes_bstg',
1379 null=True, blank=True)
1380
1381 class Meta:
1382 ordering = ['type', 'nom']
1383 verbose_name = u"Organisme BSTG"
1384 verbose_name_plural = u"Organismes BSTG"
1385
1386 def __unicode__(self):
1387 return u'%s (%s)' % (self.nom, self.get_type_display())
1388
1389 prefix_implantation = "pays__region"
1390
1391 def get_regions(self):
1392 return [self.pays.region]
1393
1394
1395 class Statut(AUFMetadata):
1396 """
1397 Statut de l'Employe dans le cadre d'un Dossier particulier.
1398 """
1399 # Identification
1400 code = models.CharField(
1401 max_length=25, unique=True,
1402 help_text=(
1403 u"Saisir un code court mais lisible pour ce statut : "
1404 u"le code est utilisé pour associer les statuts aux autres "
1405 u"données tout en demeurant plus lisible qu'un identifiant "
1406 u"numérique."
1407 )
1408 )
1409 nom = models.CharField(max_length=255)
1410
1411 class Meta:
1412 ordering = ['code']
1413 verbose_name = u"Statut d'employé"
1414 verbose_name_plural = u"Statuts d'employé"
1415
1416 def __unicode__(self):
1417 return u'%s : %s' % (self.code, self.nom)
1418
1419
1420 TYPE_CLASSEMENT_CHOICES = (
1421 ('S', 'S -Soutien'),
1422 ('T', 'T - Technicien'),
1423 ('P', 'P - Professionel'),
1424 ('C', 'C - Cadre'),
1425 ('D', 'D - Direction'),
1426 ('SO', 'SO - Sans objet [expatriés]'),
1427 ('HG', 'HG - Hors grille [direction]'),
1428 )
1429
1430
1431 class ClassementManager(models.Manager):
1432 """
1433 Ordonner les spcéfiquement les classements.
1434 """
1435 def get_query_set(self):
1436 qs = super(self.__class__, self).get_query_set()
1437 qs = qs.extra(select={
1438 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1439 })
1440 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
1441 return qs.all()
1442
1443
1444 class Classement_(AUFMetadata):
1445 """
1446 Éléments de classement de la
1447 "Grille générique de classement hiérarchique".
1448
1449 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1450 classement dans la grille. Le classement donne le coefficient utilisé dans:
1451
1452 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1453 """
1454 objects = ClassementManager()
1455
1456 # Identification
1457 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
1458 echelon = models.IntegerField(u"échelon", blank=True, default=0)
1459 degre = models.IntegerField(u"degré", blank=True, default=0)
1460 coefficient = models.FloatField(u"coefficient", default=0, null=True)
1461
1462 # Méta
1463 # annee # au lieu de date_debut et date_fin
1464 commentaire = models.TextField(null=True, blank=True)
1465
1466 class Meta:
1467 abstract = True
1468 ordering = ['type', 'echelon', 'degre', 'coefficient']
1469 verbose_name = u"Classement"
1470 verbose_name_plural = u"Classements"
1471
1472 def __unicode__(self):
1473 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
1474
1475
1476 class Classement(Classement_):
1477 __doc__ = Classement_.__doc__
1478
1479
1480 class TauxChange_(AUFMetadata):
1481 """
1482 Taux de change de la devise vers l'euro (EUR)
1483 pour chaque année budgétaire.
1484 """
1485 # Identification
1486 devise = models.ForeignKey('Devise', db_column='devise')
1487 annee = models.IntegerField(u"année")
1488 taux = models.FloatField(u"taux vers l'euro")
1489
1490 class Meta:
1491 abstract = True
1492 ordering = ['-annee', 'devise__code']
1493 verbose_name = u"Taux de change"
1494 verbose_name_plural = u"Taux de change"
1495
1496 def __unicode__(self):
1497 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
1498
1499
1500 class TauxChange(TauxChange_):
1501 __doc__ = TauxChange_.__doc__
1502
1503
1504 class ValeurPointManager(NoDeleteManager):
1505
1506 def get_query_set(self):
1507 return super(ValeurPointManager, self).get_query_set() \
1508 .select_related('devise', 'implantation')
1509
1510
1511 class ValeurPoint_(AUFMetadata):
1512 """
1513 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1514 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1515 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1516
1517 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1518 """
1519
1520 actuelles = ValeurPointManager()
1521
1522 valeur = models.FloatField(null=True)
1523 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
1524 implantation = models.ForeignKey(ref.Implantation,
1525 db_column='implantation',
1526 related_name='%(app_label)s_valeur_point')
1527 # Méta
1528 annee = models.IntegerField()
1529
1530 class Meta:
1531 ordering = ['-annee', 'implantation__nom']
1532 abstract = True
1533 verbose_name = u"Valeur du point"
1534 verbose_name_plural = u"Valeurs du point"
1535
1536 def __unicode__(self):
1537 return u'%s %s %s [%s] %s' % (
1538 self.devise.code, self.annee, self.valeur,
1539 self.implantation.nom_court, self.devise.nom
1540 )
1541
1542
1543 class ValeurPoint(ValeurPoint_):
1544 __doc__ = ValeurPoint_.__doc__
1545
1546
1547 class Devise(AUFMetadata):
1548 """
1549 Devise monétaire.
1550 """
1551 objects = DeviseManager()
1552
1553 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
1554 code = models.CharField(max_length=10, unique=True)
1555 nom = models.CharField(max_length=255)
1556
1557 class Meta:
1558 ordering = ['code']
1559 verbose_name = u"Devise"
1560 verbose_name_plural = u"Devises"
1561
1562 def __unicode__(self):
1563 return u'%s - %s' % (self.code, self.nom)
1564
1565
1566 class TypeContrat(AUFMetadata):
1567 """
1568 Type de contrat.
1569 """
1570 nom = models.CharField(max_length=255)
1571 nom_long = models.CharField(max_length=255)
1572
1573 class Meta:
1574 ordering = ['nom']
1575 verbose_name = u"Type de contrat"
1576 verbose_name_plural = u"Types de contrat"
1577
1578 def __unicode__(self):
1579 return u'%s' % (self.nom)
1580
1581
1582 ### AUTRES
1583
1584 class ResponsableImplantationProxy(ref.Implantation):
1585
1586 class Meta:
1587 proxy = True
1588 verbose_name = u"Responsable d'implantation"
1589 verbose_name_plural = u"Responsables d'implantation"
1590
1591
1592 class ResponsableImplantation(models.Model):
1593 """
1594 Le responsable d'une implantation.
1595 Anciennement géré sur le Dossier du responsable.
1596 """
1597 employe = models.ForeignKey(
1598 'Employe', db_column='employe', related_name='+', null=True,
1599 blank=True
1600 )
1601 implantation = models.OneToOneField(
1602 "ResponsableImplantationProxy", db_column='implantation',
1603 related_name='responsable', unique=True
1604 )
1605
1606 def __unicode__(self):
1607 return u'%s : %s' % (self.implantation, self.employe)
1608
1609 class Meta:
1610 ordering = ['implantation__nom']
1611 verbose_name = "Responsable d'implantation"
1612 verbose_name_plural = "Responsables d'implantation"