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