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