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