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