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