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