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