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