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