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