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