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