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