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