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