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