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