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