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