PEP8
[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 """
552 Retourne le dossier principal (ou le plus ancien si il y en a
553 plusieurs)
554 """
555 try:
556 dossier = self.rh_dossiers \
557 .filter(principal=True).order_by('date_debut')[0]
558 except IndexError, Dossier.DoesNotExist:
559 dossier = None
560 return dossier
561
562 def postes_encours(self):
563 postes_encours = set()
564 for d in self.dossiers_encours():
565 postes_encours.add(d.poste)
566 return postes_encours
567
568 def poste_principal(self):
569 """
570 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
571 Idée derrière :
572 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
573 """
574 # DEPRECATED : on a maintenant Dossier.principal
575 poste = Poste.objects.none()
576 try:
577 poste = self.dossiers_encours().order_by('date_debut')[0].poste
578 except:
579 pass
580 return poste
581
582 prefix_implantation = "rh_dossiers__poste__implantation__region"
583
584 def get_regions(self):
585 regions = []
586 for d in self.dossiers.all():
587 regions.append(d.poste.implantation.region)
588 return regions
589
590
591 class EmployePiece(models.Model):
592 """
593 Documents relatifs à un employé.
594 Ex.: CV...
595 """
596 employe = models.ForeignKey(
597 'Employe', db_column='employe', related_name="pieces",
598 verbose_name=u"employé"
599 )
600 nom = models.CharField(max_length=255)
601 fichier = models.FileField(
602 u"fichier", upload_to=employe_piece_dispatch, storage=storage_prive
603 )
604
605 class Meta:
606 ordering = ['nom']
607 verbose_name = u"Employé pièce"
608 verbose_name_plural = u"Employé pièces"
609
610 def __unicode__(self):
611 return u'%s' % (self.nom)
612
613
614 class EmployeCommentaire(Commentaire):
615 employe = models.ForeignKey(
616 'Employe', db_column='employe', related_name='+'
617 )
618
619 class Meta:
620 verbose_name = u"Employé commentaire"
621 verbose_name_plural = u"Employé commentaires"
622
623
624 LIEN_PARENTE_CHOICES = (
625 ('Conjoint', 'Conjoint'),
626 ('Conjointe', 'Conjointe'),
627 ('Fille', 'Fille'),
628 ('Fils', 'Fils'),
629 )
630
631
632 class AyantDroit(AUFMetadata):
633 """
634 Personne en relation avec un Employe.
635 """
636 # Identification
637 nom = models.CharField(max_length=255)
638 prenom = models.CharField(u"prénom", max_length=255)
639 nom_affichage = models.CharField(
640 u"nom d'affichage", max_length=255, null=True, blank=True
641 )
642 nationalite = models.ForeignKey(
643 ref.Pays, to_field='code', db_column='nationalite',
644 related_name='ayantdroits_nationalite',
645 verbose_name=u"nationalité", null=True, blank=True
646 )
647 date_naissance = models.DateField(
648 u"Date de naissance", help_text=HELP_TEXT_DATE,
649 validators=[validate_date_passee], null=True, blank=True
650 )
651 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
652
653 # Relation
654 employe = models.ForeignKey(
655 'Employe', db_column='employe', related_name='ayantdroits',
656 verbose_name=u"Employé"
657 )
658 lien_parente = models.CharField(
659 u"lien de parenté", max_length=10, choices=LIEN_PARENTE_CHOICES,
660 null=True, blank=True
661 )
662
663 class Meta:
664 ordering = ['nom', ]
665 verbose_name = u"Ayant droit"
666 verbose_name_plural = u"Ayants droit"
667
668 def __unicode__(self):
669 return u'%s %s' % (self.nom.upper(), self.prenom, )
670
671 prefix_implantation = "employe__dossiers__poste__implantation__region"
672
673 def get_regions(self):
674 regions = []
675 for d in self.employe.dossiers.all():
676 regions.append(d.poste.implantation.region)
677 return regions
678
679
680 class AyantDroitCommentaire(Commentaire):
681 ayant_droit = models.ForeignKey(
682 'AyantDroit', db_column='ayant_droit', related_name='+'
683 )
684
685
686 ### DOSSIER
687
688 STATUT_RESIDENCE_CHOICES = (
689 ('local', 'Local'),
690 ('expat', 'Expatrié'),
691 )
692
693 COMPTE_COMPTA_CHOICES = (
694 ('coda', 'CODA'),
695 ('scs', 'SCS'),
696 ('aucun', 'Aucun'),
697 )
698
699
700 class Dossier_(AUFMetadata, DevisableMixin):
701 """
702 Le Dossier regroupe les informations relatives à l'occupation
703 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
704 par un Employe.
705
706 Plusieurs Contrats peuvent être associés au Dossier.
707 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
708 lequel aucun Dossier n'existe est un poste vacant.
709 """
710
711 objects = DossierManager()
712
713 # TODO: OneToOne ??
714 statut = models.ForeignKey('Statut', related_name='+', null=True)
715 organisme_bstg = models.ForeignKey(
716 'OrganismeBstg', db_column='organisme_bstg', related_name='+',
717 verbose_name=u"organisme",
718 help_text=(
719 u"Si détaché (DET) ou mis à disposition (MAD), "
720 u"préciser l'organisme."
721 ), null=True, blank=True
722 )
723
724 # Recrutement
725 remplacement = models.BooleanField(default=False)
726 remplacement_de = models.ForeignKey(
727 'self', related_name='+', help_text=u"Taper le nom de l'employé",
728 null=True, blank=True
729 )
730 statut_residence = models.CharField(
731 u"statut", max_length=10, default='local', null=True,
732 choices=STATUT_RESIDENCE_CHOICES
733 )
734
735 # Rémunération
736 classement = models.ForeignKey(
737 'Classement', db_column='classement', related_name='+', null=True,
738 blank=True
739 )
740 regime_travail = models.DecimalField(
741 u"régime de travail", max_digits=12, null=True, decimal_places=2,
742 default=REGIME_TRAVAIL_DEFAULT, help_text="% du temps complet"
743 )
744 regime_travail_nb_heure_semaine = models.DecimalField(
745 u"nb. heures par semaine", max_digits=12,
746 decimal_places=2, null=True,
747 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
748 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
749 )
750
751 # Occupation du Poste par cet Employe (anciennement "mandat")
752 date_debut = models.DateField(u"date de début d'occupation de poste")
753 date_fin = models.DateField(
754 u"Date de fin d'occupation de poste", null=True, blank=True
755 )
756
757 # Comptes
758 # TODO?
759
760 class Meta:
761 abstract = True
762 ordering = ['employe__nom', ]
763 verbose_name = u"Dossier"
764 verbose_name_plural = "Dossiers"
765
766 def salaire_theorique(self):
767 annee = date.today().year
768 coeff = self.classement.coefficient
769 implantation = self.poste.implantation
770 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
771
772 montant = coeff * point.valeur
773 devise = point.devise
774 return {'montant': montant, 'devise': devise}
775
776 def __unicode__(self):
777 poste = self.poste.nom
778 if self.employe.genre == 'F':
779 poste = self.poste.nom_feminin
780 return u'%s - %s' % (self.employe, poste)
781
782 prefix_implantation = "poste__implantation__region"
783
784 def get_regions(self):
785 return [self.poste.implantation.region]
786
787 def remunerations(self):
788 key = "%s_remunerations" % self._meta.app_label
789 remunerations = getattr(self, key)
790 return remunerations.all().order_by('-date_debut')
791
792 def remunerations_en_cours(self):
793 q = Q(date_fin__exact=None) | Q(date_fin__gt=datetime.date.today())
794 return self.remunerations().all().filter(q).order_by('date_debut')
795
796 def get_salaire(self):
797 try:
798 return [r for r in self.remunerations().order_by('-date_debut')
799 if r.type_id == 1][0]
800 except:
801 return None
802
803 def get_salaire_euros(self):
804 tx = self.taux_devise()
805 return (float)(tx) * (float)(self.salaire)
806
807 def get_remunerations_brutes(self):
808 """
809 1 Salaire de base
810 3 Indemnité de base
811 4 Indemnité d'expatriation
812 5 Indemnité pour frais
813 6 Indemnité de logement
814 7 Indemnité de fonction
815 8 Indemnité de responsabilité
816 9 Indemnité de transport
817 10 Indemnité compensatrice
818 11 Indemnité de subsistance
819 12 Indemnité différentielle
820 13 Prime d'installation
821 14 Billet d'avion
822 15 Déménagement
823 16 Indemnité de départ
824 18 Prime de 13ième mois
825 19 Prime d'intérim
826 """
827 ids = [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
828 return [r for r in self.remunerations_en_cours().all()
829 if r.type_id in ids]
830
831 def get_charges_salariales(self):
832 """
833 20 Charges salariales ?
834 """
835 ids = [20]
836 return [r for r in self.remunerations_en_cours().all()
837 if r.type_id in ids]
838
839 def get_charges_patronales(self):
840 """
841 17 Charges patronales
842 """
843 ids = [17]
844 return [r for r in self.remunerations_en_cours().all()
845 if r.type_id in ids]
846
847 def get_remunerations_tierces(self):
848 """
849 2 Salaire MAD
850 """
851 return [r for r in self.remunerations_en_cours().all()
852 if r.type_id in (2,)]
853
854 # DEVISE LOCALE
855
856 def get_total_local_charges_salariales(self):
857 devise = self.poste.get_devise()
858 total = 0.0
859 for r in self.get_charges_salariales():
860 if r.devise != devise:
861 return None
862 total += float(r.montant)
863 return total
864
865 def get_total_local_charges_patronales(self):
866 devise = self.poste.get_devise()
867 total = 0.0
868 for r in self.get_charges_patronales():
869 if r.devise != devise:
870 return None
871 total += float(r.montant)
872 return total
873
874 def get_local_salaire_brut(self):
875 """
876 somme des rémuérations brutes
877 """
878 devise = self.poste.get_devise()
879 total = 0.0
880 for r in self.get_remunerations_brutes():
881 if r.devise != devise:
882 return None
883 total += float(r.montant)
884 return total
885
886 def get_local_salaire_net(self):
887 """
888 salaire brut - charges salariales
889 """
890 devise = self.poste.get_devise()
891 total_charges = 0.0
892 for r in self.get_charges_salariales():
893 if r.devise != devise:
894 return None
895 total_charges += float(r.montant)
896 return self.get_local_salaire_brut() - total_charges
897
898 def get_local_couts_auf(self):
899 """
900 salaire net + charges patronales
901 """
902 devise = self.poste.get_devise()
903 total_charges = 0.0
904 for r in self.get_charges_patronales():
905 if r.devise != devise:
906 return None
907 total_charges += float(r.montant)
908 return self.get_local_salaire_net() + total_charges
909
910 def get_total_local_remunerations_tierces(self):
911 devise = self.poste.get_devise()
912 total = 0.0
913 for r in self.get_remunerations_tierces():
914 if r.devise != devise:
915 return None
916 total += float(r.montant)
917 return total
918
919 # DEVISE EURO
920
921 def get_total_charges_salariales(self):
922 total = 0.0
923 for r in self.get_charges_salariales():
924 total += r.montant_euros()
925 return total
926
927 def get_total_charges_patronales(self):
928 total = 0.0
929 for r in self.get_charges_patronales():
930 total += r.montant_euros()
931 return total
932
933 def get_salaire_brut(self):
934 """
935 somme des rémuérations brutes
936 """
937 total = 0.0
938 for r in self.get_remunerations_brutes():
939 total += r.montant_euros()
940 return total
941
942 def get_salaire_net(self):
943 """
944 salaire brut - charges salariales
945 """
946 total_charges = 0.0
947 for r in self.get_charges_salariales():
948 total_charges += r.montant_euros()
949 return self.get_salaire_brut() - total_charges
950
951 def get_couts_auf(self):
952 """
953 salaire net + charges patronales
954 """
955 total_charges = 0.0
956 for r in self.get_charges_patronales():
957 total_charges += r.montant_euros()
958 return self.get_salaire_net() + total_charges
959
960 def get_total_remunerations_tierces(self):
961 total = 0.0
962 for r in self.get_remunerations_tierces():
963 total += r.montant_euros()
964 return total
965
966 def premier_contrat(self):
967 """contrat avec plus petite date de début"""
968 try:
969 contrat = self.rh_contrats.exclude(date_debut=None) \
970 .order_by('date_debut')[0]
971 except IndexError, Contrat.DoesNotExist:
972 contrat = None
973 return contrat
974
975 def dernier_contrat(self):
976 """contrat avec plus grande date de fin"""
977 try:
978 contrat = self.rh_contrats.exclude(date_debut=None) \
979 .order_by('-date_debut')[0]
980 except IndexError, Contrat.DoesNotExist:
981 contrat = None
982 return contrat
983
984 def actif(self):
985 today = date.today()
986 return (self.date_debut is None or self.date_debut <= today) \
987 and (self.date_fin is None or self.date_fin >= today) \
988 and not (self.date_fin is None and self.date_debut is None)
989
990
991 class Dossier(Dossier_):
992 __doc__ = Dossier_.__doc__
993 poste = models.ForeignKey(
994 Poste, db_column='poste', related_name='rh_dossiers',
995 help_text=u"Taper le nom du poste ou du type de poste",
996 )
997 employe = models.ForeignKey(
998 'Employe', db_column='employe',
999 help_text=u"Taper le nom de l'employé",
1000 related_name='rh_dossiers', verbose_name=u"employé"
1001 )
1002 principal = models.BooleanField(
1003 u"dossier principal", default=True,
1004 help_text=(
1005 u"Ce dossier est pour le principal poste occupé par l'employé"
1006 )
1007 )
1008
1009
1010 class DossierPiece_(models.Model):
1011 """
1012 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1013 Ex.: Lettre de motivation.
1014 """
1015 nom = models.CharField(max_length=255)
1016 fichier = models.FileField(
1017 upload_to=dossier_piece_dispatch, storage=storage_prive
1018 )
1019
1020 class Meta:
1021 abstract = True
1022 ordering = ['nom']
1023
1024 def __unicode__(self):
1025 return u'%s' % (self.nom)
1026
1027
1028 class DossierPiece(DossierPiece_):
1029 dossier = models.ForeignKey(
1030 Dossier, db_column='dossier', related_name='rh_dossierpieces'
1031 )
1032
1033
1034 class DossierCommentaire(Commentaire):
1035 dossier = models.ForeignKey(
1036 Dossier, db_column='dossier', related_name='commentaires'
1037 )
1038
1039
1040 class DossierComparaison_(models.Model, DevisableMixin):
1041 """
1042 Photo d'une comparaison salariale au moment de l'embauche.
1043 """
1044 objects = DossierComparaisonManager()
1045
1046 implantation = models.ForeignKey(
1047 ref.Implantation, related_name="+", null=True, blank=True
1048 )
1049 poste = models.CharField(max_length=255, null=True, blank=True)
1050 personne = models.CharField(max_length=255, null=True, blank=True)
1051 montant = models.IntegerField(null=True)
1052 devise = models.ForeignKey(
1053 'Devise', related_name='+', null=True, blank=True
1054 )
1055
1056 class Meta:
1057 abstract = True
1058
1059 def __unicode__(self):
1060 return "%s (%s)" % (self.poste, self.personne)
1061
1062
1063 class DossierComparaison(DossierComparaison_):
1064 dossier = models.ForeignKey(
1065 Dossier, related_name='rh_comparaisons'
1066 )
1067
1068
1069 ### RÉMUNÉRATION
1070
1071 class RemunerationMixin(AUFMetadata):
1072
1073 # Identification
1074 type = models.ForeignKey(
1075 'TypeRemuneration', db_column='type', related_name='+',
1076 verbose_name=u"type de rémunération"
1077 )
1078 type_revalorisation = models.ForeignKey(
1079 'TypeRevalorisation', db_column='type_revalorisation',
1080 related_name='+', verbose_name=u"type de revalorisation",
1081 null=True, blank=True
1082 )
1083 montant = models.DecimalField(
1084 null=True, blank=True,
1085 default=0, max_digits=12, decimal_places=2
1086 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1087 devise = models.ForeignKey('Devise', db_column='devise', related_name='+')
1088
1089 # commentaire = precision
1090 commentaire = models.CharField(max_length=255, null=True, blank=True)
1091
1092 # date_debut = anciennement date_effectif
1093 date_debut = models.DateField(u"date de début", null=True, blank=True)
1094 date_fin = models.DateField(u"date de fin", null=True, blank=True)
1095
1096 objects = RemunerationManager()
1097
1098 class Meta:
1099 abstract = True
1100 ordering = ['type__nom', '-date_fin']
1101
1102 def __unicode__(self):
1103 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
1104
1105
1106 class Remuneration_(RemunerationMixin, DevisableMixin):
1107 """
1108 Structure de rémunération (données budgétaires) en situation normale
1109 pour un Dossier. Si un Evenement existe, utiliser la structure de
1110 rémunération EvenementRemuneration de cet événement.
1111 """
1112
1113 def montant_mois(self):
1114 return round(self.montant / 12, 2)
1115
1116 def montant_avec_regime(self):
1117 return round(self.montant * (self.dossier.regime_travail / 100), 2)
1118
1119 def montant_euro_mois(self):
1120 return round(self.montant_euros() / 12, 2)
1121
1122 def __unicode__(self):
1123 try:
1124 devise = self.devise.code
1125 except:
1126 devise = "???"
1127 return "%s %s" % (self.montant, devise)
1128
1129 class Meta:
1130 abstract = True
1131 verbose_name = u"Rémunération"
1132 verbose_name_plural = u"Rémunérations"
1133
1134
1135 class Remuneration(Remuneration_):
1136 dossier = models.ForeignKey(
1137 Dossier, db_column='dossier', related_name='rh_remunerations'
1138 )
1139
1140
1141 ### CONTRATS
1142
1143 class ContratManager(NoDeleteManager):
1144 def get_query_set(self):
1145 return super(ContratManager, self).get_query_set() \
1146 .select_related('dossier', 'dossier__poste')
1147
1148
1149 class Contrat_(AUFMetadata):
1150 """
1151 Document juridique qui encadre la relation de travail d'un Employe
1152 pour un Poste particulier. Pour un Dossier (qui documente cette
1153 relation de travail) plusieurs contrats peuvent être associés.
1154 """
1155 objects = ContratManager()
1156 type_contrat = models.ForeignKey(
1157 'TypeContrat', db_column='type_contrat',
1158 verbose_name=u'type de contrat', related_name='+'
1159 )
1160 date_debut = models.DateField(u"date de début")
1161 date_fin = models.DateField(u"date de fin", null=True, blank=True)
1162 fichier = models.FileField(
1163 upload_to=contrat_dispatch, storage=storage_prive, null=True,
1164 blank=True
1165 )
1166
1167 class Meta:
1168 abstract = True
1169 ordering = ['dossier__employe__nom']
1170 verbose_name = u"Contrat"
1171 verbose_name_plural = u"Contrats"
1172
1173 def __unicode__(self):
1174 return u'%s - %s' % (self.dossier, self.id)
1175
1176
1177 class Contrat(Contrat_):
1178 dossier = models.ForeignKey(
1179 Dossier, db_column='dossier', related_name='rh_contrats'
1180 )
1181
1182
1183 ### RÉFÉRENCES RH
1184
1185 class CategorieEmploi(AUFMetadata):
1186 """
1187 Catégorie utilisée dans la gestion des Postes.
1188 Catégorie supérieure à TypePoste.
1189 """
1190 nom = models.CharField(max_length=255)
1191
1192 class Meta:
1193 ordering = ('nom',)
1194 verbose_name = u"catégorie d'emploi"
1195 verbose_name_plural = u"catégories d'emploi"
1196
1197 def __unicode__(self):
1198 return self.nom
1199
1200
1201 class FamilleProfessionnelle(models.Model):
1202 """
1203 Famille professionnelle d'un poste.
1204 """
1205 nom = models.CharField(max_length=100)
1206
1207 class Meta:
1208 ordering = ('nom',)
1209 verbose_name = u'famille professionnelle'
1210 verbose_name_plural = u'familles professionnelles'
1211
1212 def __unicode__(self):
1213 return self.nom
1214
1215
1216 class TypePoste(AUFMetadata):
1217 """
1218 Catégorie de Poste.
1219 """
1220 nom = models.CharField(max_length=255)
1221 nom_feminin = models.CharField(u"nom féminin", max_length=255)
1222 is_responsable = models.BooleanField(
1223 u"poste de responsabilité", default=False
1224 )
1225 categorie_emploi = models.ForeignKey(
1226 CategorieEmploi, db_column='categorie_emploi', related_name='+',
1227 verbose_name=u"catégorie d'emploi"
1228 )
1229 famille_professionnelle = models.ForeignKey(
1230 FamilleProfessionnelle, related_name='types_de_poste',
1231 verbose_name=u"famille professionnelle", blank=True, null=True
1232 )
1233
1234 class Meta:
1235 ordering = ['nom']
1236 verbose_name = u"Type de poste"
1237 verbose_name_plural = u"Types de poste"
1238
1239 def __unicode__(self):
1240 return u'%s' % (self.nom)
1241
1242 TYPE_PAIEMENT_CHOICES = (
1243 (u'Régulier', u'Régulier'),
1244 (u'Ponctuel', u'Ponctuel'),
1245 )
1246
1247 NATURE_REMUNERATION_CHOICES = (
1248 (u'Accessoire', u'Accessoire'),
1249 (u'Charges', u'Charges'),
1250 (u'Indemnité', u'Indemnité'),
1251 (u'RAS', u'Rémunération autre source'),
1252 (u'Traitement', u'Traitement'),
1253 )
1254
1255
1256 class TypeRemuneration(AUFMetadata):
1257 """
1258 Catégorie de Remuneration.
1259 """
1260 objects = TypeRemunerationManager()
1261
1262 nom = models.CharField(max_length=255)
1263 type_paiement = models.CharField(
1264 u"type de paiement", max_length=30, choices=TYPE_PAIEMENT_CHOICES
1265 )
1266 nature_remuneration = models.CharField(
1267 u"nature de la rémunération", max_length=30,
1268 choices=NATURE_REMUNERATION_CHOICES
1269 )
1270 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
1271
1272 class Meta:
1273 ordering = ['nom']
1274 verbose_name = u"Type de rémunération"
1275 verbose_name_plural = u"Types de rémunération"
1276
1277 def __unicode__(self):
1278 if self.archive:
1279 archive = u"(archivé)"
1280 else:
1281 archive = ""
1282 return u'%s %s' % (self.nom, archive)
1283
1284
1285 class TypeRevalorisation(AUFMetadata):
1286 """
1287 Justification du changement de la Remuneration.
1288 (Actuellement utilisé dans aucun traitement informatique.)
1289 """
1290 nom = models.CharField(max_length=255)
1291
1292 class Meta:
1293 ordering = ['nom']
1294 verbose_name = u"Type de revalorisation"
1295 verbose_name_plural = u"Types de revalorisation"
1296
1297 def __unicode__(self):
1298 return u'%s' % (self.nom)
1299
1300
1301 class Service(AUFMetadata):
1302 """
1303 Unité administrative où les Postes sont rattachés.
1304 """
1305 objects = ServiceManager()
1306
1307 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
1308 nom = models.CharField(max_length=255)
1309
1310 class Meta:
1311 ordering = ['nom']
1312 verbose_name = u"Service"
1313 verbose_name_plural = u"Services"
1314
1315 def __unicode__(self):
1316 if self.archive:
1317 archive = u"(archivé)"
1318 else:
1319 archive = ""
1320 return u'%s %s' % (self.nom, archive)
1321
1322
1323 TYPE_ORGANISME_CHOICES = (
1324 ('MAD', 'Mise à disposition'),
1325 ('DET', 'Détachement'),
1326 )
1327
1328
1329 class OrganismeBstg(AUFMetadata):
1330 """
1331 Organisation d'où provient un Employe mis à disposition (MAD) de
1332 ou détaché (DET) à l'AUF à titre gratuit.
1333
1334 (BSTG = bien et service à titre gratuit.)
1335 """
1336 nom = models.CharField(max_length=255)
1337 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
1338 pays = models.ForeignKey(ref.Pays, to_field='code',
1339 db_column='pays',
1340 related_name='organismes_bstg',
1341 null=True, blank=True)
1342
1343 class Meta:
1344 ordering = ['type', 'nom']
1345 verbose_name = u"Organisme BSTG"
1346 verbose_name_plural = u"Organismes BSTG"
1347
1348 def __unicode__(self):
1349 return u'%s (%s)' % (self.nom, self.get_type_display())
1350
1351 prefix_implantation = "pays__region"
1352
1353 def get_regions(self):
1354 return [self.pays.region]
1355
1356
1357 class Statut(AUFMetadata):
1358 """
1359 Statut de l'Employe dans le cadre d'un Dossier particulier.
1360 """
1361 # Identification
1362 code = models.CharField(
1363 max_length=25, unique=True,
1364 help_text=(
1365 u"Saisir un code court mais lisible pour ce statut : "
1366 u"le code est utilisé pour associer les statuts aux autres "
1367 u"données tout en demeurant plus lisible qu'un identifiant "
1368 u"numérique."
1369 )
1370 )
1371 nom = models.CharField(max_length=255)
1372
1373 class Meta:
1374 ordering = ['code']
1375 verbose_name = u"Statut d'employé"
1376 verbose_name_plural = u"Statuts d'employé"
1377
1378 def __unicode__(self):
1379 return u'%s : %s' % (self.code, self.nom)
1380
1381
1382 TYPE_CLASSEMENT_CHOICES = (
1383 ('S', 'S -Soutien'),
1384 ('T', 'T - Technicien'),
1385 ('P', 'P - Professionel'),
1386 ('C', 'C - Cadre'),
1387 ('D', 'D - Direction'),
1388 ('SO', 'SO - Sans objet [expatriés]'),
1389 ('HG', 'HG - Hors grille [direction]'),
1390 )
1391
1392
1393 class ClassementManager(models.Manager):
1394 """
1395 Ordonner les spcéfiquement les classements.
1396 """
1397 def get_query_set(self):
1398 qs = super(self.__class__, self).get_query_set()
1399 qs = qs.extra(select={
1400 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1401 })
1402 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
1403 return qs.all()
1404
1405
1406 class Classement_(AUFMetadata):
1407 """
1408 Éléments de classement de la
1409 "Grille générique de classement hiérarchique".
1410
1411 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1412 classement dans la grille. Le classement donne le coefficient utilisé dans:
1413
1414 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1415 """
1416 objects = ClassementManager()
1417
1418 # Identification
1419 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
1420 echelon = models.IntegerField(u"échelon", blank=True, default=0)
1421 degre = models.IntegerField(u"degré", blank=True, default=0)
1422 coefficient = models.FloatField(u"coefficient", default=0, null=True)
1423
1424 # Méta
1425 # annee # au lieu de date_debut et date_fin
1426 commentaire = models.TextField(null=True, blank=True)
1427
1428 class Meta:
1429 abstract = True
1430 ordering = ['type', 'echelon', 'degre', 'coefficient']
1431 verbose_name = u"Classement"
1432 verbose_name_plural = u"Classements"
1433
1434 def __unicode__(self):
1435 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
1436
1437
1438 class Classement(Classement_):
1439 __doc__ = Classement_.__doc__
1440
1441
1442 class TauxChange_(AUFMetadata):
1443 """
1444 Taux de change de la devise vers l'euro (EUR)
1445 pour chaque année budgétaire.
1446 """
1447 # Identification
1448 devise = models.ForeignKey('Devise', db_column='devise')
1449 annee = models.IntegerField(u"année")
1450 taux = models.FloatField(u"taux vers l'euro")
1451
1452 class Meta:
1453 abstract = True
1454 ordering = ['-annee', 'devise__code']
1455 verbose_name = u"Taux de change"
1456 verbose_name_plural = u"Taux de change"
1457
1458 def __unicode__(self):
1459 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
1460
1461
1462 class TauxChange(TauxChange_):
1463 __doc__ = TauxChange_.__doc__
1464
1465
1466 class ValeurPointManager(NoDeleteManager):
1467
1468 def get_query_set(self):
1469 return super(ValeurPointManager, self).get_query_set() \
1470 .select_related('devise', 'implantation')
1471
1472
1473 class ValeurPoint_(AUFMetadata):
1474 """
1475 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1476 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1477 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1478
1479 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1480 """
1481
1482 actuelles = ValeurPointManager()
1483
1484 valeur = models.FloatField(null=True)
1485 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
1486 implantation = models.ForeignKey(ref.Implantation,
1487 db_column='implantation',
1488 related_name='%(app_label)s_valeur_point')
1489 # Méta
1490 annee = models.IntegerField()
1491
1492 class Meta:
1493 ordering = ['-annee', 'implantation__nom']
1494 abstract = True
1495 verbose_name = u"Valeur du point"
1496 verbose_name_plural = u"Valeurs du point"
1497
1498 def __unicode__(self):
1499 return u'%s %s %s [%s] %s' % (
1500 self.devise.code, self.annee, self.valeur,
1501 self.implantation.nom_court, self.devise.nom
1502 )
1503
1504
1505 class ValeurPoint(ValeurPoint_):
1506 __doc__ = ValeurPoint_.__doc__
1507
1508
1509 class Devise(AUFMetadata):
1510 """
1511 Devise monétaire.
1512 """
1513 objects = DeviseManager()
1514
1515 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
1516 code = models.CharField(max_length=10, unique=True)
1517 nom = models.CharField(max_length=255)
1518
1519 class Meta:
1520 ordering = ['code']
1521 verbose_name = u"Devise"
1522 verbose_name_plural = u"Devises"
1523
1524 def __unicode__(self):
1525 return u'%s - %s' % (self.code, self.nom)
1526
1527
1528 class TypeContrat(AUFMetadata):
1529 """
1530 Type de contrat.
1531 """
1532 nom = models.CharField(max_length=255)
1533 nom_long = models.CharField(max_length=255)
1534
1535 class Meta:
1536 ordering = ['nom']
1537 verbose_name = u"Type de contrat"
1538 verbose_name_plural = u"Types de contrat"
1539
1540 def __unicode__(self):
1541 return u'%s' % (self.nom)
1542
1543
1544 ### AUTRES
1545
1546 class ResponsableImplantationProxy(ref.Implantation):
1547
1548 def save(self):
1549 pass
1550
1551 class Meta:
1552 proxy = True
1553 verbose_name = u"Responsable d'implantation"
1554 verbose_name_plural = u"Responsables d'implantation"
1555
1556
1557 class ResponsableImplantation(models.Model):
1558 """
1559 Le responsable d'une implantation.
1560 Anciennement géré sur le Dossier du responsable.
1561 """
1562 employe = models.ForeignKey(
1563 'Employe', db_column='employe', related_name='+', null=True,
1564 blank=True
1565 )
1566 implantation = models.OneToOneField(
1567 "ResponsableImplantationProxy", db_column='implantation',
1568 related_name='responsable', unique=True
1569 )
1570
1571 def __unicode__(self):
1572 return u'%s : %s' % (self.implantation, self.employe)
1573
1574 class Meta:
1575 ordering = ['implantation__nom']
1576 verbose_name = "Responsable d'implantation"
1577 verbose_name_plural = "Responsables d'implantation"