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