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