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