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