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