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