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