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