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