Correction accent et widget pour employé ajout pièce
[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
PP
621class DossierInactif(Dossier):
622 class Meta:
623 proxy = True
624 ordering = ['employe__nom', ]
625 verbose_name = u"Dossier inactif"
626 verbose_name_plural = u"Dossiers inactifs"
627
628
83b7692b 629class DossierPiece(models.Model):
7abc6d45 630 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
631 Ex.: Lettre de motivation.
632 """
8c1ae2b3 633 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 634 related_name='+')
c1195471
OL
635 nom = models.CharField(verbose_name = u"Nom", max_length=255)
636 fichier = models.FileField(verbose_name = u"Fichier",
83b7692b 637 upload_to=dossier_piece_dispatch,
638 storage=storage_prive)
639
6e4600ef 640 class Meta:
641 ordering = ['nom']
642
643 def __unicode__(self):
644 return u'%s' % (self.nom)
645
07b40eda 646class DossierCommentaire(Commentaire):
8c1ae2b3 647 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 648 related_name='+')
83b7692b 649
1d0f4eef
OL
650class DossierComparaison(models.Model):
651 """
652 Photo d'une comparaison salariale au moment de l'embauche.
653 """
654 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
8c6269cc 655 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
1d0f4eef
OL
656 poste = models.CharField(max_length=255, null=True, blank=True)
657 personne = models.CharField(max_length=255, null=True, blank=True)
658 montant = models.IntegerField(null=True)
93494cf1 659 devise = models.ForeignKey('Devise', default=5, related_name='+', null=True, blank=True)
1d0f4eef
OL
660
661 def taux_devise(self):
662 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
663 if len(liste_taux) == 0:
664 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
665 else:
666 return liste_taux[0].taux
667
668 def montant_euros(self):
669 return round(float(self.montant) * float(self.taux_devise()), 2)
670
2d4d2fcf 671
07b40eda 672### RÉMUNÉRATION
e9bbd6ba 673
d6985a3a 674class RemunerationMixin(AUFMetadata):
9afaa55e 675 # Identification
6e7c919b
NC
676 dossier = models.ForeignKey('Dossier', db_column='dossier',
677 related_name='%(app_label)s_%(class)s_remunerations')
83b7692b 678 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 679 related_name='+',
c1195471 680 verbose_name = u"Type de rémunération")
7abc6d45 681 type_revalorisation = models.ForeignKey('TypeRevalorisation',
682 db_column='type_revalorisation',
6e4600ef 683 related_name='+',
c1195471 684 verbose_name = u"Type de revalorisation",
7abc6d45 685 null=True, blank=True)
50fa9bc1 686 montant = models.FloatField(null=True, blank=True,
6e4600ef 687 default=0)
2d4d2fcf 688 # Annuel (12 mois, 52 semaines, 364 jours?)
c589d980 689 devise = models.ForeignKey('Devise', to_field='id',
6e4600ef 690 db_column='devise', related_name='+',
691 default=5)
2d4d2fcf 692 # commentaire = precision
693 commentaire = models.CharField(max_length=255, null=True, blank=True)
694 # date_debut = anciennement date_effectif
8c1ae2b3 695 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 696 verbose_name = u"Date de début",
6e4600ef 697 null=True, blank=True)
698 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 699 verbose_name = u"Date de fin",
6e4600ef 700 null=True, blank=True)
83b7692b 701
2d4d2fcf 702 class Meta:
703 abstract = True
6e4600ef 704 ordering = ['type__nom', '-date_fin']
705
706 def __unicode__(self):
707 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
2d4d2fcf 708
6e7c919b 709class Remuneration_(RemunerationMixin):
2d4d2fcf 710 """Structure de rémunération (données budgétaires) en situation normale
711 pour un Dossier. Si un Evenement existe, utiliser la structure de
712 rémunération EvenementRemuneration de cet événement.
713 """
83b7692b 714
715 def montant_mois(self):
716 return round(self.montant / 12, 2)
717
718 def taux_devise(self):
719 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
720
721 def montant_euro(self):
722 return round(float(self.montant) / float(self.taux_devise()), 2)
723
724 def montant_euro_mois(self):
725 return round(self.montant_euro() / 12, 2)
9afaa55e 726
727 def __unicode__(self):
728 try:
729 devise = self.devise.code
730 except:
731 devise = "???"
732 return "%s %s" % (self.montant, devise)
83b7692b 733
6e7c919b
NC
734 class Meta:
735 abstract = True
c1195471
OL
736 verbose_name = u"Rémunération"
737 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
738
739
740class Remuneration(Remuneration_):
741 __doc__ = Remuneration_.__doc__
742
2d4d2fcf 743
744### CONTRATS
c41b7fcc
OL
745
746class ContratManager(NoDeleteManager):
747 def get_query_set(self):
748 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
749
2d4d2fcf 750
d6985a3a 751class Contrat(AUFMetadata):
2d4d2fcf 752 """Document juridique qui encadre la relation de travail d'un Employe
753 pour un Poste particulier. Pour un Dossier (qui documente cette
754 relation de travail) plusieurs contrats peuvent être associés.
755 """
c41b7fcc
OL
756
757 objects = ContratManager()
758
6e4600ef 759 dossier = models.ForeignKey('Dossier', db_column='dossier',
65f9fac8 760 related_name='contrats')
6e4600ef 761 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 762 related_name='+',
c1195471 763 verbose_name = u"Type de contrat")
8c1ae2b3 764 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 765 verbose_name = u"Date de début")
6e4600ef 766 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 767 verbose_name = u"Date de fin",
6e4600ef 768 null=True, blank=True)
769
770 class Meta:
771 ordering = ['dossier__employe__nom_affichage']
c1195471
OL
772 verbose_name = u"Contrat"
773 verbose_name_plural = u"Contrats"
6e4600ef 774
775 def __unicode__(self):
8c1ae2b3 776 return u'%s - %s' % (self.dossier, self.id)
6e4600ef 777
778# TODO? class ContratPiece(models.Model):
2d4d2fcf 779
780
781### ÉVÉNEMENTS
782
d6985a3a 783class Evenement_(AUFMetadata):
6e4600ef 784 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
785 d'un Dossier qui vient altérer des informations normales liées à un Dossier
786 (ex.: la Remuneration).
787
788 Ex.: congé de maternité, maladie...
789
790 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
791 différent et une rémunération en conséquence. On souhaite toutefois
792 conserver le Dossier intact afin d'éviter une re-saisie des données lors
793 du retour à la normale.
794 """
8c1ae2b3 795 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 796 related_name='+')
797 nom = models.CharField(max_length=255)
8c1ae2b3 798 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 799 verbose_name = u"Date de début")
8c1ae2b3 800 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
c1195471 801 verbose_name = u"Date de fin",
6e4600ef 802 null=True, blank=True)
6e7c919b 803
6e4600ef 804 class Meta:
6e7c919b 805 abstract = True
6e4600ef 806 ordering = ['nom']
c1195471
OL
807 verbose_name = u"Évènement"
808 verbose_name_plural = u"Évènements"
6e4600ef 809
810 def __unicode__(self):
811 return u'%s' % (self.nom)
6e7c919b
NC
812
813
814class Evenement(Evenement_):
815 __doc__ = Evenement_.__doc__
816
2d4d2fcf 817
6e7c919b 818class EvenementRemuneration_(RemunerationMixin):
6e4600ef 819 """Structure de rémunération liée à un Evenement qui remplace
820 temporairement la Remuneration normale d'un Dossier, pour toute la durée
821 de l'Evenement.
822 """
823 evenement = models.ForeignKey("Evenement", db_column='evenement',
8c1ae2b3 824 related_name='+',
c1195471 825 verbose_name = u"Évènement")
8c1ae2b3 826 # TODO : le champ dossier hérité de Remuneration doit être dérivé
827 # de l'Evenement associé
83b7692b 828
6e7c919b
NC
829 class Meta:
830 abstract = True
8c1ae2b3 831 ordering = ['evenement', 'type__nom', '-date_fin']
c1195471
OL
832 verbose_name = u"Évènement - rémunération"
833 verbose_name_plural = u"Évènements - rémunérations"
6e7c919b
NC
834
835
836class EvenementRemuneration(EvenementRemuneration_):
837 __doc__ = EvenementRemuneration_.__doc__
83b7692b 838
f31ddfa0
NC
839 class Meta:
840 abstract = True
841
842
843class EvenementRemuneration(EvenementRemuneration_):
844 __doc__ = EvenementRemuneration_.__doc__
845
83b7692b 846
847### RÉFÉRENCES RH
848
d6985a3a 849class FamilleEmploi(AUFMetadata):
6e4600ef 850 """Catégorie utilisée dans la gestion des Postes.
851 Catégorie supérieure à TypePoste.
852 """
e9bbd6ba 853 nom = models.CharField(max_length=255)
6e4600ef 854
8c1ae2b3 855 class Meta:
856 ordering = ['nom']
c1195471
OL
857 verbose_name = u"Famille d'emploi"
858 verbose_name_plural = u"Familles d'emploi"
8c1ae2b3 859
6e4600ef 860 def __unicode__(self):
861 return u'%s' % (self.nom)
e9bbd6ba 862
d6985a3a 863class TypePoste(AUFMetadata):
6e4600ef 864 """Catégorie de Poste.
865 """
e9bbd6ba 866 nom = models.CharField(max_length=255)
8c1ae2b3 867 nom_feminin = models.CharField(max_length=255,
c1195471 868 verbose_name = u"Nom féminin")
6e4600ef 869
8c1ae2b3 870 is_responsable = models.BooleanField(default=False,
c1195471 871 verbose_name = u"Poste de responsabilité")
9afaa55e 872 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 873 db_column='famille_emploi',
8c1ae2b3 874 related_name='+',
c1195471 875 verbose_name = u"Famille d'emploi")
e9bbd6ba 876
6e4600ef 877 class Meta:
878 ordering = ['nom']
c1195471
OL
879 verbose_name = u"Type de poste"
880 verbose_name_plural = u"Types de poste"
6e4600ef 881
e9bbd6ba 882 def __unicode__(self):
6e4600ef 883 return u'%s' % (self.nom)
e9bbd6ba 884
885
886TYPE_PAIEMENT_CHOICES = (
887 ('Régulier', 'Régulier'),
888 ('Ponctuel', 'Ponctuel'),
889)
890
891NATURE_REMUNERATION_CHOICES = (
892 ('Accessoire', 'Accessoire'),
893 ('Charges', 'Charges'),
894 ('Indemnité', 'Indemnité'),
7abc6d45 895 ('RAS', 'Rémunération autre source'),
e9bbd6ba 896 ('Traitement', 'Traitement'),
897)
898
d6985a3a 899class TypeRemuneration(AUFMetadata):
6e4600ef 900 """Catégorie de Remuneration.
901 """
e9bbd6ba 902 nom = models.CharField(max_length=255)
9afaa55e 903 type_paiement = models.CharField(max_length=30,
8c1ae2b3 904 choices=TYPE_PAIEMENT_CHOICES,
c1195471 905 verbose_name = u"Type de paiement")
9afaa55e 906 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 907 choices=NATURE_REMUNERATION_CHOICES,
c1195471 908 verbose_name = u"Nature de la rémunération")
8c1ae2b3 909
910 class Meta:
911 ordering = ['nom']
c1195471
OL
912 verbose_name = u"Type de rémunération"
913 verbose_name_plural = u"Types de rémunération"
9afaa55e 914
915 def __unicode__(self):
6e4600ef 916 return u'%s' % (self.nom)
e9bbd6ba 917
d6985a3a 918class TypeRevalorisation(AUFMetadata):
7abc6d45 919 """Justification du changement de la Remuneration.
6e4600ef 920 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 921 """
e9bbd6ba 922 nom = models.CharField(max_length=255)
8c1ae2b3 923
924 class Meta:
925 ordering = ['nom']
c1195471
OL
926 verbose_name = u"Type de revalorisation"
927 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 928
929 def __unicode__(self):
6e4600ef 930 return u'%s' % (self.nom)
931
d6985a3a 932class Service(AUFMetadata):
6e4600ef 933 """Unité administrative où les Postes sont rattachés.
934 """
935 nom = models.CharField(max_length=255)
9afaa55e 936
937 class Meta:
938 ordering = ['nom']
c1195471
OL
939 verbose_name = u"Service"
940 verbose_name_plural = u"Services"
e9bbd6ba 941
6e4600ef 942 def __unicode__(self):
943 return u'%s' % (self.nom)
944
e9bbd6ba 945
946TYPE_ORGANISME_CHOICES = (
947 ('MAD', 'Mise à disposition'),
948 ('DET', 'Détachement'),
949)
950
d6985a3a 951class OrganismeBstg(AUFMetadata):
6e4600ef 952 """Organisation d'où provient un Employe mis à disposition (MAD) de
953 ou détaché (DET) à l'AUF à titre gratuit.
954
955 (BSTG = bien et service à titre gratuit.)
956 """
e9bbd6ba 957 nom = models.CharField(max_length=255)
958 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
6e4600ef 959 pays = models.ForeignKey(ref.Pays, to_field='code',
960 db_column='pays',
961 related_name='organismes_bstg',
962 null=True, blank=True)
9afaa55e 963
964 class Meta:
965 ordering = ['type', 'nom']
c1195471
OL
966 verbose_name = u"Organisme BSTG"
967 verbose_name_plural = u"Organismes BSTG"
9afaa55e 968
6e4600ef 969 def __unicode__(self):
8c1ae2b3 970 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 971
aff1a4c6
PP
972 prefix_implantation = "pays__region"
973 def get_regions(self):
974 return [self.pays.region]
975
976
d6985a3a 977class Statut(AUFMetadata):
6e4600ef 978 """Statut de l'Employe dans le cadre d'un Dossier particulier.
979 """
9afaa55e 980 # Identification
e9bbd6ba 981 code = models.CharField(max_length=25, unique=True)
982 nom = models.CharField(max_length=255)
e9bbd6ba 983
6e4600ef 984 class Meta:
985 ordering = ['code']
c1195471
OL
986 verbose_name = u"Statut d'employé"
987 verbose_name_plural = u"Statuts d'employé"
6e4600ef 988
9afaa55e 989 def __unicode__(self):
990 return u'%s : %s' % (self.code, self.nom)
991
83b7692b 992
e9bbd6ba 993TYPE_CLASSEMENT_CHOICES = (
6e4600ef 994 ('S', 'S -Soutien'),
995 ('T', 'T - Technicien'),
996 ('P', 'P - Professionel'),
997 ('C', 'C - Cadre'),
998 ('D', 'D - Direction'),
999 ('SO', 'SO - Sans objet [expatriés]'),
1000 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 1001)
83b7692b 1002
6e7c919b 1003
d6985a3a 1004class Classement_(AUFMetadata):
6e4600ef 1005 """Éléments de classement de la
1006 "Grille générique de classement hiérarchique".
1007
1008 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1009 classement dans la grille. Le classement donne le coefficient utilisé dans:
1010
1011 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1012 """
9afaa55e 1013 # Identification
e9bbd6ba 1014 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
c1195471
OL
1015 echelon = models.IntegerField(verbose_name = u"Échelon")
1016 degre = models.IntegerField(verbose_name = u"Degré")
1017 coefficient = models.FloatField(default=0, verbose_name = u"Coéfficient",
8277a35b 1018 null=True)
9afaa55e 1019 # Méta
6e4600ef 1020 # annee # au lieu de date_debut et date_fin
1021 commentaire = models.TextField(null=True, blank=True)
1022
1023 class Meta:
6e7c919b 1024 abstract = True
6e4600ef 1025 ordering = ['type','echelon','degre','coefficient']
c1195471
OL
1026 verbose_name = u"Classement"
1027 verbose_name_plural = u"Classements"
e9bbd6ba 1028
1029 def __unicode__(self):
1030 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
1031 self.coefficient)
1032
6e7c919b
NC
1033class Classement(Classement_):
1034 __doc__ = Classement_.__doc__
1035
1036
d6985a3a 1037class TauxChange_(AUFMetadata):
7abc6d45 1038 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 1039 pour chaque année budgétaire.
7abc6d45 1040 """
9afaa55e 1041 # Identification
8d3e2fff 1042 devise = models.ForeignKey('Devise', db_column='devise')
c1195471
OL
1043 annee = models.IntegerField(verbose_name = u"Année")
1044 taux = models.FloatField(verbose_name = u"Taux vers l'euro")
6e7c919b 1045
6e4600ef 1046 class Meta:
6e7c919b 1047 abstract = True
8c1ae2b3 1048 ordering = ['-annee', 'devise__code']
c1195471
OL
1049 verbose_name = u"Taux de change"
1050 verbose_name_plural = u"Taux de change"
6e4600ef 1051
1052 def __unicode__(self):
8c1ae2b3 1053 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1054
6e7c919b
NC
1055
1056class TauxChange(TauxChange_):
1057 __doc__ = TauxChange_.__doc__
1058
701f3bea
OL
1059class ValeurPointManager(NoDeleteManager):
1060 def get_query_set(self):
1061 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
1062
6e7c919b 1063
d6985a3a 1064class ValeurPoint_(AUFMetadata):
6e4600ef 1065 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1066 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1067 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1068
1069 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1070 """
701f3bea
OL
1071
1072 objects = ValeurPointManager()
1073
8277a35b
NC
1074 valeur = models.FloatField(null=True)
1075 devise = models.ForeignKey('Devise', db_column='devise', null=True,
6e4600ef 1076 related_name='+', default=5)
83b7692b 1077 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1078 db_column='implantation',
1079 related_name='%(app_label)s_valeur_point')
9afaa55e 1080 # Méta
e9bbd6ba 1081 annee = models.IntegerField()
9afaa55e 1082
6e4600ef 1083 class Meta:
701f3bea 1084 ordering = ['-annee', 'implantation__nom']
6e7c919b 1085 abstract = True
c1195471
OL
1086 verbose_name = u"Valeur du point"
1087 verbose_name_plural = u"Valeurs du point"
6e0bbb73 1088
e9d7483c 1089 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
ee23ecbc
NC
1090 def get_tauxchange_courant(self):
1091 """
1092 Recherche le taux courant associé à la valeur d'un point.
1093 Tous les taux de l'année courante sont chargés, pour optimiser un
1094 affichage en liste. (On pourrait probablement améliorer le manager pour
1095 lui greffer le taux courant sous forme de JOIN)
1096 """
1097 for tauxchange in self.tauxchange:
1098 if tauxchange.implantation_id == self.implantation_id:
1099 return tauxchange
1100 return None
1101
9afaa55e 1102 def __unicode__(self):
8c1ae2b3 1103 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
6e7c919b
NC
1104
1105
1106class ValeurPoint(ValeurPoint_):
1107 __doc__ = ValeurPoint_.__doc__
1108
e9bbd6ba 1109
d6985a3a 1110class Devise(AUFMetadata):
6e4600ef 1111 """Devise monétaire.
1112 """
e9bbd6ba 1113 code = models.CharField(max_length=10, unique=True)
1114 nom = models.CharField(max_length=255)
1115
6e4600ef 1116 class Meta:
1117 ordering = ['code']
c1195471
OL
1118 verbose_name = u"Devise"
1119 verbose_name_plural = u"Devises"
6e4600ef 1120
e9bbd6ba 1121 def __unicode__(self):
1122 return u'%s - %s' % (self.code, self.nom)
1123
d6985a3a 1124class TypeContrat(AUFMetadata):
6e4600ef 1125 """Type de contrat.
1126 """
e9bbd6ba 1127 nom = models.CharField(max_length=255)
6e4600ef 1128 nom_long = models.CharField(max_length=255)
49f9f116 1129
8c1ae2b3 1130 class Meta:
1131 ordering = ['nom']
c1195471
OL
1132 verbose_name = u"Type de contrat"
1133 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1134
9afaa55e 1135 def __unicode__(self):
1136 return u'%s' % (self.nom)
30be56d5 1137
2d4d2fcf 1138
1139### AUTRES
1140
d6985a3a 1141class ResponsableImplantation(AUFMetadata):
30be56d5 1142 """Le responsable d'une implantation.
1143 Anciennement géré sur le Dossier du responsable.
1144 """
6e4600ef 1145 employe = models.ForeignKey('Employe', db_column='employe',
1146 related_name='+',
1147 null=True, blank=True)
1148 implantation = models.ForeignKey(ref.Implantation,
1149 db_column='implantation', related_name='+',
1150 unique=True)
30be56d5 1151
1152 def __unicode__(self):
1153 return u'%s : %s' % (self.implantation, self.employe)
1154
1155 class Meta:
1156 ordering = ['implantation__nom']
8c1ae2b3 1157 verbose_name = "Responsable d'implantation"
1158 verbose_name_plural = "Responsables d'implantation"