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