Supression d'une redéfinition de dossier_piece_dispatch
[auf_rh_dae.git] / project / rh / models.py
CommitLineData
e9bbd6ba 1# -=- encoding: utf-8 -=-
2
c267f20c 3from datetime import date
4
83b7692b 5from django.core.files.storage import FileSystemStorage
49f9f116 6from django.db import models
d6985a3a 7from django.conf import settings
c267f20c 8
d6985a3a
OL
9from auf.django.metadata.models import AUFMetadata
10from auf.django.metadata.managers import NoDeleteManager
83b7692b 11import datamaster_modeles.models as ref
b4aeadf3 12from validators import validate_date_passee
83b7692b 13
2d4d2fcf 14# Constantes
8c1ae2b3 15HELP_TEXT_DATE = "format: aaaa-mm-jj"
6e4600ef 16REGIME_TRAVAIL_DEFAULT = 100.00
17REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = 35.00
2d4d2fcf 18
19
83b7692b 20# Upload de fichiers
21storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
22 base_url=settings.PRIVE_MEDIA_URL)
23
24def poste_piece_dispatch(instance, filename):
25 path = "poste/%s/%s" % (instance.poste_id, filename)
26 return path
27
28def dossier_piece_dispatch(instance, filename):
29 path = "dossier/%s/%s" % (instance.dossier_id, filename)
30 return path
31
5ea6b5bb 32def employe_piece_dispatch(instance, filename):
33 path = "employe/%s/%s" % (instance.employe_id, filename)
34 return path
35
5f5a4f06 36
d6985a3a 37class Commentaire(AUFMetadata):
2d4d2fcf 38 texte = models.TextField()
6e4600ef 39 owner = models.ForeignKey('auth.User', db_column='owner', related_name='+')
2d4d2fcf 40
41 class Meta:
42 abstract = True
6e4600ef 43 ordering = ['-date_creation']
44
45 def __unicode__(self):
46 return u'%s' % (self.texte)
07b40eda 47
83b7692b 48
49### POSTE
50
51POSTE_APPEL_CHOICES = (
52 ('interne', 'Interne'),
53 ('externe', 'Externe'),
54)
55
d6985a3a 56class PosteManager(NoDeleteManager):
1f2979b8
OL
57 def get_query_set(self):
58 return super(PosteManager, self).get_query_set().select_related('implantation')
59
d6985a3a 60class Poste_(AUFMetadata):
6e4600ef 61 """Un Poste est un emploi (job) à combler dans une implantation.
62 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
63 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
64 """
1f2979b8
OL
65
66 objects = PosteManager()
67
83b7692b 68 # Identification
2d4d2fcf 69 nom = models.CharField(max_length=255,
c1195471 70 verbose_name = u"Titre du poste", )
2d4d2fcf 71 nom_feminin = models.CharField(max_length=255,
c1195471 72 verbose_name = u"Titre du poste (au féminin)",
6e4600ef 73 null=True)
74 implantation = models.ForeignKey(ref.Implantation,
75 db_column='implantation', related_name='+')
76 type_poste = models.ForeignKey('TypePoste', db_column='type_poste',
77 related_name='+',
78 null=True)
8277a35b 79 service = models.ForeignKey('Service', db_column='service', null=True,
6e4600ef 80 related_name='+',
c1195471 81 verbose_name = u"Direction/Service/Pôle support",
6e4600ef 82 default=1) # default = Rectorat
83 responsable = models.ForeignKey('Poste', db_column='responsable',
8277a35b 84 related_name='+', null=True,
c1195471 85 verbose_name = u"Poste du responsable",
6e4600ef 86 default=149) # default = Recteur
83b7692b 87
88 # Contrat
89 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 90 default=REGIME_TRAVAIL_DEFAULT, null=True,
c1195471 91 verbose_name = u"Temps de travail",
8c1ae2b3 92 help_text="% du temps complet")
83b7692b 93 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
8277a35b 94 decimal_places=2, null=True,
2d4d2fcf 95 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
c1195471 96 verbose_name = u"Nb. heures par semaine")
83b7692b 97
98 # Recrutement
c1195471 99 local = models.NullBooleanField(verbose_name = u"Local", default=True,
8277a35b 100 null=True, blank=True)
c1195471 101 expatrie = models.NullBooleanField(verbose_name = u"Expatrié", default=False,
8277a35b
NC
102 null=True, blank=True)
103 mise_a_disposition = models.NullBooleanField(
c1195471 104 verbose_name = u"Mise à disposition",
8277a35b
NC
105 null=True, default=False)
106 appel = models.CharField(max_length=10, null=True,
c1195471 107 verbose_name = u"Appel à candidature",
6e4600ef 108 choices=POSTE_APPEL_CHOICES,
109 default='interne')
83b7692b 110
111 # Rémunération
6e4600ef 112 classement_min = models.ForeignKey('Classement',
113 db_column='classement_min', related_name='+',
114 null=True, blank=True)
115 classement_max = models.ForeignKey('Classement',
116 db_column='classement_max', related_name='+',
117 null=True, blank=True)
118 valeur_point_min = models.ForeignKey('ValeurPoint',
119 db_column='valeur_point_min', related_name='+',
120 null=True, blank=True)
121 valeur_point_max = models.ForeignKey('ValeurPoint',
122 db_column='valeur_point_max', related_name='+',
123 null=True, blank=True)
8277a35b 124 devise_min = models.ForeignKey('Devise', db_column='devise_min', null=True,
6e4600ef 125 related_name='+', default=5)
8277a35b 126 devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
6e4600ef 127 related_name='+', default=5)
83b7692b 128 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 129 null=True, default=0)
83b7692b 130 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 131 null=True, default=0)
83b7692b 132 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 133 null=True, default=0)
83b7692b 134 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 135 null=True, default=0)
83b7692b 136 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 137 null=True, default=0)
83b7692b 138 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 139 null=True, default=0)
83b7692b 140
141 # Comparatifs de rémunération
8277a35b 142 devise_comparaison = models.ForeignKey('Devise', null=True,
6e4600ef 143 db_column='devise_comparaison',
144 related_name='+',
145 default=5)
83b7692b 146 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 147 null=True, blank=True)
83b7692b 148 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 149 null=True, blank=True)
83b7692b 150 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 151 null=True, blank=True)
83b7692b 152 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 153 null=True, blank=True)
83b7692b 154 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 155 null=True, blank=True)
83b7692b 156 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 157 null=True, blank=True)
83b7692b 158 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 159 null=True, blank=True)
83b7692b 160 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 161 null=True, blank=True)
83b7692b 162 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 163 null=True, blank=True)
83b7692b 164 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 165 null=True, blank=True)
83b7692b 166
167 # Justification
6e4600ef 168 justification = models.TextField(null=True, blank=True)
83b7692b 169
2d4d2fcf 170 # Autres Metadata
07b40eda 171 date_validation = models.DateTimeField(null=True, blank=True) # de dae
3f5f3898 172 date_debut = models.DateField(verbose_name=u"Date de début",
7332d8f9
OL
173 help_text=HELP_TEXT_DATE,
174 null=True, blank=True)
3f5f3898 175 date_fin = models.DateField(verbose_name=u"Date de fin",
6e4600ef 176 help_text=HELP_TEXT_DATE,
177 null=True, blank=True)
178
179 class Meta:
37868f0b 180 abstract = True
6e4600ef 181 ordering = ['implantation__nom', 'nom']
c1195471
OL
182 verbose_name = u"Poste"
183 verbose_name_plural = u"Postes"
6e4600ef 184
83b7692b 185 def __unicode__(self):
8c1ae2b3 186 representation = u'%s - %s [%s]' % (self.implantation, self.nom,
187 self.id)
188 if self.is_vacant():
189 representation = representation + u' (vacant)'
190 return representation
191
192 def is_vacant(self):
193 # TODO : si existe un dossier actif pour ce poste, return False
194 # self.dossier_set.all() fonctionne pas
195 return False
83b7692b 196
197
37868f0b
NC
198class Poste(Poste_):
199 __doc__ = Poste_.__doc__
200
201
769a0755
NC
202class Poste(Poste_):
203 __doc__ = Poste_.__doc__
204
205
83b7692b 206POSTE_FINANCEMENT_CHOICES = (
207 ('A', 'A - Frais de personnel'),
208 ('B', 'B - Projet(s)-Titre(s)'),
209 ('C', 'C - Autre')
210)
211
6e7c919b
NC
212
213class PosteFinancement_(models.Model):
6e4600ef 214 """Pour un Poste, structure d'informations décrivant comment on prévoit
215 financer ce Poste.
216 """
217 poste = models.ForeignKey('Poste', db_column='poste',
6e7c919b 218 related_name='%(app_label)s_financements')
83b7692b 219 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
220 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
8c1ae2b3 221 help_text="ex.: 33.33 % (décimale avec point)")
83b7692b 222 commentaire = models.TextField(
8c1ae2b3 223 help_text="Spécifiez la source de financement.")
83b7692b 224
225 class Meta:
6e7c919b 226 abstract = True
83b7692b 227 ordering = ['type']
6e4600ef 228
229 def __unicode__(self):
230 return u'%s : %s %' % (self.type, self.pourcentage)
83b7692b 231
6e7c919b
NC
232
233class PosteFinancement(PosteFinancement_):
234 __doc__ = PosteFinancement_.__doc__
235
236
83b7692b 237class PostePiece(models.Model):
6e4600ef 238 """Documents relatifs au Poste.
7abc6d45 239 Ex.: Description de poste
240 """
8c1ae2b3 241 poste = models.ForeignKey('Poste', db_column='poste',
6e4600ef 242 related_name='pieces')
c1195471
OL
243 nom = models.CharField(verbose_name = u"Nom", max_length=255)
244 fichier = models.FileField(verbose_name = u"Fichier",
83b7692b 245 upload_to=poste_piece_dispatch,
246 storage=storage_prive)
247
6e4600ef 248 class Meta:
249 ordering = ['nom']
250
251 def __unicode__(self):
252 return u'%s' % (self.nom)
253
068d1462
OL
254class PosteComparaison(models.Model):
255 """
256 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
257 """
258 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
0f0dacbb 259 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
c1195471 260 nom = models.CharField(verbose_name = u"Poste", max_length=255, null=True, blank=True)
068d1462
OL
261 montant = models.IntegerField(null=True)
262 devise = models.ForeignKey("Devise", default=5, related_name='+', null=True, blank=True)
1d0f4eef
OL
263
264 def taux_devise(self):
265 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
266 if len(liste_taux) == 0:
267 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
268 else:
269 return liste_taux[0].taux
270
271 def montant_euros(self):
272 return round(float(self.montant) * float(self.taux_devise()), 2)
068d1462
OL
273
274
07b40eda 275class PosteCommentaire(Commentaire):
8c1ae2b3 276 poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
83b7692b 277
2d4d2fcf 278
83b7692b 279### EMPLOYÉ/PERSONNE
e9bbd6ba 280
281GENRE_CHOICES = (
282 ('M', 'Homme'),
283 ('F', 'Femme'),
284)
285SITUATION_CHOICES = (
286 ('C', 'Célibataire'),
287 ('F', 'Fiancé'),
288 ('M', 'Marié'),
289)
290
d6985a3a 291class Employe(AUFMetadata):
6e4600ef 292 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
293 Dossiers qu'il occupe ou a occupé de Postes.
294
295 Cette classe aurait pu avantageusement s'appeler Personne car la notion
296 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
297 """
9afaa55e 298 # Identification
e9bbd6ba 299 nom = models.CharField(max_length=255)
c1195471 300 prenom = models.CharField(max_length=255, verbose_name = u"Prénom")
6e4600ef 301 nom_affichage = models.CharField(max_length=255,
c1195471 302 verbose_name = u"Nom d'affichage",
6e4600ef 303 null=True, blank=True)
83b7692b 304 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 305 db_column='nationalite',
8c1ae2b3 306 related_name='employes_nationalite',
c1195471 307 verbose_name = u"Nationalité")
6e4600ef 308 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 309 verbose_name = u"Date de naissance",
b4aeadf3 310 validators=[validate_date_passee],
6e4600ef 311 null=True, blank=True)
2d4d2fcf 312 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 313
9afaa55e 314 # Infos personnelles
6e4600ef 315 situation_famille = models.CharField(max_length=1,
316 choices=SITUATION_CHOICES,
c1195471 317 verbose_name = u"Situation familiale",
6e4600ef 318 null=True, blank=True)
c1195471 319 date_entree = models.DateField(verbose_name = u"Date d'entrée à l'AUF",
6e4600ef 320 help_text=HELP_TEXT_DATE,
7abc6d45 321 null=True, blank=True)
83b7692b 322
9afaa55e 323 # Coordonnées
8c1ae2b3 324 tel_domicile = models.CharField(max_length=255,
c1195471 325 verbose_name = u"Tél. domicile",
8c1ae2b3 326 null=True, blank=True)
327 tel_cellulaire = models.CharField(max_length=255,
c1195471 328 verbose_name = u"Tél. cellulaire",
8c1ae2b3 329 null=True, blank=True)
e9bbd6ba 330 adresse = models.CharField(max_length=255, null=True, blank=True)
e9bbd6ba 331 ville = models.CharField(max_length=255, null=True, blank=True)
332 province = models.CharField(max_length=255, null=True, blank=True)
333 code_postal = models.CharField(max_length=255, null=True, blank=True)
6e4600ef 334 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
335 related_name='employes',
336 null=True, blank=True)
9afaa55e 337
6e4600ef 338 class Meta:
67666927 339 ordering = ['nom_affichage','nom','prenom']
c1195471
OL
340 verbose_name = u"Employé"
341 verbose_name_plural = u"Employés"
6e4600ef 342
9afaa55e 343 def __unicode__(self):
8c1ae2b3 344 return u'%s' % (self.get_nom())
345
346 def get_nom(self):
ebc8eb32 347 nom_affichage = self.nom_affichage
348 if not nom_affichage:
349 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
8c1ae2b3 350 return nom_affichage
d04d084c 351
352 def civilite(self):
353 civilite = u''
354 if self.genre.upper() == u'M':
355 civilite = u'M.'
356 elif self.genre.upper() == u'F':
357 civilite = u'Mme'
358 return civilite
5ea6b5bb 359
360 def url_photo(self):
361 """Retourne l'URL du service retournant la photo de l'Employe.
362 Équivalent reverse url 'rh_photo' avec id en param.
363 """
364 from django.core.urlresolvers import reverse
365 return reverse('rh_photo', kwargs={'id':self.id})
35c0c2fe 366
c267f20c 367 def dossiers_passes(self):
368 today = date.today()
65f9fac8 369 dossiers_passes = self.dossiers.filter(date_fin__lt=today).order_by('-date_fin')
370 for d in dossiers_passes:
371 d.archive = True
372 return dossiers_passes
c267f20c 373
374 def dossiers_futurs(self):
375 today = date.today()
376 return self.dossiers.filter(date_debut__gt=today).order_by('-date_fin')
377
378 def dossiers_encours(self):
379 dossiers_p_f = self.dossiers_passes() | self.dossiers_futurs()
380 ids_dossiers_p_f = [d.id for d in dossiers_p_f]
65f9fac8 381 dossiers_encours = self.dossiers.exclude(id__in=ids_dossiers_p_f).order_by('-date_fin')
382
383 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
384 for d in dossiers_encours:
385 d.remunerations = Remuneration.objects.filter(dossier=d.id).order_by('-id')
386 return dossiers_encours
35c0c2fe 387
388 def postes_encours(self):
389 postes_encours = set()
390 for d in self.dossiers_encours():
391 postes_encours.add(d.poste)
392 return postes_encours
393
394 def poste_principal(self):
65f9fac8 395 """
396 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
397 Idée derrière :
398 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
399 """
400 poste = Poste.objects.none()
401 try:
402 poste = self.dossiers_encours().order_by('date_debut')[0].poste
403 except:
404 pass
405 return poste
9afaa55e 406
7abc6d45 407class EmployePiece(models.Model):
6e4600ef 408 """Documents relatifs à un employé.
7abc6d45 409 Ex.: CV...
410 """
8c1ae2b3 411 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 412 related_name='+')
8c1ae2b3 413 nom = models.CharField(verbose_name="Nom", max_length=255)
414 fichier = models.FileField(verbose_name="Fichier",
5ea6b5bb 415 upload_to=employe_piece_dispatch,
7abc6d45 416 storage=storage_prive)
417
6e4600ef 418 class Meta:
419 ordering = ['nom']
420
421 def __unicode__(self):
422 return u'%s' % (self.nom)
423
07b40eda 424class EmployeCommentaire(Commentaire):
8c1ae2b3 425 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 426 related_name='+')
9afaa55e 427
2d4d2fcf 428
e9bbd6ba 429LIEN_PARENTE_CHOICES = (
430 ('Conjoint', 'Conjoint'),
431 ('Conjointe', 'Conjointe'),
432 ('Fille', 'Fille'),
433 ('Fils', 'Fils'),
434)
435
d6985a3a 436class AyantDroit(AUFMetadata):
6e4600ef 437 """Personne en relation avec un Employe.
438 """
9afaa55e 439 # Identification
e9bbd6ba 440 nom = models.CharField(max_length=255)
8c1ae2b3 441 prenom = models.CharField(max_length=255,
c1195471 442 verbose_name = u"Prénom",)
6e4600ef 443 nom_affichage = models.CharField(max_length=255,
c1195471 444 verbose_name = u"Nom d'affichage",
6e4600ef 445 null=True, blank=True)
2d4d2fcf 446 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 447 db_column='nationalite',
8c1ae2b3 448 related_name='ayantdroits_nationalite',
c1195471 449 verbose_name = u"Nationalité")
6e4600ef 450 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 451 verbose_name = u"Date de naissance",
25037368 452 validators=[validate_date_passee],
6e4600ef 453 null=True, blank=True)
2d4d2fcf 454 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 455
9afaa55e 456 # Relation
457 employe = models.ForeignKey('Employe', db_column='employe',
8c1ae2b3 458 related_name='ayantdroits',
c1195471 459 verbose_name = u"Employé")
6e4600ef 460 lien_parente = models.CharField(max_length=10,
461 choices=LIEN_PARENTE_CHOICES,
c1195471 462 verbose_name = u"Lien de parenté",
6e4600ef 463 null=True, blank=True)
464
465 class Meta:
466 ordering = ['nom_affichage']
c1195471
OL
467 verbose_name = u"Ayant droit"
468 verbose_name_plural = u"Ayants droit"
8c1ae2b3 469
6e4600ef 470 def __unicode__(self):
8c1ae2b3 471 return u'%s' % (self.get_nom())
472
473 def get_nom(self):
474 nom_affichage = self.nom_affichage
475 if not nom_affichage:
476 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
477 return nom_affichage
83b7692b 478
07b40eda 479class AyantDroitCommentaire(Commentaire):
8c1ae2b3 480 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
6e4600ef 481 related_name='+')
83b7692b 482
2d4d2fcf 483
83b7692b 484### DOSSIER
485
486STATUT_RESIDENCE_CHOICES = (
487 ('local', 'Local'),
488 ('expat', 'Expatrié'),
489)
490
491COMPTE_COMPTA_CHOICES = (
492 ('coda', 'CODA'),
493 ('scs', 'SCS'),
494 ('aucun', 'Aucun'),
495)
496
d6985a3a 497class Dossier_(AUFMetadata):
6e4600ef 498 """Le Dossier regroupe les informations relatives à l'occupation
499 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
500 par un Employe.
501
502 Plusieurs Contrats peuvent être associés au Dossier.
503 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
504 lequel aucun Dossier n'existe est un poste vacant.
505 """
83b7692b 506 # Identification
6e4600ef 507 employe = models.ForeignKey('Employe', db_column='employe',
d04d084c 508 related_name='dossiers',
bf4f5c77 509 verbose_name=u"Employé")
1f2979b8 510 poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
8277a35b
NC
511 statut = models.ForeignKey('Statut', related_name='+', default=3,
512 null=True)
83b7692b 513 organisme_bstg = models.ForeignKey('OrganismeBstg',
6e4600ef 514 db_column='organisme_bstg',
515 related_name='+',
c1195471 516 verbose_name = u"Organisme",
8c1ae2b3 517 help_text="Si détaché (DET) ou \
6e4600ef 518 mis à disposition (MAD), \
519 préciser l'organisme.",
520 null=True, blank=True)
521
83b7692b 522 # Recrutement
2d4d2fcf 523 remplacement = models.BooleanField(default=False)
83b7692b 524 statut_residence = models.CharField(max_length=10, default='local',
c1195471 525 verbose_name = u"Statut", null=True,
2d4d2fcf 526 choices=STATUT_RESIDENCE_CHOICES)
83b7692b 527
528 # Rémunération
6e4600ef 529 classement = models.ForeignKey('Classement', db_column='classement',
530 related_name='+',
531 null=True, blank=True)
8277a35b 532 regime_travail = models.DecimalField(max_digits=12, null=True,
2d4d2fcf 533 decimal_places=2,
534 default=REGIME_TRAVAIL_DEFAULT,
c1195471 535 verbose_name = u"Régime de travail",
8c1ae2b3 536 help_text="% du temps complet")
83b7692b 537 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
8277a35b 538 decimal_places=2, null=True,
2d4d2fcf 539 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
c1195471 540 verbose_name = u"Nb. heures par semaine")
7abc6d45 541
542 # Occupation du Poste par cet Employe (anciennement "mandat")
c1195471 543 date_debut = models.DateField(verbose_name = u"Date de début d'occupation \
6e4600ef 544 de poste",
545 help_text=HELP_TEXT_DATE)
c1195471 546 date_fin = models.DateField(verbose_name = u"Date de fin d'occupation \
6e4600ef 547 de poste",
2d4d2fcf 548 help_text=HELP_TEXT_DATE,
6e4600ef 549 null=True, blank=True)
e9bbd6ba 550
2d4d2fcf 551 # Comptes
552 # TODO?
83b7692b 553
6e4600ef 554 class Meta:
37868f0b 555 abstract = True
49449367 556 ordering = ['employe__nom', ]
3f5f3898 557 verbose_name = u"Dossier"
8c1ae2b3 558 verbose_name_plural = "Dossiers"
6e4600ef 559
65f9fac8 560 def salaire_theorique(self):
561 annee = date.today().year
562 coeff = self.classement.coefficient
563 implantation = self.poste.implantation
564 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
565
566 montant = coeff * point.valeur
567 devise = point.devise
568 return {'montant':montant, 'devise':devise}
569
83b7692b 570 def __unicode__(self):
8c1ae2b3 571 poste = self.poste.nom
572 if self.employe.genre == 'F':
573 poste = self.poste.nom_feminin
574 return u'%s - %s' % (self.employe, poste)
83b7692b 575
37868f0b
NC
576
577class Dossier(Dossier_):
578 __doc__ = Dossier_.__doc__
579
580
83b7692b 581class DossierPiece(models.Model):
7abc6d45 582 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
583 Ex.: Lettre de motivation.
584 """
8c1ae2b3 585 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 586 related_name='+')
c1195471
OL
587 nom = models.CharField(verbose_name = u"Nom", max_length=255)
588 fichier = models.FileField(verbose_name = u"Fichier",
83b7692b 589 upload_to=dossier_piece_dispatch,
590 storage=storage_prive)
591
6e4600ef 592 class Meta:
593 ordering = ['nom']
594
595 def __unicode__(self):
596 return u'%s' % (self.nom)
597
07b40eda 598class DossierCommentaire(Commentaire):
8c1ae2b3 599 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 600 related_name='+')
83b7692b 601
1d0f4eef
OL
602class DossierComparaison(models.Model):
603 """
604 Photo d'une comparaison salariale au moment de l'embauche.
605 """
606 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
8c6269cc 607 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
1d0f4eef
OL
608 poste = models.CharField(max_length=255, null=True, blank=True)
609 personne = models.CharField(max_length=255, null=True, blank=True)
610 montant = models.IntegerField(null=True)
93494cf1 611 devise = models.ForeignKey('Devise', default=5, related_name='+', null=True, blank=True)
1d0f4eef
OL
612
613 def taux_devise(self):
614 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
615 if len(liste_taux) == 0:
616 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
617 else:
618 return liste_taux[0].taux
619
620 def montant_euros(self):
621 return round(float(self.montant) * float(self.taux_devise()), 2)
622
2d4d2fcf 623
07b40eda 624### RÉMUNÉRATION
e9bbd6ba 625
d6985a3a 626class RemunerationMixin(AUFMetadata):
9afaa55e 627 # Identification
6e7c919b
NC
628 dossier = models.ForeignKey('Dossier', db_column='dossier',
629 related_name='%(app_label)s_%(class)s_remunerations')
83b7692b 630 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 631 related_name='+',
c1195471 632 verbose_name = u"Type de rémunération")
7abc6d45 633 type_revalorisation = models.ForeignKey('TypeRevalorisation',
634 db_column='type_revalorisation',
6e4600ef 635 related_name='+',
c1195471 636 verbose_name = u"Type de revalorisation",
7abc6d45 637 null=True, blank=True)
50fa9bc1 638 montant = models.FloatField(null=True, blank=True,
6e4600ef 639 default=0)
2d4d2fcf 640 # Annuel (12 mois, 52 semaines, 364 jours?)
c589d980 641 devise = models.ForeignKey('Devise', to_field='id',
6e4600ef 642 db_column='devise', related_name='+',
643 default=5)
2d4d2fcf 644 # commentaire = precision
645 commentaire = models.CharField(max_length=255, null=True, blank=True)
646 # date_debut = anciennement date_effectif
8c1ae2b3 647 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 648 verbose_name = u"Date de début",
6e4600ef 649 null=True, blank=True)
650 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 651 verbose_name = u"Date de fin",
6e4600ef 652 null=True, blank=True)
83b7692b 653
2d4d2fcf 654 class Meta:
655 abstract = True
6e4600ef 656 ordering = ['type__nom', '-date_fin']
657
658 def __unicode__(self):
659 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
2d4d2fcf 660
6e7c919b 661class Remuneration_(RemunerationMixin):
2d4d2fcf 662 """Structure de rémunération (données budgétaires) en situation normale
663 pour un Dossier. Si un Evenement existe, utiliser la structure de
664 rémunération EvenementRemuneration de cet événement.
665 """
83b7692b 666
667 def montant_mois(self):
668 return round(self.montant / 12, 2)
669
670 def taux_devise(self):
671 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
672
673 def montant_euro(self):
674 return round(float(self.montant) / float(self.taux_devise()), 2)
675
676 def montant_euro_mois(self):
677 return round(self.montant_euro() / 12, 2)
9afaa55e 678
679 def __unicode__(self):
680 try:
681 devise = self.devise.code
682 except:
683 devise = "???"
684 return "%s %s" % (self.montant, devise)
83b7692b 685
6e7c919b
NC
686 class Meta:
687 abstract = True
c1195471
OL
688 verbose_name = u"Rémunération"
689 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
690
691
692class Remuneration(Remuneration_):
693 __doc__ = Remuneration_.__doc__
694
2d4d2fcf 695
696### CONTRATS
c41b7fcc
OL
697
698class ContratManager(NoDeleteManager):
699 def get_query_set(self):
700 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
701
2d4d2fcf 702
d6985a3a 703class Contrat(AUFMetadata):
2d4d2fcf 704 """Document juridique qui encadre la relation de travail d'un Employe
705 pour un Poste particulier. Pour un Dossier (qui documente cette
706 relation de travail) plusieurs contrats peuvent être associés.
707 """
c41b7fcc
OL
708
709 objects = ContratManager()
710
6e4600ef 711 dossier = models.ForeignKey('Dossier', db_column='dossier',
65f9fac8 712 related_name='contrats')
6e4600ef 713 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 714 related_name='+',
c1195471 715 verbose_name = u"Type de contrat")
8c1ae2b3 716 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 717 verbose_name = u"Date de début")
6e4600ef 718 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 719 verbose_name = u"Date de fin",
6e4600ef 720 null=True, blank=True)
721
722 class Meta:
723 ordering = ['dossier__employe__nom_affichage']
c1195471
OL
724 verbose_name = u"Contrat"
725 verbose_name_plural = u"Contrats"
6e4600ef 726
727 def __unicode__(self):
8c1ae2b3 728 return u'%s - %s' % (self.dossier, self.id)
6e4600ef 729
730# TODO? class ContratPiece(models.Model):
2d4d2fcf 731
732
733### ÉVÉNEMENTS
734
d6985a3a 735class Evenement_(AUFMetadata):
6e4600ef 736 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
737 d'un Dossier qui vient altérer des informations normales liées à un Dossier
738 (ex.: la Remuneration).
739
740 Ex.: congé de maternité, maladie...
741
742 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
743 différent et une rémunération en conséquence. On souhaite toutefois
744 conserver le Dossier intact afin d'éviter une re-saisie des données lors
745 du retour à la normale.
746 """
8c1ae2b3 747 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 748 related_name='+')
749 nom = models.CharField(max_length=255)
8c1ae2b3 750 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 751 verbose_name = u"Date de début")
8c1ae2b3 752 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 753 verbose_name = u"Date de fin",
6e4600ef 754 null=True, blank=True)
6e7c919b 755
6e4600ef 756 class Meta:
6e7c919b 757 abstract = True
6e4600ef 758 ordering = ['nom']
c1195471
OL
759 verbose_name = u"Évènement"
760 verbose_name_plural = u"Évènements"
6e4600ef 761
762 def __unicode__(self):
763 return u'%s' % (self.nom)
6e7c919b
NC
764
765
766class Evenement(Evenement_):
767 __doc__ = Evenement_.__doc__
768
2d4d2fcf 769
6e7c919b 770class EvenementRemuneration_(RemunerationMixin):
6e4600ef 771 """Structure de rémunération liée à un Evenement qui remplace
772 temporairement la Remuneration normale d'un Dossier, pour toute la durée
773 de l'Evenement.
774 """
775 evenement = models.ForeignKey("Evenement", db_column='evenement',
8c1ae2b3 776 related_name='+',
c1195471 777 verbose_name = u"Évènement")
8c1ae2b3 778 # TODO : le champ dossier hérité de Remuneration doit être dérivé
779 # de l'Evenement associé
83b7692b 780
6e7c919b
NC
781 class Meta:
782 abstract = True
8c1ae2b3 783 ordering = ['evenement', 'type__nom', '-date_fin']
c1195471
OL
784 verbose_name = u"Évènement - rémunération"
785 verbose_name_plural = u"Évènements - rémunérations"
6e7c919b
NC
786
787
788class EvenementRemuneration(EvenementRemuneration_):
789 __doc__ = EvenementRemuneration_.__doc__
83b7692b 790
f31ddfa0
NC
791 class Meta:
792 abstract = True
793
794
795class EvenementRemuneration(EvenementRemuneration_):
796 __doc__ = EvenementRemuneration_.__doc__
797
83b7692b 798
799### RÉFÉRENCES RH
800
d6985a3a 801class FamilleEmploi(AUFMetadata):
6e4600ef 802 """Catégorie utilisée dans la gestion des Postes.
803 Catégorie supérieure à TypePoste.
804 """
e9bbd6ba 805 nom = models.CharField(max_length=255)
6e4600ef 806
8c1ae2b3 807 class Meta:
808 ordering = ['nom']
c1195471
OL
809 verbose_name = u"Famille d'emploi"
810 verbose_name_plural = u"Familles d'emploi"
8c1ae2b3 811
6e4600ef 812 def __unicode__(self):
813 return u'%s' % (self.nom)
e9bbd6ba 814
d6985a3a 815class TypePoste(AUFMetadata):
6e4600ef 816 """Catégorie de Poste.
817 """
e9bbd6ba 818 nom = models.CharField(max_length=255)
8c1ae2b3 819 nom_feminin = models.CharField(max_length=255,
c1195471 820 verbose_name = u"Nom féminin")
6e4600ef 821
8c1ae2b3 822 is_responsable = models.BooleanField(default=False,
c1195471 823 verbose_name = u"Poste de responsabilité")
9afaa55e 824 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 825 db_column='famille_emploi',
8c1ae2b3 826 related_name='+',
c1195471 827 verbose_name = u"Famille d'emploi")
e9bbd6ba 828
6e4600ef 829 class Meta:
830 ordering = ['nom']
c1195471
OL
831 verbose_name = u"Type de poste"
832 verbose_name_plural = u"Types de poste"
6e4600ef 833
e9bbd6ba 834 def __unicode__(self):
6e4600ef 835 return u'%s' % (self.nom)
e9bbd6ba 836
837
838TYPE_PAIEMENT_CHOICES = (
839 ('Régulier', 'Régulier'),
840 ('Ponctuel', 'Ponctuel'),
841)
842
843NATURE_REMUNERATION_CHOICES = (
844 ('Accessoire', 'Accessoire'),
845 ('Charges', 'Charges'),
846 ('Indemnité', 'Indemnité'),
7abc6d45 847 ('RAS', 'Rémunération autre source'),
e9bbd6ba 848 ('Traitement', 'Traitement'),
849)
850
d6985a3a 851class TypeRemuneration(AUFMetadata):
6e4600ef 852 """Catégorie de Remuneration.
853 """
e9bbd6ba 854 nom = models.CharField(max_length=255)
9afaa55e 855 type_paiement = models.CharField(max_length=30,
8c1ae2b3 856 choices=TYPE_PAIEMENT_CHOICES,
c1195471 857 verbose_name = u"Type de paiement")
9afaa55e 858 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 859 choices=NATURE_REMUNERATION_CHOICES,
c1195471 860 verbose_name = u"Nature de la rémunération")
8c1ae2b3 861
862 class Meta:
863 ordering = ['nom']
c1195471
OL
864 verbose_name = u"Type de rémunération"
865 verbose_name_plural = u"Types de rémunération"
9afaa55e 866
867 def __unicode__(self):
6e4600ef 868 return u'%s' % (self.nom)
e9bbd6ba 869
d6985a3a 870class TypeRevalorisation(AUFMetadata):
7abc6d45 871 """Justification du changement de la Remuneration.
6e4600ef 872 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 873 """
e9bbd6ba 874 nom = models.CharField(max_length=255)
8c1ae2b3 875
876 class Meta:
877 ordering = ['nom']
c1195471
OL
878 verbose_name = u"Type de revalorisation"
879 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 880
881 def __unicode__(self):
6e4600ef 882 return u'%s' % (self.nom)
883
d6985a3a 884class Service(AUFMetadata):
6e4600ef 885 """Unité administrative où les Postes sont rattachés.
886 """
887 nom = models.CharField(max_length=255)
9afaa55e 888
889 class Meta:
890 ordering = ['nom']
c1195471
OL
891 verbose_name = u"Service"
892 verbose_name_plural = u"Services"
e9bbd6ba 893
6e4600ef 894 def __unicode__(self):
895 return u'%s' % (self.nom)
896
e9bbd6ba 897
898TYPE_ORGANISME_CHOICES = (
899 ('MAD', 'Mise à disposition'),
900 ('DET', 'Détachement'),
901)
902
d6985a3a 903class OrganismeBstg(AUFMetadata):
6e4600ef 904 """Organisation d'où provient un Employe mis à disposition (MAD) de
905 ou détaché (DET) à l'AUF à titre gratuit.
906
907 (BSTG = bien et service à titre gratuit.)
908 """
e9bbd6ba 909 nom = models.CharField(max_length=255)
910 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
6e4600ef 911 pays = models.ForeignKey(ref.Pays, to_field='code',
912 db_column='pays',
913 related_name='organismes_bstg',
914 null=True, blank=True)
9afaa55e 915
916 class Meta:
917 ordering = ['type', 'nom']
c1195471
OL
918 verbose_name = u"Organisme BSTG"
919 verbose_name_plural = u"Organismes BSTG"
9afaa55e 920
6e4600ef 921 def __unicode__(self):
8c1ae2b3 922 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 923
d6985a3a 924class Statut(AUFMetadata):
6e4600ef 925 """Statut de l'Employe dans le cadre d'un Dossier particulier.
926 """
9afaa55e 927 # Identification
e9bbd6ba 928 code = models.CharField(max_length=25, unique=True)
929 nom = models.CharField(max_length=255)
e9bbd6ba 930
6e4600ef 931 class Meta:
932 ordering = ['code']
c1195471
OL
933 verbose_name = u"Statut d'employé"
934 verbose_name_plural = u"Statuts d'employé"
6e4600ef 935
9afaa55e 936 def __unicode__(self):
937 return u'%s : %s' % (self.code, self.nom)
938
83b7692b 939
e9bbd6ba 940TYPE_CLASSEMENT_CHOICES = (
6e4600ef 941 ('S', 'S -Soutien'),
942 ('T', 'T - Technicien'),
943 ('P', 'P - Professionel'),
944 ('C', 'C - Cadre'),
945 ('D', 'D - Direction'),
946 ('SO', 'SO - Sans objet [expatriés]'),
947 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 948)
83b7692b 949
6e7c919b 950
d6985a3a 951class Classement_(AUFMetadata):
6e4600ef 952 """Éléments de classement de la
953 "Grille générique de classement hiérarchique".
954
955 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
956 classement dans la grille. Le classement donne le coefficient utilisé dans:
957
958 salaire de base = coefficient * valeur du point de l'Implantation du Poste
959 """
9afaa55e 960 # Identification
e9bbd6ba 961 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
c1195471
OL
962 echelon = models.IntegerField(verbose_name = u"Échelon")
963 degre = models.IntegerField(verbose_name = u"Degré")
964 coefficient = models.FloatField(default=0, verbose_name = u"Coéfficient",
8277a35b 965 null=True)
9afaa55e 966 # Méta
6e4600ef 967 # annee # au lieu de date_debut et date_fin
968 commentaire = models.TextField(null=True, blank=True)
969
970 class Meta:
6e7c919b 971 abstract = True
6e4600ef 972 ordering = ['type','echelon','degre','coefficient']
c1195471
OL
973 verbose_name = u"Classement"
974 verbose_name_plural = u"Classements"
e9bbd6ba 975
976 def __unicode__(self):
977 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
978 self.coefficient)
979
6e7c919b
NC
980class Classement(Classement_):
981 __doc__ = Classement_.__doc__
982
983
d6985a3a 984class TauxChange_(AUFMetadata):
7abc6d45 985 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 986 pour chaque année budgétaire.
7abc6d45 987 """
9afaa55e 988 # Identification
8c1ae2b3 989 devise = models.ForeignKey('Devise', db_column='devise',
6e4600ef 990 related_name='+')
c1195471
OL
991 annee = models.IntegerField(verbose_name = u"Année")
992 taux = models.FloatField(verbose_name = u"Taux vers l'euro")
6e7c919b 993
6e4600ef 994 class Meta:
6e7c919b 995 abstract = True
8c1ae2b3 996 ordering = ['-annee', 'devise__code']
c1195471
OL
997 verbose_name = u"Taux de change"
998 verbose_name_plural = u"Taux de change"
6e4600ef 999
1000 def __unicode__(self):
8c1ae2b3 1001 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1002
6e7c919b
NC
1003
1004class TauxChange(TauxChange_):
1005 __doc__ = TauxChange_.__doc__
1006
701f3bea
OL
1007class ValeurPointManager(NoDeleteManager):
1008 def get_query_set(self):
1009 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
1010
6e7c919b 1011
d6985a3a 1012class ValeurPoint_(AUFMetadata):
6e4600ef 1013 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1014 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1015 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1016
1017 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1018 """
701f3bea
OL
1019
1020 objects = ValeurPointManager()
1021
8277a35b
NC
1022 valeur = models.FloatField(null=True)
1023 devise = models.ForeignKey('Devise', db_column='devise', null=True,
6e4600ef 1024 related_name='+', default=5)
83b7692b 1025 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1026 db_column='implantation',
1027 related_name='%(app_label)s_valeur_point')
9afaa55e 1028 # Méta
e9bbd6ba 1029 annee = models.IntegerField()
9afaa55e 1030
6e4600ef 1031 class Meta:
701f3bea 1032 ordering = ['-annee', 'implantation__nom']
6e7c919b 1033 abstract = True
c1195471
OL
1034 verbose_name = u"Valeur du point"
1035 verbose_name_plural = u"Valeurs du point"
6e0bbb73 1036
e9d7483c 1037 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
ee23ecbc
NC
1038 def get_tauxchange_courant(self):
1039 """
1040 Recherche le taux courant associé à la valeur d'un point.
1041 Tous les taux de l'année courante sont chargés, pour optimiser un
1042 affichage en liste. (On pourrait probablement améliorer le manager pour
1043 lui greffer le taux courant sous forme de JOIN)
1044 """
1045 for tauxchange in self.tauxchange:
1046 if tauxchange.implantation_id == self.implantation_id:
1047 return tauxchange
1048 return None
1049
9afaa55e 1050 def __unicode__(self):
8c1ae2b3 1051 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
6e7c919b
NC
1052
1053
1054class ValeurPoint(ValeurPoint_):
1055 __doc__ = ValeurPoint_.__doc__
1056
e9bbd6ba 1057
d6985a3a 1058class Devise(AUFMetadata):
6e4600ef 1059 """Devise monétaire.
1060 """
e9bbd6ba 1061 code = models.CharField(max_length=10, unique=True)
1062 nom = models.CharField(max_length=255)
1063
6e4600ef 1064 class Meta:
1065 ordering = ['code']
c1195471
OL
1066 verbose_name = u"Devise"
1067 verbose_name_plural = u"Devises"
6e4600ef 1068
e9bbd6ba 1069 def __unicode__(self):
1070 return u'%s - %s' % (self.code, self.nom)
1071
d6985a3a 1072class TypeContrat(AUFMetadata):
6e4600ef 1073 """Type de contrat.
1074 """
e9bbd6ba 1075 nom = models.CharField(max_length=255)
6e4600ef 1076 nom_long = models.CharField(max_length=255)
49f9f116 1077
8c1ae2b3 1078 class Meta:
1079 ordering = ['nom']
c1195471
OL
1080 verbose_name = u"Type de contrat"
1081 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1082
9afaa55e 1083 def __unicode__(self):
1084 return u'%s' % (self.nom)
30be56d5 1085
2d4d2fcf 1086
1087### AUTRES
1088
d6985a3a 1089class ResponsableImplantation(AUFMetadata):
30be56d5 1090 """Le responsable d'une implantation.
1091 Anciennement géré sur le Dossier du responsable.
1092 """
6e4600ef 1093 employe = models.ForeignKey('Employe', db_column='employe',
1094 related_name='+',
1095 null=True, blank=True)
1096 implantation = models.ForeignKey(ref.Implantation,
1097 db_column='implantation', related_name='+',
1098 unique=True)
30be56d5 1099
1100 def __unicode__(self):
1101 return u'%s : %s' % (self.implantation, self.employe)
1102
1103 class Meta:
1104 ordering = ['implantation__nom']
8c1ae2b3 1105 verbose_name = "Responsable d'implantation"
1106 verbose_name_plural = "Responsables d'implantation"