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