ensure all apps have login_required decorator
[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
64721a83
PP
419class EmployeInactif(Employe):
420 class Meta:
421 proxy = True
422 ordering = ['nom_affichage','nom','prenom']
423 verbose_name = u"Employé inactif"
424 verbose_name_plural = u"Employés inactifs"
425
426
7abc6d45 427class EmployePiece(models.Model):
6e4600ef 428 """Documents relatifs à un employé.
7abc6d45 429 Ex.: CV...
430 """
f9e54d59 431 employe = models.ForeignKey('Employe', db_column='employe')
8c1ae2b3 432 nom = models.CharField(verbose_name="Nom", max_length=255)
433 fichier = models.FileField(verbose_name="Fichier",
5ea6b5bb 434 upload_to=employe_piece_dispatch,
7abc6d45 435 storage=storage_prive)
436
6e4600ef 437 class Meta:
438 ordering = ['nom']
f9e54d59
PP
439 verbose_name = u"Employé pièce"
440 verbose_name_plural = u"Employé pièces"
441
6e4600ef 442 def __unicode__(self):
443 return u'%s' % (self.nom)
444
07b40eda 445class EmployeCommentaire(Commentaire):
8c1ae2b3 446 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 447 related_name='+')
9afaa55e 448
b343eb3d
PP
449 class Meta:
450 verbose_name = u"Employé commentaire"
451 verbose_name_plural = u"Employé commentaires"
452
2d4d2fcf 453
e9bbd6ba 454LIEN_PARENTE_CHOICES = (
455 ('Conjoint', 'Conjoint'),
456 ('Conjointe', 'Conjointe'),
457 ('Fille', 'Fille'),
458 ('Fils', 'Fils'),
459)
460
d6985a3a 461class AyantDroit(AUFMetadata):
6e4600ef 462 """Personne en relation avec un Employe.
463 """
9afaa55e 464 # Identification
e9bbd6ba 465 nom = models.CharField(max_length=255)
8c1ae2b3 466 prenom = models.CharField(max_length=255,
c1195471 467 verbose_name = u"Prénom",)
6e4600ef 468 nom_affichage = models.CharField(max_length=255,
c1195471 469 verbose_name = u"Nom d'affichage",
6e4600ef 470 null=True, blank=True)
2d4d2fcf 471 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 472 db_column='nationalite',
8c1ae2b3 473 related_name='ayantdroits_nationalite',
c1195471 474 verbose_name = u"Nationalité")
6e4600ef 475 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 476 verbose_name = u"Date de naissance",
25037368 477 validators=[validate_date_passee],
6e4600ef 478 null=True, blank=True)
2d4d2fcf 479 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 480
9afaa55e 481 # Relation
482 employe = models.ForeignKey('Employe', db_column='employe',
8c1ae2b3 483 related_name='ayantdroits',
c1195471 484 verbose_name = u"Employé")
6e4600ef 485 lien_parente = models.CharField(max_length=10,
486 choices=LIEN_PARENTE_CHOICES,
c1195471 487 verbose_name = u"Lien de parenté",
6e4600ef 488 null=True, blank=True)
489
490 class Meta:
491 ordering = ['nom_affichage']
c1195471
OL
492 verbose_name = u"Ayant droit"
493 verbose_name_plural = u"Ayants droit"
8c1ae2b3 494
6e4600ef 495 def __unicode__(self):
8c1ae2b3 496 return u'%s' % (self.get_nom())
497
498 def get_nom(self):
499 nom_affichage = self.nom_affichage
500 if not nom_affichage:
501 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
502 return nom_affichage
83b7692b 503
aff1a4c6
PP
504 prefix_implantation = "employe__dossiers__poste__implantation__region"
505 def get_regions(self):
506 regions = []
507 for d in self.employe.dossiers.all():
508 regions.append(d.poste.implantation.region)
509 return regions
510
511
07b40eda 512class AyantDroitCommentaire(Commentaire):
8c1ae2b3 513 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
6e4600ef 514 related_name='+')
83b7692b 515
2d4d2fcf 516
83b7692b 517### DOSSIER
518
519STATUT_RESIDENCE_CHOICES = (
520 ('local', 'Local'),
521 ('expat', 'Expatrié'),
522)
523
524COMPTE_COMPTA_CHOICES = (
525 ('coda', 'CODA'),
526 ('scs', 'SCS'),
527 ('aucun', 'Aucun'),
528)
529
d6985a3a 530class Dossier_(AUFMetadata):
6e4600ef 531 """Le Dossier regroupe les informations relatives à l'occupation
532 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
533 par un Employe.
534
535 Plusieurs Contrats peuvent être associés au Dossier.
536 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
537 lequel aucun Dossier n'existe est un poste vacant.
538 """
83b7692b 539 # Identification
6e4600ef 540 employe = models.ForeignKey('Employe', db_column='employe',
d04d084c 541 related_name='dossiers',
bf4f5c77 542 verbose_name=u"Employé")
63e17dff
PP
543 # TODO: OneToOne ??
544 poste = models.ForeignKey('Poste', db_column='poste', related_name='dossiers')
8277a35b
NC
545 statut = models.ForeignKey('Statut', related_name='+', default=3,
546 null=True)
83b7692b 547 organisme_bstg = models.ForeignKey('OrganismeBstg',
6e4600ef 548 db_column='organisme_bstg',
549 related_name='+',
c1195471 550 verbose_name = u"Organisme",
8c1ae2b3 551 help_text="Si détaché (DET) ou \
6e4600ef 552 mis à disposition (MAD), \
553 préciser l'organisme.",
554 null=True, blank=True)
555
83b7692b 556 # Recrutement
2d4d2fcf 557 remplacement = models.BooleanField(default=False)
7c182958
PP
558 remplacement_de = models.ForeignKey('self', related_name='+',
559 null=True, blank=True)
83b7692b 560 statut_residence = models.CharField(max_length=10, default='local',
c1195471 561 verbose_name = u"Statut", null=True,
2d4d2fcf 562 choices=STATUT_RESIDENCE_CHOICES)
83b7692b 563
564 # Rémunération
6e4600ef 565 classement = models.ForeignKey('Classement', db_column='classement',
566 related_name='+',
567 null=True, blank=True)
8277a35b 568 regime_travail = models.DecimalField(max_digits=12, null=True,
2d4d2fcf 569 decimal_places=2,
570 default=REGIME_TRAVAIL_DEFAULT,
c1195471 571 verbose_name = u"Régime de travail",
8c1ae2b3 572 help_text="% du temps complet")
83b7692b 573 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
8277a35b 574 decimal_places=2, null=True,
2d4d2fcf 575 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
c1195471 576 verbose_name = u"Nb. heures par semaine")
7abc6d45 577
578 # Occupation du Poste par cet Employe (anciennement "mandat")
c1195471 579 date_debut = models.DateField(verbose_name = u"Date de début d'occupation \
6e4600ef 580 de poste",
581 help_text=HELP_TEXT_DATE)
c1195471 582 date_fin = models.DateField(verbose_name = u"Date de fin d'occupation \
6e4600ef 583 de poste",
2d4d2fcf 584 help_text=HELP_TEXT_DATE,
6e4600ef 585 null=True, blank=True)
e9bbd6ba 586
2d4d2fcf 587 # Comptes
588 # TODO?
83b7692b 589
6e4600ef 590 class Meta:
37868f0b 591 abstract = True
49449367 592 ordering = ['employe__nom', ]
3f5f3898 593 verbose_name = u"Dossier"
8c1ae2b3 594 verbose_name_plural = "Dossiers"
6e4600ef 595
65f9fac8 596 def salaire_theorique(self):
597 annee = date.today().year
598 coeff = self.classement.coefficient
599 implantation = self.poste.implantation
600 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
601
602 montant = coeff * point.valeur
603 devise = point.devise
604 return {'montant':montant, 'devise':devise}
605
83b7692b 606 def __unicode__(self):
8c1ae2b3 607 poste = self.poste.nom
608 if self.employe.genre == 'F':
609 poste = self.poste.nom_feminin
610 return u'%s - %s' % (self.employe, poste)
83b7692b 611
aff1a4c6
PP
612 prefix_implantation = "poste__implantation__region"
613 def get_regions(self):
614 return [self.poste.implantation.region]
615
37868f0b
NC
616
617class Dossier(Dossier_):
618 __doc__ = Dossier_.__doc__
619
620
64721a83 621class DossierInactif(Dossier):
6ad01c0a 622
64721a83
PP
623 class Meta:
624 proxy = True
625 ordering = ['employe__nom', ]
626 verbose_name = u"Dossier inactif"
627 verbose_name_plural = u"Dossiers inactifs"
628
629
83b7692b 630class DossierPiece(models.Model):
7abc6d45 631 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
632 Ex.: Lettre de motivation.
633 """
8c1ae2b3 634 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 635 related_name='+')
c1195471
OL
636 nom = models.CharField(verbose_name = u"Nom", max_length=255)
637 fichier = models.FileField(verbose_name = u"Fichier",
83b7692b 638 upload_to=dossier_piece_dispatch,
639 storage=storage_prive)
640
6e4600ef 641 class Meta:
642 ordering = ['nom']
643
644 def __unicode__(self):
645 return u'%s' % (self.nom)
646
07b40eda 647class DossierCommentaire(Commentaire):
8c1ae2b3 648 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 649 related_name='+')
83b7692b 650
1d0f4eef
OL
651class DossierComparaison(models.Model):
652 """
653 Photo d'une comparaison salariale au moment de l'embauche.
654 """
655 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
8c6269cc 656 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
1d0f4eef
OL
657 poste = models.CharField(max_length=255, null=True, blank=True)
658 personne = models.CharField(max_length=255, null=True, blank=True)
659 montant = models.IntegerField(null=True)
93494cf1 660 devise = models.ForeignKey('Devise', default=5, related_name='+', null=True, blank=True)
1d0f4eef
OL
661
662 def taux_devise(self):
663 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
664 if len(liste_taux) == 0:
665 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
666 else:
667 return liste_taux[0].taux
668
669 def montant_euros(self):
670 return round(float(self.montant) * float(self.taux_devise()), 2)
671
2d4d2fcf 672
07b40eda 673### RÉMUNÉRATION
e9bbd6ba 674
d6985a3a 675class RemunerationMixin(AUFMetadata):
9afaa55e 676 # Identification
6e7c919b
NC
677 dossier = models.ForeignKey('Dossier', db_column='dossier',
678 related_name='%(app_label)s_%(class)s_remunerations')
83b7692b 679 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 680 related_name='+',
c1195471 681 verbose_name = u"Type de rémunération")
7abc6d45 682 type_revalorisation = models.ForeignKey('TypeRevalorisation',
683 db_column='type_revalorisation',
6e4600ef 684 related_name='+',
c1195471 685 verbose_name = u"Type de revalorisation",
7abc6d45 686 null=True, blank=True)
50fa9bc1 687 montant = models.FloatField(null=True, blank=True,
6e4600ef 688 default=0)
2d4d2fcf 689 # Annuel (12 mois, 52 semaines, 364 jours?)
c589d980 690 devise = models.ForeignKey('Devise', to_field='id',
6e4600ef 691 db_column='devise', related_name='+',
692 default=5)
2d4d2fcf 693 # commentaire = precision
694 commentaire = models.CharField(max_length=255, null=True, blank=True)
695 # date_debut = anciennement date_effectif
8c1ae2b3 696 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 697 verbose_name = u"Date de début",
6e4600ef 698 null=True, blank=True)
699 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 700 verbose_name = u"Date de fin",
6e4600ef 701 null=True, blank=True)
83b7692b 702
2d4d2fcf 703 class Meta:
704 abstract = True
6e4600ef 705 ordering = ['type__nom', '-date_fin']
706
707 def __unicode__(self):
708 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
2d4d2fcf 709
6e7c919b 710class Remuneration_(RemunerationMixin):
2d4d2fcf 711 """Structure de rémunération (données budgétaires) en situation normale
712 pour un Dossier. Si un Evenement existe, utiliser la structure de
713 rémunération EvenementRemuneration de cet événement.
714 """
83b7692b 715
716 def montant_mois(self):
717 return round(self.montant / 12, 2)
718
719 def taux_devise(self):
720 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
721
722 def montant_euro(self):
723 return round(float(self.montant) / float(self.taux_devise()), 2)
724
725 def montant_euro_mois(self):
726 return round(self.montant_euro() / 12, 2)
9afaa55e 727
728 def __unicode__(self):
729 try:
730 devise = self.devise.code
731 except:
732 devise = "???"
733 return "%s %s" % (self.montant, devise)
83b7692b 734
6e7c919b
NC
735 class Meta:
736 abstract = True
c1195471
OL
737 verbose_name = u"Rémunération"
738 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
739
740
741class Remuneration(Remuneration_):
742 __doc__ = Remuneration_.__doc__
743
2d4d2fcf 744
745### CONTRATS
c41b7fcc
OL
746
747class ContratManager(NoDeleteManager):
748 def get_query_set(self):
749 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
750
2d4d2fcf 751
d6985a3a 752class Contrat(AUFMetadata):
2d4d2fcf 753 """Document juridique qui encadre la relation de travail d'un Employe
754 pour un Poste particulier. Pour un Dossier (qui documente cette
755 relation de travail) plusieurs contrats peuvent être associés.
756 """
c41b7fcc
OL
757
758 objects = ContratManager()
759
6e4600ef 760 dossier = models.ForeignKey('Dossier', db_column='dossier',
65f9fac8 761 related_name='contrats')
6e4600ef 762 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 763 related_name='+',
c1195471 764 verbose_name = u"Type de contrat")
8c1ae2b3 765 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 766 verbose_name = u"Date de début")
6e4600ef 767 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 768 verbose_name = u"Date de fin",
6e4600ef 769 null=True, blank=True)
770
771 class Meta:
772 ordering = ['dossier__employe__nom_affichage']
c1195471
OL
773 verbose_name = u"Contrat"
774 verbose_name_plural = u"Contrats"
6e4600ef 775
776 def __unicode__(self):
8c1ae2b3 777 return u'%s - %s' % (self.dossier, self.id)
6e4600ef 778
779# TODO? class ContratPiece(models.Model):
2d4d2fcf 780
781
782### ÉVÉNEMENTS
783
d6985a3a 784class Evenement_(AUFMetadata):
6e4600ef 785 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
786 d'un Dossier qui vient altérer des informations normales liées à un Dossier
787 (ex.: la Remuneration).
788
789 Ex.: congé de maternité, maladie...
790
791 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
792 différent et une rémunération en conséquence. On souhaite toutefois
793 conserver le Dossier intact afin d'éviter une re-saisie des données lors
794 du retour à la normale.
795 """
8c1ae2b3 796 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 797 related_name='+')
798 nom = models.CharField(max_length=255)
8c1ae2b3 799 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 800 verbose_name = u"Date de début")
8c1ae2b3 801 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 802 verbose_name = u"Date de fin",
6e4600ef 803 null=True, blank=True)
6e7c919b 804
6e4600ef 805 class Meta:
6e7c919b 806 abstract = True
6e4600ef 807 ordering = ['nom']
c1195471
OL
808 verbose_name = u"Évènement"
809 verbose_name_plural = u"Évènements"
6e4600ef 810
811 def __unicode__(self):
812 return u'%s' % (self.nom)
6e7c919b
NC
813
814
815class Evenement(Evenement_):
816 __doc__ = Evenement_.__doc__
817
2d4d2fcf 818
6e7c919b 819class EvenementRemuneration_(RemunerationMixin):
6e4600ef 820 """Structure de rémunération liée à un Evenement qui remplace
821 temporairement la Remuneration normale d'un Dossier, pour toute la durée
822 de l'Evenement.
823 """
824 evenement = models.ForeignKey("Evenement", db_column='evenement',
8c1ae2b3 825 related_name='+',
c1195471 826 verbose_name = u"Évènement")
8c1ae2b3 827 # TODO : le champ dossier hérité de Remuneration doit être dérivé
828 # de l'Evenement associé
83b7692b 829
6e7c919b
NC
830 class Meta:
831 abstract = True
8c1ae2b3 832 ordering = ['evenement', 'type__nom', '-date_fin']
c1195471
OL
833 verbose_name = u"Évènement - rémunération"
834 verbose_name_plural = u"Évènements - rémunérations"
6e7c919b
NC
835
836
837class EvenementRemuneration(EvenementRemuneration_):
838 __doc__ = EvenementRemuneration_.__doc__
83b7692b 839
f31ddfa0
NC
840 class Meta:
841 abstract = True
842
843
844class EvenementRemuneration(EvenementRemuneration_):
845 __doc__ = EvenementRemuneration_.__doc__
846
83b7692b 847
848### RÉFÉRENCES RH
849
d6985a3a 850class FamilleEmploi(AUFMetadata):
6e4600ef 851 """Catégorie utilisée dans la gestion des Postes.
852 Catégorie supérieure à TypePoste.
853 """
e9bbd6ba 854 nom = models.CharField(max_length=255)
6e4600ef 855
8c1ae2b3 856 class Meta:
857 ordering = ['nom']
c1195471
OL
858 verbose_name = u"Famille d'emploi"
859 verbose_name_plural = u"Familles d'emploi"
8c1ae2b3 860
6e4600ef 861 def __unicode__(self):
862 return u'%s' % (self.nom)
e9bbd6ba 863
d6985a3a 864class TypePoste(AUFMetadata):
6e4600ef 865 """Catégorie de Poste.
866 """
e9bbd6ba 867 nom = models.CharField(max_length=255)
8c1ae2b3 868 nom_feminin = models.CharField(max_length=255,
c1195471 869 verbose_name = u"Nom féminin")
6e4600ef 870
8c1ae2b3 871 is_responsable = models.BooleanField(default=False,
c1195471 872 verbose_name = u"Poste de responsabilité")
9afaa55e 873 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 874 db_column='famille_emploi',
8c1ae2b3 875 related_name='+',
c1195471 876 verbose_name = u"Famille d'emploi")
e9bbd6ba 877
6e4600ef 878 class Meta:
879 ordering = ['nom']
c1195471
OL
880 verbose_name = u"Type de poste"
881 verbose_name_plural = u"Types de poste"
6e4600ef 882
e9bbd6ba 883 def __unicode__(self):
6e4600ef 884 return u'%s' % (self.nom)
e9bbd6ba 885
886
887TYPE_PAIEMENT_CHOICES = (
888 ('Régulier', 'Régulier'),
889 ('Ponctuel', 'Ponctuel'),
890)
891
892NATURE_REMUNERATION_CHOICES = (
893 ('Accessoire', 'Accessoire'),
894 ('Charges', 'Charges'),
895 ('Indemnité', 'Indemnité'),
7abc6d45 896 ('RAS', 'Rémunération autre source'),
e9bbd6ba 897 ('Traitement', 'Traitement'),
898)
899
d6985a3a 900class TypeRemuneration(AUFMetadata):
6e4600ef 901 """Catégorie de Remuneration.
902 """
e9bbd6ba 903 nom = models.CharField(max_length=255)
9afaa55e 904 type_paiement = models.CharField(max_length=30,
8c1ae2b3 905 choices=TYPE_PAIEMENT_CHOICES,
c1195471 906 verbose_name = u"Type de paiement")
9afaa55e 907 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 908 choices=NATURE_REMUNERATION_CHOICES,
c1195471 909 verbose_name = u"Nature de la rémunération")
8c1ae2b3 910
911 class Meta:
912 ordering = ['nom']
c1195471
OL
913 verbose_name = u"Type de rémunération"
914 verbose_name_plural = u"Types de rémunération"
9afaa55e 915
916 def __unicode__(self):
6e4600ef 917 return u'%s' % (self.nom)
e9bbd6ba 918
d6985a3a 919class TypeRevalorisation(AUFMetadata):
7abc6d45 920 """Justification du changement de la Remuneration.
6e4600ef 921 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 922 """
e9bbd6ba 923 nom = models.CharField(max_length=255)
8c1ae2b3 924
925 class Meta:
926 ordering = ['nom']
c1195471
OL
927 verbose_name = u"Type de revalorisation"
928 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 929
930 def __unicode__(self):
6e4600ef 931 return u'%s' % (self.nom)
932
d6985a3a 933class Service(AUFMetadata):
6e4600ef 934 """Unité administrative où les Postes sont rattachés.
935 """
936 nom = models.CharField(max_length=255)
9afaa55e 937
938 class Meta:
939 ordering = ['nom']
c1195471
OL
940 verbose_name = u"Service"
941 verbose_name_plural = u"Services"
e9bbd6ba 942
6e4600ef 943 def __unicode__(self):
944 return u'%s' % (self.nom)
945
e9bbd6ba 946
947TYPE_ORGANISME_CHOICES = (
948 ('MAD', 'Mise à disposition'),
949 ('DET', 'Détachement'),
950)
951
d6985a3a 952class OrganismeBstg(AUFMetadata):
6e4600ef 953 """Organisation d'où provient un Employe mis à disposition (MAD) de
954 ou détaché (DET) à l'AUF à titre gratuit.
955
956 (BSTG = bien et service à titre gratuit.)
957 """
e9bbd6ba 958 nom = models.CharField(max_length=255)
959 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
6e4600ef 960 pays = models.ForeignKey(ref.Pays, to_field='code',
961 db_column='pays',
962 related_name='organismes_bstg',
963 null=True, blank=True)
9afaa55e 964
965 class Meta:
966 ordering = ['type', 'nom']
c1195471
OL
967 verbose_name = u"Organisme BSTG"
968 verbose_name_plural = u"Organismes BSTG"
9afaa55e 969
6e4600ef 970 def __unicode__(self):
8c1ae2b3 971 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 972
aff1a4c6
PP
973 prefix_implantation = "pays__region"
974 def get_regions(self):
975 return [self.pays.region]
976
977
d6985a3a 978class Statut(AUFMetadata):
6e4600ef 979 """Statut de l'Employe dans le cadre d'un Dossier particulier.
980 """
9afaa55e 981 # Identification
a122050d 982 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 983 nom = models.CharField(max_length=255)
e9bbd6ba 984
6e4600ef 985 class Meta:
986 ordering = ['code']
c1195471
OL
987 verbose_name = u"Statut d'employé"
988 verbose_name_plural = u"Statuts d'employé"
6e4600ef 989
9afaa55e 990 def __unicode__(self):
991 return u'%s : %s' % (self.code, self.nom)
992
83b7692b 993
e9bbd6ba 994TYPE_CLASSEMENT_CHOICES = (
6e4600ef 995 ('S', 'S -Soutien'),
996 ('T', 'T - Technicien'),
997 ('P', 'P - Professionel'),
998 ('C', 'C - Cadre'),
999 ('D', 'D - Direction'),
1000 ('SO', 'SO - Sans objet [expatriés]'),
1001 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 1002)
83b7692b 1003
6e7c919b 1004
d6985a3a 1005class Classement_(AUFMetadata):
6e4600ef 1006 """Éléments de classement de la
1007 "Grille générique de classement hiérarchique".
1008
1009 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1010 classement dans la grille. Le classement donne le coefficient utilisé dans:
1011
1012 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1013 """
9afaa55e 1014 # Identification
e9bbd6ba 1015 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
c1195471
OL
1016 echelon = models.IntegerField(verbose_name = u"Échelon")
1017 degre = models.IntegerField(verbose_name = u"Degré")
1018 coefficient = models.FloatField(default=0, verbose_name = u"Coéfficient",
8277a35b 1019 null=True)
9afaa55e 1020 # Méta
6e4600ef 1021 # annee # au lieu de date_debut et date_fin
1022 commentaire = models.TextField(null=True, blank=True)
1023
1024 class Meta:
6e7c919b 1025 abstract = True
6e4600ef 1026 ordering = ['type','echelon','degre','coefficient']
c1195471
OL
1027 verbose_name = u"Classement"
1028 verbose_name_plural = u"Classements"
e9bbd6ba 1029
1030 def __unicode__(self):
1031 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
1032 self.coefficient)
1033
6e7c919b
NC
1034class Classement(Classement_):
1035 __doc__ = Classement_.__doc__
1036
1037
d6985a3a 1038class TauxChange_(AUFMetadata):
7abc6d45 1039 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 1040 pour chaque année budgétaire.
7abc6d45 1041 """
9afaa55e 1042 # Identification
8d3e2fff 1043 devise = models.ForeignKey('Devise', db_column='devise')
c1195471
OL
1044 annee = models.IntegerField(verbose_name = u"Année")
1045 taux = models.FloatField(verbose_name = u"Taux vers l'euro")
6e7c919b 1046
6e4600ef 1047 class Meta:
6e7c919b 1048 abstract = True
8c1ae2b3 1049 ordering = ['-annee', 'devise__code']
c1195471
OL
1050 verbose_name = u"Taux de change"
1051 verbose_name_plural = u"Taux de change"
6e4600ef 1052
1053 def __unicode__(self):
8c1ae2b3 1054 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1055
6e7c919b
NC
1056
1057class TauxChange(TauxChange_):
1058 __doc__ = TauxChange_.__doc__
1059
701f3bea
OL
1060class ValeurPointManager(NoDeleteManager):
1061 def get_query_set(self):
1062 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
1063
6e7c919b 1064
d6985a3a 1065class ValeurPoint_(AUFMetadata):
6e4600ef 1066 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1067 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1068 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1069
1070 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1071 """
701f3bea
OL
1072
1073 objects = ValeurPointManager()
1074
8277a35b
NC
1075 valeur = models.FloatField(null=True)
1076 devise = models.ForeignKey('Devise', db_column='devise', null=True,
6e4600ef 1077 related_name='+', default=5)
83b7692b 1078 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1079 db_column='implantation',
1080 related_name='%(app_label)s_valeur_point')
9afaa55e 1081 # Méta
e9bbd6ba 1082 annee = models.IntegerField()
9afaa55e 1083
6e4600ef 1084 class Meta:
701f3bea 1085 ordering = ['-annee', 'implantation__nom']
6e7c919b 1086 abstract = True
c1195471
OL
1087 verbose_name = u"Valeur du point"
1088 verbose_name_plural = u"Valeurs du point"
6e0bbb73 1089
e9d7483c 1090 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
ee23ecbc
NC
1091 def get_tauxchange_courant(self):
1092 """
1093 Recherche le taux courant associé à la valeur d'un point.
1094 Tous les taux de l'année courante sont chargés, pour optimiser un
1095 affichage en liste. (On pourrait probablement améliorer le manager pour
1096 lui greffer le taux courant sous forme de JOIN)
1097 """
1098 for tauxchange in self.tauxchange:
1099 if tauxchange.implantation_id == self.implantation_id:
1100 return tauxchange
1101 return None
1102
9afaa55e 1103 def __unicode__(self):
8c1ae2b3 1104 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
6e7c919b
NC
1105
1106
1107class ValeurPoint(ValeurPoint_):
1108 __doc__ = ValeurPoint_.__doc__
1109
e9bbd6ba 1110
d6985a3a 1111class Devise(AUFMetadata):
6e4600ef 1112 """Devise monétaire.
1113 """
e9bbd6ba 1114 code = models.CharField(max_length=10, unique=True)
1115 nom = models.CharField(max_length=255)
1116
6e4600ef 1117 class Meta:
1118 ordering = ['code']
c1195471
OL
1119 verbose_name = u"Devise"
1120 verbose_name_plural = u"Devises"
6e4600ef 1121
e9bbd6ba 1122 def __unicode__(self):
1123 return u'%s - %s' % (self.code, self.nom)
1124
d6985a3a 1125class TypeContrat(AUFMetadata):
6e4600ef 1126 """Type de contrat.
1127 """
e9bbd6ba 1128 nom = models.CharField(max_length=255)
6e4600ef 1129 nom_long = models.CharField(max_length=255)
49f9f116 1130
8c1ae2b3 1131 class Meta:
1132 ordering = ['nom']
c1195471
OL
1133 verbose_name = u"Type de contrat"
1134 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1135
9afaa55e 1136 def __unicode__(self):
1137 return u'%s' % (self.nom)
30be56d5 1138
2d4d2fcf 1139
1140### AUTRES
1141
d6985a3a 1142class ResponsableImplantation(AUFMetadata):
30be56d5 1143 """Le responsable d'une implantation.
1144 Anciennement géré sur le Dossier du responsable.
1145 """
6e4600ef 1146 employe = models.ForeignKey('Employe', db_column='employe',
1147 related_name='+',
1148 null=True, blank=True)
1149 implantation = models.ForeignKey(ref.Implantation,
1150 db_column='implantation', related_name='+',
1151 unique=True)
30be56d5 1152
1153 def __unicode__(self):
1154 return u'%s : %s' % (self.implantation, self.employe)
1155
1156 class Meta:
1157 ordering = ['implantation__nom']
8c1ae2b3 1158 verbose_name = "Responsable d'implantation"
1159 verbose_name_plural = "Responsables d'implantation"