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