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