ergo UI : uniformiastion colonnes des changelists
[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
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
444 ### EMPLOYÉ/PERSONNE
445
446 class Employe(AUFMetadata):
447 """
448 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
449 Dossiers qu'il occupe ou a occupé de Postes.
450
451 Cette classe aurait pu avantageusement s'appeler Personne car la notion
452 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
453 """
454
455 objects = EmployeManager()
456
457 # Identification
458 nom = models.CharField(max_length=255)
459 prenom = models.CharField(u"prénom", max_length=255)
460 nom_affichage = models.CharField(
461 u"nom d'affichage", max_length=255, null=True, blank=True
462 )
463 nationalite = models.ForeignKey(
464 ref.Pays, to_field='code', db_column='nationalite',
465 related_name='employes_nationalite', verbose_name=u"nationalité",
466 blank=True, null=True
467 )
468 date_naissance = models.DateField(
469 u"date de naissance", help_text=HELP_TEXT_DATE,
470 validators=[validate_date_passee], null=True, blank=True
471 )
472 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
473
474 # Infos personnelles
475 situation_famille = models.CharField(
476 u"situation familiale", max_length=1, choices=SITUATION_CHOICES,
477 null=True, blank=True
478 )
479 date_entree = models.DateField(
480 u"date d'entrée à l'AUF", help_text=HELP_TEXT_DATE, null=True,
481 blank=True
482 )
483
484 # Coordonnées
485 tel_domicile = models.CharField(
486 u"tél. domicile", max_length=255, null=True, blank=True
487 )
488 tel_cellulaire = models.CharField(
489 u"tél. cellulaire", max_length=255, null=True, blank=True
490 )
491 adresse = models.CharField(max_length=255, null=True, blank=True)
492 ville = models.CharField(max_length=255, null=True, blank=True)
493 province = models.CharField(max_length=255, null=True, blank=True)
494 code_postal = models.CharField(max_length=255, null=True, blank=True)
495 pays = models.ForeignKey(
496 ref.Pays, to_field='code', db_column='pays',
497 related_name='employes', null=True, blank=True
498 )
499 courriel_perso = models.EmailField(
500 u'adresse courriel personnelle', blank=True
501 )
502
503 # meta dématérialisation : pour permettre le filtrage
504 nb_postes = models.IntegerField(u"nombre de postes", null=True, blank=True)
505
506 class Meta:
507 ordering = ['nom', 'prenom']
508 verbose_name = u"Employé"
509 verbose_name_plural = u"Employés"
510
511 def __unicode__(self):
512 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
513
514 def civilite(self):
515 civilite = u''
516 if self.genre.upper() == u'M':
517 civilite = u'M.'
518 elif self.genre.upper() == u'F':
519 civilite = u'Mme'
520 return civilite
521
522 def url_photo(self):
523 """
524 Retourne l'URL du service retournant la photo de l'Employe.
525 Équivalent reverse url 'rh_photo' avec id en param.
526 """
527 from django.core.urlresolvers import reverse
528 return reverse('rh_photo', kwargs={'id': self.id})
529
530 def dossiers_passes(self):
531 params = {KEY_STATUT: STATUT_INACTIF, }
532 search = RechercheTemporelle(params, self.__class__)
533 search.purge_params(params)
534 q = search.get_q_temporel(self.rh_dossiers)
535 return self.rh_dossiers.filter(q)
536
537 def dossiers_futurs(self):
538 params = {KEY_STATUT: STATUT_FUTUR, }
539 search = RechercheTemporelle(params, self.__class__)
540 search.purge_params(params)
541 q = search.get_q_temporel(self.rh_dossiers)
542 return self.rh_dossiers.filter(q)
543
544 def dossiers_encours(self):
545 params = {KEY_STATUT: STATUT_ACTIF, }
546 search = RechercheTemporelle(params, self.__class__)
547 search.purge_params(params)
548 q = search.get_q_temporel(self.rh_dossiers)
549 return self.rh_dossiers.filter(q)
550
551 def dossier_principal(self):
552 """Retourne le dossier principal
553 (ou le plus ancien si il y en a plusieurs)
554 """
555 try:
556 dossier = self.rh_dossiers.filter(principal=True).order_by('date_debut')[0]
557 except IndexError, Dossier.DoesNotExist:
558 dossier = None
559 return dossier
560
561 def postes_encours(self):
562 postes_encours = set()
563 for d in self.dossiers_encours():
564 postes_encours.add(d.poste)
565 return postes_encours
566
567 def poste_principal(self):
568 """
569 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
570 Idée derrière :
571 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
572 """
573 # DEPRECATED : on a maintenant Dossier.principal
574 poste = Poste.objects.none()
575 try:
576 poste = self.dossiers_encours().order_by('date_debut')[0].poste
577 except:
578 pass
579 return poste
580
581 prefix_implantation = "rh_dossiers__poste__implantation__region"
582
583 def get_regions(self):
584 regions = []
585 for d in self.dossiers.all():
586 regions.append(d.poste.implantation.region)
587 return regions
588
589
590 class EmployePiece(models.Model):
591 """
592 Documents relatifs à un employé.
593 Ex.: CV...
594 """
595 employe = models.ForeignKey(
596 'Employe', db_column='employe', related_name="pieces",
597 verbose_name=u"employé"
598 )
599 nom = models.CharField(max_length=255)
600 fichier = models.FileField(
601 u"fichier", upload_to=employe_piece_dispatch, storage=storage_prive
602 )
603
604 class Meta:
605 ordering = ['nom']
606 verbose_name = u"Employé pièce"
607 verbose_name_plural = u"Employé pièces"
608
609 def __unicode__(self):
610 return u'%s' % (self.nom)
611
612
613 class EmployeCommentaire(Commentaire):
614 employe = models.ForeignKey(
615 'Employe', db_column='employe', related_name='+'
616 )
617
618 class Meta:
619 verbose_name = u"Employé commentaire"
620 verbose_name_plural = u"Employé commentaires"
621
622
623 LIEN_PARENTE_CHOICES = (
624 ('Conjoint', 'Conjoint'),
625 ('Conjointe', 'Conjointe'),
626 ('Fille', 'Fille'),
627 ('Fils', 'Fils'),
628 )
629
630
631 class AyantDroit(AUFMetadata):
632 """
633 Personne en relation avec un Employe.
634 """
635 # Identification
636 nom = models.CharField(max_length=255)
637 prenom = models.CharField(u"prénom", max_length=255)
638 nom_affichage = models.CharField(
639 u"nom d'affichage", max_length=255, null=True, blank=True
640 )
641 nationalite = models.ForeignKey(
642 ref.Pays, to_field='code', db_column='nationalite',
643 related_name='ayantdroits_nationalite',
644 verbose_name=u"nationalité", null=True, blank=True
645 )
646 date_naissance = models.DateField(
647 u"Date de naissance", help_text=HELP_TEXT_DATE,
648 validators=[validate_date_passee], null=True, blank=True
649 )
650 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
651
652 # Relation
653 employe = models.ForeignKey(
654 'Employe', db_column='employe', related_name='ayantdroits',
655 verbose_name=u"Employé"
656 )
657 lien_parente = models.CharField(
658 u"lien de parenté", max_length=10, choices=LIEN_PARENTE_CHOICES,
659 null=True, blank=True
660 )
661
662 class Meta:
663 ordering = ['nom', ]
664 verbose_name = u"Ayant droit"
665 verbose_name_plural = u"Ayants droit"
666
667 def __unicode__(self):
668 return u'%s %s' % (self.nom.upper(), self.prenom, )
669
670 prefix_implantation = "employe__dossiers__poste__implantation__region"
671
672 def get_regions(self):
673 regions = []
674 for d in self.employe.dossiers.all():
675 regions.append(d.poste.implantation.region)
676 return regions
677
678
679 class AyantDroitCommentaire(Commentaire):
680 ayant_droit = models.ForeignKey(
681 'AyantDroit', db_column='ayant_droit', related_name='+'
682 )
683
684
685 ### DOSSIER
686
687 STATUT_RESIDENCE_CHOICES = (
688 ('local', 'Local'),
689 ('expat', 'Expatrié'),
690 )
691
692 COMPTE_COMPTA_CHOICES = (
693 ('coda', 'CODA'),
694 ('scs', 'SCS'),
695 ('aucun', 'Aucun'),
696 )
697
698
699 class Dossier_(AUFMetadata, DevisableMixin):
700 """
701 Le Dossier regroupe les informations relatives à l'occupation
702 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
703 par un Employe.
704
705 Plusieurs Contrats peuvent être associés au Dossier.
706 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
707 lequel aucun Dossier n'existe est un poste vacant.
708 """
709
710 objects = DossierManager()
711
712 # TODO: OneToOne ??
713 statut = models.ForeignKey('Statut', related_name='+', null=True)
714 organisme_bstg = models.ForeignKey(
715 'OrganismeBstg', db_column='organisme_bstg', related_name='+',
716 verbose_name=u"organisme",
717 help_text=(
718 u"Si détaché (DET) ou mis à disposition (MAD), "
719 u"préciser l'organisme."
720 ), null=True, blank=True
721 )
722
723 # Recrutement
724 remplacement = models.BooleanField(default=False)
725 remplacement_de = models.ForeignKey(
726 'self', related_name='+', help_text=u"Taper le nom de l'employé",
727 null=True, blank=True
728 )
729 statut_residence = models.CharField(
730 u"statut", max_length=10, default='local', null=True,
731 choices=STATUT_RESIDENCE_CHOICES
732 )
733
734 # Rémunération
735 classement = models.ForeignKey(
736 'Classement', db_column='classement', related_name='+', null=True,
737 blank=True
738 )
739 regime_travail = models.DecimalField(
740 u"régime de travail", max_digits=12, null=True, decimal_places=2,
741 default=REGIME_TRAVAIL_DEFAULT, help_text="% du temps complet"
742 )
743 regime_travail_nb_heure_semaine = models.DecimalField(
744 u"nb. heures par semaine", max_digits=12,
745 decimal_places=2, null=True,
746 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
747 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
748 )
749
750 # Occupation du Poste par cet Employe (anciennement "mandat")
751 date_debut = models.DateField(u"date de début d'occupation de poste")
752 date_fin = models.DateField(
753 u"Date de fin d'occupation de poste", null=True, blank=True
754 )
755
756 # Comptes
757 # TODO?
758
759 class Meta:
760 abstract = True
761 ordering = ['employe__nom', ]
762 verbose_name = u"Dossier"
763 verbose_name_plural = "Dossiers"
764
765 def salaire_theorique(self):
766 annee = date.today().year
767 coeff = self.classement.coefficient
768 implantation = self.poste.implantation
769 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
770
771 montant = coeff * point.valeur
772 devise = point.devise
773 return {'montant': montant, 'devise': devise}
774
775 def __unicode__(self):
776 poste = self.poste.nom
777 if self.employe.genre == 'F':
778 poste = self.poste.nom_feminin
779 return u'%s - %s' % (self.employe, poste)
780
781 prefix_implantation = "poste__implantation__region"
782
783 def get_regions(self):
784 return [self.poste.implantation.region]
785
786 def remunerations(self):
787 key = "%s_remunerations" % self._meta.app_label
788 remunerations = getattr(self, key)
789 return remunerations.all().order_by('-date_debut')
790
791 def remunerations_en_cours(self):
792 q = Q(date_fin__exact=None) | Q(date_fin__gt=datetime.date.today())
793 return self.remunerations().all().filter(q).order_by('date_debut')
794
795 def get_salaire(self):
796 try:
797 return [r for r in self.remunerations().order_by('-date_debut')
798 if r.type_id == 1][0]
799 except:
800 return None
801
802 def get_salaire_euros(self):
803 tx = self.taux_devise()
804 return (float)(tx) * (float)(self.salaire)
805
806 def get_remunerations_brutes(self):
807 """
808 1 Salaire de base
809 3 Indemnité de base
810 4 Indemnité d'expatriation
811 5 Indemnité pour frais
812 6 Indemnité de logement
813 7 Indemnité de fonction
814 8 Indemnité de responsabilité
815 9 Indemnité de transport
816 10 Indemnité compensatrice
817 11 Indemnité de subsistance
818 12 Indemnité différentielle
819 13 Prime d'installation
820 14 Billet d'avion
821 15 Déménagement
822 16 Indemnité de départ
823 18 Prime de 13ième mois
824 19 Prime d'intérim
825 """
826 ids = [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
827 return [r for r in self.remunerations_en_cours().all()
828 if r.type_id in ids]
829
830 def get_charges_salariales(self):
831 """
832 20 Charges salariales ?
833 """
834 ids = [20]
835 return [r for r in self.remunerations_en_cours().all()
836 if r.type_id in ids]
837
838 def get_charges_patronales(self):
839 """
840 17 Charges patronales
841 """
842 ids = [17]
843 return [r for r in self.remunerations_en_cours().all()
844 if r.type_id in ids]
845
846 def get_remunerations_tierces(self):
847 """
848 2 Salaire MAD
849 """
850 return [r for r in self.remunerations_en_cours().all()
851 if r.type_id in (2,)]
852
853 # DEVISE LOCALE
854
855 def get_total_local_charges_salariales(self):
856 devise = self.poste.get_devise()
857 total = 0.0
858 for r in self.get_charges_salariales():
859 if r.devise != devise:
860 return None
861 total += float(r.montant)
862 return total
863
864 def get_total_local_charges_patronales(self):
865 devise = self.poste.get_devise()
866 total = 0.0
867 for r in self.get_charges_patronales():
868 if r.devise != devise:
869 return None
870 total += float(r.montant)
871 return total
872
873 def get_local_salaire_brut(self):
874 """
875 somme des rémuérations brutes
876 """
877 devise = self.poste.get_devise()
878 total = 0.0
879 for r in self.get_remunerations_brutes():
880 if r.devise != devise:
881 return None
882 total += float(r.montant)
883 return total
884
885 def get_local_salaire_net(self):
886 """
887 salaire brut - charges salariales
888 """
889 devise = self.poste.get_devise()
890 total_charges = 0.0
891 for r in self.get_charges_salariales():
892 if r.devise != devise:
893 return None
894 total_charges += float(r.montant)
895 return self.get_local_salaire_brut() - total_charges
896
897 def get_local_couts_auf(self):
898 """
899 salaire net + charges patronales
900 """
901 devise = self.poste.get_devise()
902 total_charges = 0.0
903 for r in self.get_charges_patronales():
904 if r.devise != devise:
905 return None
906 total_charges += float(r.montant)
907 return self.get_local_salaire_net() + total_charges
908
909 def get_total_local_remunerations_tierces(self):
910 devise = self.poste.get_devise()
911 total = 0.0
912 for r in self.get_remunerations_tierces():
913 if r.devise != devise:
914 return None
915 total += float(r.montant)
916 return total
917
918 # DEVISE EURO
919
920 def get_total_charges_salariales(self):
921 total = 0.0
922 for r in self.get_charges_salariales():
923 total += r.montant_euros()
924 return total
925
926 def get_total_charges_patronales(self):
927 total = 0.0
928 for r in self.get_charges_patronales():
929 total += r.montant_euros()
930 return total
931
932 def get_salaire_brut(self):
933 """
934 somme des rémuérations brutes
935 """
936 total = 0.0
937 for r in self.get_remunerations_brutes():
938 total += r.montant_euros()
939 return total
940
941 def get_salaire_net(self):
942 """
943 salaire brut - charges salariales
944 """
945 total_charges = 0.0
946 for r in self.get_charges_salariales():
947 total_charges += r.montant_euros()
948 return self.get_salaire_brut() - total_charges
949
950 def get_couts_auf(self):
951 """
952 salaire net + charges patronales
953 """
954 total_charges = 0.0
955 for r in self.get_charges_patronales():
956 total_charges += r.montant_euros()
957 return self.get_salaire_net() + total_charges
958
959 def get_total_remunerations_tierces(self):
960 total = 0.0
961 for r in self.get_remunerations_tierces():
962 total += r.montant_euros()
963 return total
964
965 def premier_contrat(self):
966 """contrat avec plus petite date de début"""
967 try:
968 contrat = self.rh_contrats.exclude(date_debut=None).order_by('date_debut')[0]
969 except IndexError, Contrat.DoesNotExist:
970 contrat = None
971 return contrat
972
973 def dernier_contrat(self):
974 """contrat avec plus grande date de fin"""
975 try:
976 contrat = self.rh_contrats.exclude(date_debut=None).order_by('-date_debut')[0]
977 except IndexError, Contrat.DoesNotExist:
978 contrat = None
979 return contrat
980
981 def actif(self):
982 today = date.today()
983 return (self.date_debut is None or self.date_debut <= today) \
984 and (self.date_fin is None or self.date_fin >= today) \
985 and not (self.date_fin is None and self.date_debut is None)
986
987
988 class Dossier(Dossier_):
989 __doc__ = Dossier_.__doc__
990 poste = models.ForeignKey(
991 Poste, db_column='poste', related_name='rh_dossiers',
992 help_text=u"Taper le nom du poste ou du type de poste",
993 )
994 employe = models.ForeignKey(
995 'Employe', db_column='employe',
996 help_text=u"Taper le nom de l'employé",
997 related_name='rh_dossiers', verbose_name=u"employé"
998 )
999 principal = models.BooleanField(
1000 u"dossier principal", default=True,
1001 help_text=(
1002 u"Ce dossier est pour le principal poste occupé par l'employé"
1003 )
1004 )
1005
1006
1007 class DossierPiece_(models.Model):
1008 """
1009 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1010 Ex.: Lettre de motivation.
1011 """
1012 nom = models.CharField(max_length=255)
1013 fichier = models.FileField(
1014 upload_to=dossier_piece_dispatch, storage=storage_prive
1015 )
1016
1017 class Meta:
1018 abstract = True
1019 ordering = ['nom']
1020
1021 def __unicode__(self):
1022 return u'%s' % (self.nom)
1023
1024
1025 class DossierPiece(DossierPiece_):
1026 dossier = models.ForeignKey(
1027 Dossier, db_column='dossier', related_name='rh_dossierpieces'
1028 )
1029
1030
1031
1032 class DossierCommentaire(Commentaire):
1033 dossier = models.ForeignKey(
1034 Dossier, db_column='dossier', related_name='commentaires'
1035 )
1036
1037
1038 class DossierComparaison_(models.Model, DevisableMixin):
1039 """
1040 Photo d'une comparaison salariale au moment de l'embauche.
1041 """
1042 objects = DossierComparaisonManager()
1043
1044 implantation = models.ForeignKey(
1045 ref.Implantation, related_name="+", null=True, blank=True
1046 )
1047 poste = models.CharField(max_length=255, null=True, blank=True)
1048 personne = models.CharField(max_length=255, null=True, blank=True)
1049 montant = models.IntegerField(null=True)
1050 devise = models.ForeignKey(
1051 'Devise', related_name='+', null=True, blank=True
1052 )
1053
1054 class Meta:
1055 abstract = True
1056
1057 def __unicode__(self):
1058 return "%s (%s)" % (self.poste, self.personne)
1059
1060
1061 class DossierComparaison(DossierComparaison_):
1062 dossier = models.ForeignKey(
1063 Dossier, related_name='rh_comparaisons'
1064 )
1065
1066
1067 ### RÉMUNÉRATION
1068
1069 class RemunerationMixin(AUFMetadata):
1070
1071 # Identification
1072 type = models.ForeignKey(
1073 'TypeRemuneration', db_column='type', related_name='+',
1074 verbose_name=u"type de rémunération"
1075 )
1076 type_revalorisation = models.ForeignKey(
1077 'TypeRevalorisation', db_column='type_revalorisation',
1078 related_name='+', verbose_name=u"type de revalorisation",
1079 null=True, blank=True
1080 )
1081 montant = models.DecimalField(
1082 null=True, blank=True,
1083 default=0, max_digits=12, decimal_places=2
1084 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1085 devise = models.ForeignKey('Devise', db_column='devise', related_name='+')
1086
1087 # commentaire = precision
1088 commentaire = models.CharField(max_length=255, null=True, blank=True)
1089
1090 # date_debut = anciennement date_effectif
1091 date_debut = models.DateField(u"date de début", null=True, blank=True)
1092 date_fin = models.DateField(u"date de fin", null=True, blank=True)
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"