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