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