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