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