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