apercu
[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():
186 representation = representation + u' (vacant)'
187 return representation
188
189 def is_vacant(self):
190 # TODO : si existe un dossier actif pour ce poste, return False
191 # self.dossier_set.all() fonctionne pas
192 return False
83b7692b 193
aff1a4c6
PP
194 prefix_implantation = "implantation__region"
195 def get_regions(self):
196 return [self.implantation.region]
197
83b7692b 198
37868f0b
NC
199class Poste(Poste_):
200 __doc__ = Poste_.__doc__
201
202
769a0755
NC
203class Poste(Poste_):
204 __doc__ = Poste_.__doc__
205
206
83b7692b 207POSTE_FINANCEMENT_CHOICES = (
208 ('A', 'A - Frais de personnel'),
209 ('B', 'B - Projet(s)-Titre(s)'),
210 ('C', 'C - Autre')
211)
212
6e7c919b
NC
213
214class PosteFinancement_(models.Model):
6e4600ef 215 """Pour un Poste, structure d'informations décrivant comment on prévoit
216 financer ce Poste.
217 """
218 poste = models.ForeignKey('Poste', db_column='poste',
6e7c919b 219 related_name='%(app_label)s_financements')
83b7692b 220 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
221 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
8c1ae2b3 222 help_text="ex.: 33.33 % (décimale avec point)")
83b7692b 223 commentaire = models.TextField(
8c1ae2b3 224 help_text="Spécifiez la source de financement.")
83b7692b 225
226 class Meta:
6e7c919b 227 abstract = True
83b7692b 228 ordering = ['type']
6e4600ef 229
230 def __unicode__(self):
231 return u'%s : %s %' % (self.type, self.pourcentage)
83b7692b 232
6e7c919b
NC
233
234class PosteFinancement(PosteFinancement_):
235 __doc__ = PosteFinancement_.__doc__
236
237
83b7692b 238class PostePiece(models.Model):
6e4600ef 239 """Documents relatifs au Poste.
7abc6d45 240 Ex.: Description de poste
241 """
8c1ae2b3 242 poste = models.ForeignKey('Poste', db_column='poste',
6e4600ef 243 related_name='pieces')
c1195471
OL
244 nom = models.CharField(verbose_name = u"Nom", max_length=255)
245 fichier = models.FileField(verbose_name = u"Fichier",
83b7692b 246 upload_to=poste_piece_dispatch,
247 storage=storage_prive)
248
6e4600ef 249 class Meta:
250 ordering = ['nom']
251
252 def __unicode__(self):
253 return u'%s' % (self.nom)
254
068d1462
OL
255class PosteComparaison(models.Model):
256 """
257 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
258 """
259 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
0f0dacbb 260 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
c1195471 261 nom = models.CharField(verbose_name = u"Poste", max_length=255, null=True, blank=True)
068d1462
OL
262 montant = models.IntegerField(null=True)
263 devise = models.ForeignKey("Devise", default=5, related_name='+', null=True, blank=True)
1d0f4eef
OL
264
265 def taux_devise(self):
266 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
267 if len(liste_taux) == 0:
268 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
269 else:
270 return liste_taux[0].taux
271
272 def montant_euros(self):
273 return round(float(self.montant) * float(self.taux_devise()), 2)
068d1462
OL
274
275
07b40eda 276class PosteCommentaire(Commentaire):
8c1ae2b3 277 poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
83b7692b 278
2d4d2fcf 279
83b7692b 280### EMPLOYÉ/PERSONNE
e9bbd6ba 281
282GENRE_CHOICES = (
283 ('M', 'Homme'),
284 ('F', 'Femme'),
285)
286SITUATION_CHOICES = (
287 ('C', 'Célibataire'),
288 ('F', 'Fiancé'),
289 ('M', 'Marié'),
290)
291
d6985a3a 292class Employe(AUFMetadata):
6e4600ef 293 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
294 Dossiers qu'il occupe ou a occupé de Postes.
295
296 Cette classe aurait pu avantageusement s'appeler Personne car la notion
297 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
298 """
9afaa55e 299 # Identification
e9bbd6ba 300 nom = models.CharField(max_length=255)
c1195471 301 prenom = models.CharField(max_length=255, verbose_name = u"Prénom")
6e4600ef 302 nom_affichage = models.CharField(max_length=255,
c1195471 303 verbose_name = u"Nom d'affichage",
6e4600ef 304 null=True, blank=True)
83b7692b 305 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 306 db_column='nationalite',
8c1ae2b3 307 related_name='employes_nationalite',
c1195471 308 verbose_name = u"Nationalité")
a25e1d5c 309 date_naissance = models.DateField(verbose_name = u"Date de naissance",
b4aeadf3 310 validators=[validate_date_passee],
6e4600ef 311 null=True, blank=True)
2d4d2fcf 312 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 313
9afaa55e 314 # Infos personnelles
6e4600ef 315 situation_famille = models.CharField(max_length=1,
316 choices=SITUATION_CHOICES,
c1195471 317 verbose_name = u"Situation familiale",
6e4600ef 318 null=True, blank=True)
c1195471 319 date_entree = models.DateField(verbose_name = u"Date d'entrée à l'AUF",
7abc6d45 320 null=True, blank=True)
83b7692b 321
9afaa55e 322 # Coordonnées
8c1ae2b3 323 tel_domicile = models.CharField(max_length=255,
c1195471 324 verbose_name = u"Tél. domicile",
8c1ae2b3 325 null=True, blank=True)
326 tel_cellulaire = models.CharField(max_length=255,
c1195471 327 verbose_name = u"Tél. cellulaire",
8c1ae2b3 328 null=True, blank=True)
e9bbd6ba 329 adresse = models.CharField(max_length=255, null=True, blank=True)
e9bbd6ba 330 ville = models.CharField(max_length=255, null=True, blank=True)
331 province = models.CharField(max_length=255, null=True, blank=True)
332 code_postal = models.CharField(max_length=255, null=True, blank=True)
6e4600ef 333 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
334 related_name='employes',
335 null=True, blank=True)
9afaa55e 336
6e4600ef 337 class Meta:
67666927 338 ordering = ['nom_affichage','nom','prenom']
c1195471
OL
339 verbose_name = u"Employé"
340 verbose_name_plural = u"Employés"
6e4600ef 341
9afaa55e 342 def __unicode__(self):
54773196 343 return u'%s [%s]' % (self.get_nom(), self.id)
8c1ae2b3 344
345 def get_nom(self):
ebc8eb32 346 nom_affichage = self.nom_affichage
347 if not nom_affichage:
348 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
8c1ae2b3 349 return nom_affichage
d04d084c 350
351 def civilite(self):
352 civilite = u''
353 if self.genre.upper() == u'M':
354 civilite = u'M.'
355 elif self.genre.upper() == u'F':
356 civilite = u'Mme'
357 return civilite
5ea6b5bb 358
359 def url_photo(self):
360 """Retourne l'URL du service retournant la photo de l'Employe.
361 Équivalent reverse url 'rh_photo' avec id en param.
362 """
363 from django.core.urlresolvers import reverse
364 return reverse('rh_photo', kwargs={'id':self.id})
35c0c2fe 365
c267f20c 366 def dossiers_passes(self):
367 today = date.today()
65f9fac8 368 dossiers_passes = self.dossiers.filter(date_fin__lt=today).order_by('-date_fin')
369 for d in dossiers_passes:
370 d.archive = True
371 return dossiers_passes
c267f20c 372
373 def dossiers_futurs(self):
374 today = date.today()
375 return self.dossiers.filter(date_debut__gt=today).order_by('-date_fin')
376
377 def dossiers_encours(self):
378 dossiers_p_f = self.dossiers_passes() | self.dossiers_futurs()
379 ids_dossiers_p_f = [d.id for d in dossiers_p_f]
65f9fac8 380 dossiers_encours = self.dossiers.exclude(id__in=ids_dossiers_p_f).order_by('-date_fin')
381
382 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
383 for d in dossiers_encours:
384 d.remunerations = Remuneration.objects.filter(dossier=d.id).order_by('-id')
385 return dossiers_encours
35c0c2fe 386
387 def postes_encours(self):
388 postes_encours = set()
389 for d in self.dossiers_encours():
390 postes_encours.add(d.poste)
391 return postes_encours
392
393 def poste_principal(self):
65f9fac8 394 """
395 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
396 Idée derrière :
397 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
398 """
399 poste = Poste.objects.none()
400 try:
401 poste = self.dossiers_encours().order_by('date_debut')[0].poste
402 except:
403 pass
404 return poste
9afaa55e 405
aff1a4c6
PP
406 prefix_implantation = "dossiers__poste__implantation__region"
407 def get_regions(self):
408 regions = []
409 for d in self.dossiers.all():
410 regions.append(d.poste.implantation.region)
411 return regions
412
413
64721a83
PP
414class EmployeInactif(Employe):
415 class Meta:
416 proxy = True
417 ordering = ['nom_affichage','nom','prenom']
418 verbose_name = u"Employé inactif"
419 verbose_name_plural = u"Employés inactifs"
420
421
7abc6d45 422class EmployePiece(models.Model):
6e4600ef 423 """Documents relatifs à un employé.
7abc6d45 424 Ex.: CV...
425 """
f9e54d59 426 employe = models.ForeignKey('Employe', db_column='employe')
8c1ae2b3 427 nom = models.CharField(verbose_name="Nom", max_length=255)
428 fichier = models.FileField(verbose_name="Fichier",
5ea6b5bb 429 upload_to=employe_piece_dispatch,
7abc6d45 430 storage=storage_prive)
431
6e4600ef 432 class Meta:
433 ordering = ['nom']
f9e54d59
PP
434 verbose_name = u"Employé pièce"
435 verbose_name_plural = u"Employé pièces"
436
6e4600ef 437 def __unicode__(self):
438 return u'%s' % (self.nom)
439
07b40eda 440class EmployeCommentaire(Commentaire):
8c1ae2b3 441 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 442 related_name='+')
9afaa55e 443
b343eb3d
PP
444 class Meta:
445 verbose_name = u"Employé commentaire"
446 verbose_name_plural = u"Employé commentaires"
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é")
a25e1d5c 470 date_naissance = models.DateField(verbose_name = u"Date de naissance",
25037368 471 validators=[validate_date_passee],
6e4600ef 472 null=True, blank=True)
2d4d2fcf 473 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 474
9afaa55e 475 # Relation
476 employe = models.ForeignKey('Employe', db_column='employe',
8c1ae2b3 477 related_name='ayantdroits',
c1195471 478 verbose_name = u"Employé")
6e4600ef 479 lien_parente = models.CharField(max_length=10,
480 choices=LIEN_PARENTE_CHOICES,
c1195471 481 verbose_name = u"Lien de parenté",
6e4600ef 482 null=True, blank=True)
483
484 class Meta:
485 ordering = ['nom_affichage']
c1195471
OL
486 verbose_name = u"Ayant droit"
487 verbose_name_plural = u"Ayants droit"
8c1ae2b3 488
6e4600ef 489 def __unicode__(self):
8c1ae2b3 490 return u'%s' % (self.get_nom())
491
492 def get_nom(self):
493 nom_affichage = self.nom_affichage
494 if not nom_affichage:
495 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
496 return nom_affichage
83b7692b 497
aff1a4c6
PP
498 prefix_implantation = "employe__dossiers__poste__implantation__region"
499 def get_regions(self):
500 regions = []
501 for d in self.employe.dossiers.all():
502 regions.append(d.poste.implantation.region)
503 return regions
504
505
07b40eda 506class AyantDroitCommentaire(Commentaire):
8c1ae2b3 507 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
6e4600ef 508 related_name='+')
83b7692b 509
2d4d2fcf 510
83b7692b 511### DOSSIER
512
513STATUT_RESIDENCE_CHOICES = (
514 ('local', 'Local'),
515 ('expat', 'Expatrié'),
516)
517
518COMPTE_COMPTA_CHOICES = (
519 ('coda', 'CODA'),
520 ('scs', 'SCS'),
521 ('aucun', 'Aucun'),
522)
523
d6985a3a 524class Dossier_(AUFMetadata):
6e4600ef 525 """Le Dossier regroupe les informations relatives à l'occupation
526 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
527 par un Employe.
528
529 Plusieurs Contrats peuvent être associés au Dossier.
530 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
531 lequel aucun Dossier n'existe est un poste vacant.
532 """
83b7692b 533 # Identification
6e4600ef 534 employe = models.ForeignKey('Employe', db_column='employe',
d04d084c 535 related_name='dossiers',
bf4f5c77 536 verbose_name=u"Employé")
63e17dff
PP
537 # TODO: OneToOne ??
538 poste = models.ForeignKey('Poste', db_column='poste', related_name='dossiers')
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 \
a25e1d5c 574 de poste",)
c1195471 575 date_fin = models.DateField(verbose_name = u"Date de fin d'occupation \
6e4600ef 576 de poste",
6e4600ef 577 null=True, blank=True)
e9bbd6ba 578
2d4d2fcf 579 # Comptes
580 # TODO?
83b7692b 581
6e4600ef 582 class Meta:
37868f0b 583 abstract = True
49449367 584 ordering = ['employe__nom', ]
3f5f3898 585 verbose_name = u"Dossier"
8c1ae2b3 586 verbose_name_plural = "Dossiers"
6e4600ef 587
65f9fac8 588 def salaire_theorique(self):
589 annee = date.today().year
590 coeff = self.classement.coefficient
591 implantation = self.poste.implantation
592 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
593
594 montant = coeff * point.valeur
595 devise = point.devise
596 return {'montant':montant, 'devise':devise}
597
83b7692b 598 def __unicode__(self):
8c1ae2b3 599 poste = self.poste.nom
600 if self.employe.genre == 'F':
601 poste = self.poste.nom_feminin
602 return u'%s - %s' % (self.employe, poste)
83b7692b 603
aff1a4c6
PP
604 prefix_implantation = "poste__implantation__region"
605 def get_regions(self):
606 return [self.poste.implantation.region]
607
37868f0b 608
3ebc0952
OL
609 def remunerations(self):
610 return self.rh_remuneration_remunerations.all().order_by('date_debut')
611
612
37868f0b
NC
613class Dossier(Dossier_):
614 __doc__ = Dossier_.__doc__
615
616
64721a83 617class DossierInactif(Dossier):
6ad01c0a 618
64721a83
PP
619 class Meta:
620 proxy = True
621 ordering = ['employe__nom', ]
622 verbose_name = u"Dossier inactif"
623 verbose_name_plural = u"Dossiers inactifs"
624
625
83b7692b 626class DossierPiece(models.Model):
7abc6d45 627 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
628 Ex.: Lettre de motivation.
629 """
8c1ae2b3 630 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 631 related_name='+')
c1195471
OL
632 nom = models.CharField(verbose_name = u"Nom", max_length=255)
633 fichier = models.FileField(verbose_name = u"Fichier",
83b7692b 634 upload_to=dossier_piece_dispatch,
635 storage=storage_prive)
636
6e4600ef 637 class Meta:
638 ordering = ['nom']
639
640 def __unicode__(self):
641 return u'%s' % (self.nom)
642
07b40eda 643class DossierCommentaire(Commentaire):
8c1ae2b3 644 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 645 related_name='+')
83b7692b 646
1d0f4eef
OL
647class DossierComparaison(models.Model):
648 """
649 Photo d'une comparaison salariale au moment de l'embauche.
650 """
651 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
8c6269cc 652 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
1d0f4eef
OL
653 poste = models.CharField(max_length=255, null=True, blank=True)
654 personne = models.CharField(max_length=255, null=True, blank=True)
655 montant = models.IntegerField(null=True)
93494cf1 656 devise = models.ForeignKey('Devise', default=5, related_name='+', null=True, blank=True)
1d0f4eef
OL
657
658 def taux_devise(self):
659 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
660 if len(liste_taux) == 0:
661 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
662 else:
663 return liste_taux[0].taux
664
665 def montant_euros(self):
666 return round(float(self.montant) * float(self.taux_devise()), 2)
667
2d4d2fcf 668
07b40eda 669### RÉMUNÉRATION
e9bbd6ba 670
d6985a3a 671class RemunerationMixin(AUFMetadata):
9afaa55e 672 # Identification
6e7c919b
NC
673 dossier = models.ForeignKey('Dossier', db_column='dossier',
674 related_name='%(app_label)s_%(class)s_remunerations')
83b7692b 675 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 676 related_name='+',
c1195471 677 verbose_name = u"Type de rémunération")
7abc6d45 678 type_revalorisation = models.ForeignKey('TypeRevalorisation',
679 db_column='type_revalorisation',
6e4600ef 680 related_name='+',
c1195471 681 verbose_name = u"Type de revalorisation",
7abc6d45 682 null=True, blank=True)
50fa9bc1 683 montant = models.FloatField(null=True, blank=True,
6e4600ef 684 default=0)
2d4d2fcf 685 # Annuel (12 mois, 52 semaines, 364 jours?)
c589d980 686 devise = models.ForeignKey('Devise', to_field='id',
6e4600ef 687 db_column='devise', related_name='+',
688 default=5)
2d4d2fcf 689 # commentaire = precision
690 commentaire = models.CharField(max_length=255, null=True, blank=True)
691 # date_debut = anciennement date_effectif
a25e1d5c 692 date_debut = models.DateField(verbose_name = u"Date de début",
6e4600ef 693 null=True, blank=True)
a25e1d5c 694 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 695 null=True, blank=True)
83b7692b 696
2d4d2fcf 697 class Meta:
698 abstract = True
6e4600ef 699 ordering = ['type__nom', '-date_fin']
700
701 def __unicode__(self):
702 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
2d4d2fcf 703
6e7c919b 704class Remuneration_(RemunerationMixin):
2d4d2fcf 705 """Structure de rémunération (données budgétaires) en situation normale
706 pour un Dossier. Si un Evenement existe, utiliser la structure de
707 rémunération EvenementRemuneration de cet événement.
708 """
83b7692b 709
710 def montant_mois(self):
711 return round(self.montant / 12, 2)
712
713 def taux_devise(self):
714 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
715
716 def montant_euro(self):
717 return round(float(self.montant) / float(self.taux_devise()), 2)
718
719 def montant_euro_mois(self):
720 return round(self.montant_euro() / 12, 2)
9afaa55e 721
722 def __unicode__(self):
723 try:
724 devise = self.devise.code
725 except:
726 devise = "???"
727 return "%s %s" % (self.montant, devise)
83b7692b 728
6e7c919b
NC
729 class Meta:
730 abstract = True
c1195471
OL
731 verbose_name = u"Rémunération"
732 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
733
734
735class Remuneration(Remuneration_):
736 __doc__ = Remuneration_.__doc__
737
2d4d2fcf 738
739### CONTRATS
c41b7fcc
OL
740
741class ContratManager(NoDeleteManager):
742 def get_query_set(self):
743 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
744
2d4d2fcf 745
d6985a3a 746class Contrat(AUFMetadata):
2d4d2fcf 747 """Document juridique qui encadre la relation de travail d'un Employe
748 pour un Poste particulier. Pour un Dossier (qui documente cette
749 relation de travail) plusieurs contrats peuvent être associés.
750 """
c41b7fcc
OL
751
752 objects = ContratManager()
753
6e4600ef 754 dossier = models.ForeignKey('Dossier', db_column='dossier',
65f9fac8 755 related_name='contrats')
6e4600ef 756 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 757 related_name='+',
c1195471 758 verbose_name = u"Type de contrat")
a25e1d5c
OL
759 date_debut = models.DateField(verbose_name = u"Date de début")
760 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 761 null=True, blank=True)
762
763 class Meta:
764 ordering = ['dossier__employe__nom_affichage']
c1195471
OL
765 verbose_name = u"Contrat"
766 verbose_name_plural = u"Contrats"
6e4600ef 767
768 def __unicode__(self):
8c1ae2b3 769 return u'%s - %s' % (self.dossier, self.id)
6e4600ef 770
771# TODO? class ContratPiece(models.Model):
2d4d2fcf 772
773
774### ÉVÉNEMENTS
775
d6985a3a 776class Evenement_(AUFMetadata):
6e4600ef 777 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
778 d'un Dossier qui vient altérer des informations normales liées à un Dossier
779 (ex.: la Remuneration).
780
781 Ex.: congé de maternité, maladie...
782
783 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
784 différent et une rémunération en conséquence. On souhaite toutefois
785 conserver le Dossier intact afin d'éviter une re-saisie des données lors
786 du retour à la normale.
787 """
8c1ae2b3 788 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 789 related_name='+')
790 nom = models.CharField(max_length=255)
a25e1d5c
OL
791 date_debut = models.DateField(verbose_name = u"Date de début")
792 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 793 null=True, blank=True)
6e7c919b 794
6e4600ef 795 class Meta:
6e7c919b 796 abstract = True
6e4600ef 797 ordering = ['nom']
c1195471
OL
798 verbose_name = u"Évènement"
799 verbose_name_plural = u"Évènements"
6e4600ef 800
801 def __unicode__(self):
802 return u'%s' % (self.nom)
6e7c919b
NC
803
804
805class Evenement(Evenement_):
806 __doc__ = Evenement_.__doc__
807
2d4d2fcf 808
6e7c919b 809class EvenementRemuneration_(RemunerationMixin):
6e4600ef 810 """Structure de rémunération liée à un Evenement qui remplace
811 temporairement la Remuneration normale d'un Dossier, pour toute la durée
812 de l'Evenement.
813 """
814 evenement = models.ForeignKey("Evenement", db_column='evenement',
8c1ae2b3 815 related_name='+',
c1195471 816 verbose_name = u"Évènement")
8c1ae2b3 817 # TODO : le champ dossier hérité de Remuneration doit être dérivé
818 # de l'Evenement associé
83b7692b 819
6e7c919b
NC
820 class Meta:
821 abstract = True
8c1ae2b3 822 ordering = ['evenement', 'type__nom', '-date_fin']
c1195471
OL
823 verbose_name = u"Évènement - rémunération"
824 verbose_name_plural = u"Évènements - rémunérations"
6e7c919b
NC
825
826
827class EvenementRemuneration(EvenementRemuneration_):
828 __doc__ = EvenementRemuneration_.__doc__
83b7692b 829
f31ddfa0
NC
830 class Meta:
831 abstract = True
832
833
834class EvenementRemuneration(EvenementRemuneration_):
835 __doc__ = EvenementRemuneration_.__doc__
836
83b7692b 837
838### RÉFÉRENCES RH
839
d6985a3a 840class FamilleEmploi(AUFMetadata):
6e4600ef 841 """Catégorie utilisée dans la gestion des Postes.
842 Catégorie supérieure à TypePoste.
843 """
e9bbd6ba 844 nom = models.CharField(max_length=255)
6e4600ef 845
8c1ae2b3 846 class Meta:
847 ordering = ['nom']
c1195471
OL
848 verbose_name = u"Famille d'emploi"
849 verbose_name_plural = u"Familles d'emploi"
8c1ae2b3 850
6e4600ef 851 def __unicode__(self):
852 return u'%s' % (self.nom)
e9bbd6ba 853
d6985a3a 854class TypePoste(AUFMetadata):
6e4600ef 855 """Catégorie de Poste.
856 """
e9bbd6ba 857 nom = models.CharField(max_length=255)
8c1ae2b3 858 nom_feminin = models.CharField(max_length=255,
c1195471 859 verbose_name = u"Nom féminin")
6e4600ef 860
8c1ae2b3 861 is_responsable = models.BooleanField(default=False,
c1195471 862 verbose_name = u"Poste de responsabilité")
9afaa55e 863 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 864 db_column='famille_emploi',
8c1ae2b3 865 related_name='+',
c1195471 866 verbose_name = u"Famille d'emploi")
e9bbd6ba 867
6e4600ef 868 class Meta:
869 ordering = ['nom']
c1195471
OL
870 verbose_name = u"Type de poste"
871 verbose_name_plural = u"Types de poste"
6e4600ef 872
e9bbd6ba 873 def __unicode__(self):
6e4600ef 874 return u'%s' % (self.nom)
e9bbd6ba 875
876
877TYPE_PAIEMENT_CHOICES = (
878 ('Régulier', 'Régulier'),
879 ('Ponctuel', 'Ponctuel'),
880)
881
882NATURE_REMUNERATION_CHOICES = (
883 ('Accessoire', 'Accessoire'),
884 ('Charges', 'Charges'),
885 ('Indemnité', 'Indemnité'),
7abc6d45 886 ('RAS', 'Rémunération autre source'),
e9bbd6ba 887 ('Traitement', 'Traitement'),
888)
889
d6985a3a 890class TypeRemuneration(AUFMetadata):
6e4600ef 891 """Catégorie de Remuneration.
892 """
e9bbd6ba 893 nom = models.CharField(max_length=255)
9afaa55e 894 type_paiement = models.CharField(max_length=30,
8c1ae2b3 895 choices=TYPE_PAIEMENT_CHOICES,
c1195471 896 verbose_name = u"Type de paiement")
9afaa55e 897 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 898 choices=NATURE_REMUNERATION_CHOICES,
c1195471 899 verbose_name = u"Nature de la rémunération")
8c1ae2b3 900
901 class Meta:
902 ordering = ['nom']
c1195471
OL
903 verbose_name = u"Type de rémunération"
904 verbose_name_plural = u"Types de rémunération"
9afaa55e 905
906 def __unicode__(self):
6e4600ef 907 return u'%s' % (self.nom)
e9bbd6ba 908
d6985a3a 909class TypeRevalorisation(AUFMetadata):
7abc6d45 910 """Justification du changement de la Remuneration.
6e4600ef 911 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 912 """
e9bbd6ba 913 nom = models.CharField(max_length=255)
8c1ae2b3 914
915 class Meta:
916 ordering = ['nom']
c1195471
OL
917 verbose_name = u"Type de revalorisation"
918 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 919
920 def __unicode__(self):
6e4600ef 921 return u'%s' % (self.nom)
922
d6985a3a 923class Service(AUFMetadata):
6e4600ef 924 """Unité administrative où les Postes sont rattachés.
925 """
926 nom = models.CharField(max_length=255)
9afaa55e 927
928 class Meta:
929 ordering = ['nom']
c1195471
OL
930 verbose_name = u"Service"
931 verbose_name_plural = u"Services"
e9bbd6ba 932
6e4600ef 933 def __unicode__(self):
934 return u'%s' % (self.nom)
935
e9bbd6ba 936
937TYPE_ORGANISME_CHOICES = (
938 ('MAD', 'Mise à disposition'),
939 ('DET', 'Détachement'),
940)
941
d6985a3a 942class OrganismeBstg(AUFMetadata):
6e4600ef 943 """Organisation d'où provient un Employe mis à disposition (MAD) de
944 ou détaché (DET) à l'AUF à titre gratuit.
945
946 (BSTG = bien et service à titre gratuit.)
947 """
e9bbd6ba 948 nom = models.CharField(max_length=255)
949 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
6e4600ef 950 pays = models.ForeignKey(ref.Pays, to_field='code',
951 db_column='pays',
952 related_name='organismes_bstg',
953 null=True, blank=True)
9afaa55e 954
955 class Meta:
956 ordering = ['type', 'nom']
c1195471
OL
957 verbose_name = u"Organisme BSTG"
958 verbose_name_plural = u"Organismes BSTG"
9afaa55e 959
6e4600ef 960 def __unicode__(self):
8c1ae2b3 961 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 962
aff1a4c6
PP
963 prefix_implantation = "pays__region"
964 def get_regions(self):
965 return [self.pays.region]
966
967
d6985a3a 968class Statut(AUFMetadata):
6e4600ef 969 """Statut de l'Employe dans le cadre d'un Dossier particulier.
970 """
9afaa55e 971 # Identification
a122050d 972 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 973 nom = models.CharField(max_length=255)
e9bbd6ba 974
6e4600ef 975 class Meta:
976 ordering = ['code']
c1195471
OL
977 verbose_name = u"Statut d'employé"
978 verbose_name_plural = u"Statuts d'employé"
6e4600ef 979
9afaa55e 980 def __unicode__(self):
981 return u'%s : %s' % (self.code, self.nom)
982
83b7692b 983
e9bbd6ba 984TYPE_CLASSEMENT_CHOICES = (
6e4600ef 985 ('S', 'S -Soutien'),
986 ('T', 'T - Technicien'),
987 ('P', 'P - Professionel'),
988 ('C', 'C - Cadre'),
989 ('D', 'D - Direction'),
990 ('SO', 'SO - Sans objet [expatriés]'),
991 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 992)
83b7692b 993
6e7c919b 994
d6985a3a 995class Classement_(AUFMetadata):
6e4600ef 996 """Éléments de classement de la
997 "Grille générique de classement hiérarchique".
998
999 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1000 classement dans la grille. Le classement donne le coefficient utilisé dans:
1001
1002 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1003 """
9afaa55e 1004 # Identification
e9bbd6ba 1005 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
c1195471
OL
1006 echelon = models.IntegerField(verbose_name = u"Échelon")
1007 degre = models.IntegerField(verbose_name = u"Degré")
1008 coefficient = models.FloatField(default=0, verbose_name = u"Coéfficient",
8277a35b 1009 null=True)
9afaa55e 1010 # Méta
6e4600ef 1011 # annee # au lieu de date_debut et date_fin
1012 commentaire = models.TextField(null=True, blank=True)
1013
1014 class Meta:
6e7c919b 1015 abstract = True
6e4600ef 1016 ordering = ['type','echelon','degre','coefficient']
c1195471
OL
1017 verbose_name = u"Classement"
1018 verbose_name_plural = u"Classements"
e9bbd6ba 1019
1020 def __unicode__(self):
1021 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
1022 self.coefficient)
1023
6e7c919b
NC
1024class Classement(Classement_):
1025 __doc__ = Classement_.__doc__
1026
1027
d6985a3a 1028class TauxChange_(AUFMetadata):
7abc6d45 1029 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 1030 pour chaque année budgétaire.
7abc6d45 1031 """
9afaa55e 1032 # Identification
8d3e2fff 1033 devise = models.ForeignKey('Devise', db_column='devise')
c1195471
OL
1034 annee = models.IntegerField(verbose_name = u"Année")
1035 taux = models.FloatField(verbose_name = u"Taux vers l'euro")
6e7c919b 1036
6e4600ef 1037 class Meta:
6e7c919b 1038 abstract = True
8c1ae2b3 1039 ordering = ['-annee', 'devise__code']
c1195471
OL
1040 verbose_name = u"Taux de change"
1041 verbose_name_plural = u"Taux de change"
6e4600ef 1042
1043 def __unicode__(self):
8c1ae2b3 1044 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1045
6e7c919b
NC
1046
1047class TauxChange(TauxChange_):
1048 __doc__ = TauxChange_.__doc__
1049
701f3bea
OL
1050class ValeurPointManager(NoDeleteManager):
1051 def get_query_set(self):
1052 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
1053
6e7c919b 1054
d6985a3a 1055class ValeurPoint_(AUFMetadata):
6e4600ef 1056 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1057 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1058 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1059
1060 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1061 """
701f3bea
OL
1062
1063 objects = ValeurPointManager()
1064
8277a35b
NC
1065 valeur = models.FloatField(null=True)
1066 devise = models.ForeignKey('Devise', db_column='devise', null=True,
6e4600ef 1067 related_name='+', default=5)
83b7692b 1068 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1069 db_column='implantation',
1070 related_name='%(app_label)s_valeur_point')
9afaa55e 1071 # Méta
e9bbd6ba 1072 annee = models.IntegerField()
9afaa55e 1073
6e4600ef 1074 class Meta:
701f3bea 1075 ordering = ['-annee', 'implantation__nom']
6e7c919b 1076 abstract = True
c1195471
OL
1077 verbose_name = u"Valeur du point"
1078 verbose_name_plural = u"Valeurs du point"
6e0bbb73 1079
e9d7483c 1080 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
ee23ecbc
NC
1081 def get_tauxchange_courant(self):
1082 """
1083 Recherche le taux courant associé à la valeur d'un point.
1084 Tous les taux de l'année courante sont chargés, pour optimiser un
1085 affichage en liste. (On pourrait probablement améliorer le manager pour
1086 lui greffer le taux courant sous forme de JOIN)
1087 """
1088 for tauxchange in self.tauxchange:
1089 if tauxchange.implantation_id == self.implantation_id:
1090 return tauxchange
1091 return None
1092
9afaa55e 1093 def __unicode__(self):
8c1ae2b3 1094 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
6e7c919b
NC
1095
1096
1097class ValeurPoint(ValeurPoint_):
1098 __doc__ = ValeurPoint_.__doc__
1099
e9bbd6ba 1100
d6985a3a 1101class Devise(AUFMetadata):
6e4600ef 1102 """Devise monétaire.
1103 """
e9bbd6ba 1104 code = models.CharField(max_length=10, unique=True)
1105 nom = models.CharField(max_length=255)
1106
6e4600ef 1107 class Meta:
1108 ordering = ['code']
c1195471
OL
1109 verbose_name = u"Devise"
1110 verbose_name_plural = u"Devises"
6e4600ef 1111
e9bbd6ba 1112 def __unicode__(self):
1113 return u'%s - %s' % (self.code, self.nom)
1114
d6985a3a 1115class TypeContrat(AUFMetadata):
6e4600ef 1116 """Type de contrat.
1117 """
e9bbd6ba 1118 nom = models.CharField(max_length=255)
6e4600ef 1119 nom_long = models.CharField(max_length=255)
49f9f116 1120
8c1ae2b3 1121 class Meta:
1122 ordering = ['nom']
c1195471
OL
1123 verbose_name = u"Type de contrat"
1124 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1125
9afaa55e 1126 def __unicode__(self):
1127 return u'%s' % (self.nom)
30be56d5 1128
2d4d2fcf 1129
1130### AUTRES
1131
d6985a3a 1132class ResponsableImplantation(AUFMetadata):
30be56d5 1133 """Le responsable d'une implantation.
1134 Anciennement géré sur le Dossier du responsable.
1135 """
6e4600ef 1136 employe = models.ForeignKey('Employe', db_column='employe',
1137 related_name='+',
1138 null=True, blank=True)
1139 implantation = models.ForeignKey(ref.Implantation,
1140 db_column='implantation', related_name='+',
1141 unique=True)
30be56d5 1142
1143 def __unicode__(self):
1144 return u'%s : %s' % (self.implantation, self.employe)
1145
1146 class Meta:
1147 ordering = ['implantation__nom']
8c1ae2b3 1148 verbose_name = "Responsable d'implantation"
1149 verbose_name_plural = "Responsables d'implantation"