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