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