PEP8
[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 24 PosteComparaisonManager, DeviseManager, ServiceManager, \
fc62be5d 25 TypeRemunerationManager, RemunerationManager
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
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 """
6fb68b2f
DB
453
454 objects = EmployeManager()
db265492 455
9afaa55e 456 # Identification
e9bbd6ba 457 nom = models.CharField(max_length=255)
fa1f7426
EMS
458 prenom = models.CharField(u"prénom", max_length=255)
459 nom_affichage = models.CharField(
460 u"nom d'affichage", max_length=255, null=True, blank=True
461 )
462 nationalite = models.ForeignKey(
463 ref.Pays, to_field='code', db_column='nationalite',
464 related_name='employes_nationalite', verbose_name=u"nationalité",
465 blank=True, null=True
466 )
467 date_naissance = models.DateField(
468 u"date de naissance", help_text=HELP_TEXT_DATE,
469 validators=[validate_date_passee], null=True, blank=True
470 )
2d4d2fcf 471 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
ca1a7b76 472
9afaa55e 473 # Infos personnelles
fa1f7426
EMS
474 situation_famille = models.CharField(
475 u"situation familiale", max_length=1, choices=SITUATION_CHOICES,
476 null=True, blank=True
477 )
478 date_entree = models.DateField(
479 u"date d'entrée à l'AUF", help_text=HELP_TEXT_DATE, null=True,
480 blank=True
481 )
ca1a7b76 482
9afaa55e 483 # Coordonnées
fa1f7426
EMS
484 tel_domicile = models.CharField(
485 u"tél. domicile", max_length=255, null=True, blank=True
486 )
487 tel_cellulaire = models.CharField(
488 u"tél. cellulaire", max_length=255, null=True, blank=True
489 )
e9bbd6ba 490 adresse = models.CharField(max_length=255, null=True, blank=True)
e9bbd6ba 491 ville = models.CharField(max_length=255, null=True, blank=True)
492 province = models.CharField(max_length=255, null=True, blank=True)
493 code_postal = models.CharField(max_length=255, null=True, blank=True)
fa1f7426
EMS
494 pays = models.ForeignKey(
495 ref.Pays, to_field='code', db_column='pays',
496 related_name='employes', null=True, blank=True
497 )
89a8df07
EMS
498 courriel_perso = models.EmailField(
499 u'adresse courriel personnelle', blank=True
500 )
9afaa55e 501
a45e414b 502 # meta dématérialisation : pour permettre le filtrage
fa1f7426 503 nb_postes = models.IntegerField(u"nombre de postes", null=True, blank=True)
a45e414b 504
6e4600ef 505 class Meta:
fa1f7426 506 ordering = ['nom', 'prenom']
c1195471
OL
507 verbose_name = u"Employé"
508 verbose_name_plural = u"Employés"
ca1a7b76 509
9afaa55e 510 def __unicode__(self):
a2c3ad52 511 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
ca1a7b76 512
d04d084c 513 def civilite(self):
514 civilite = u''
515 if self.genre.upper() == u'M':
516 civilite = u'M.'
517 elif self.genre.upper() == u'F':
518 civilite = u'Mme'
519 return civilite
ca1a7b76 520
5ea6b5bb 521 def url_photo(self):
fa1f7426
EMS
522 """
523 Retourne l'URL du service retournant la photo de l'Employe.
5ea6b5bb 524 Équivalent reverse url 'rh_photo' avec id en param.
525 """
526 from django.core.urlresolvers import reverse
fa1f7426 527 return reverse('rh_photo', kwargs={'id': self.id})
ca1a7b76 528
c267f20c 529 def dossiers_passes(self):
6bee05ff
OL
530 params = {KEY_STATUT: STATUT_INACTIF, }
531 search = RechercheTemporelle(params, self.__class__)
532 search.purge_params(params)
dcd1b959
OL
533 q = search.get_q_temporel(self.rh_dossiers)
534 return self.rh_dossiers.filter(q)
ca1a7b76 535
c267f20c 536 def dossiers_futurs(self):
6bee05ff
OL
537 params = {KEY_STATUT: STATUT_FUTUR, }
538 search = RechercheTemporelle(params, self.__class__)
539 search.purge_params(params)
dcd1b959
OL
540 q = search.get_q_temporel(self.rh_dossiers)
541 return self.rh_dossiers.filter(q)
ca1a7b76 542
c267f20c 543 def dossiers_encours(self):
6bee05ff
OL
544 params = {KEY_STATUT: STATUT_ACTIF, }
545 search = RechercheTemporelle(params, self.__class__)
546 search.purge_params(params)
dcd1b959
OL
547 q = search.get_q_temporel(self.rh_dossiers)
548 return self.rh_dossiers.filter(q)
ca1a7b76 549
5db1c5a3 550 def dossier_principal(self):
db265492
EMS
551 """
552 Retourne le dossier principal (ou le plus ancien si il y en a
553 plusieurs)
5db1c5a3
DB
554 """
555 try:
db265492
EMS
556 dossier = self.rh_dossiers \
557 .filter(principal=True).order_by('date_debut')[0]
5db1c5a3
DB
558 except IndexError, Dossier.DoesNotExist:
559 dossier = None
560 return dossier
561
35c0c2fe 562 def postes_encours(self):
563 postes_encours = set()
564 for d in self.dossiers_encours():
565 postes_encours.add(d.poste)
566 return postes_encours
ca1a7b76 567
35c0c2fe 568 def poste_principal(self):
65f9fac8 569 """
570 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
ca1a7b76 571 Idée derrière :
65f9fac8 572 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
573 """
5db1c5a3 574 # DEPRECATED : on a maintenant Dossier.principal
65f9fac8 575 poste = Poste.objects.none()
576 try:
577 poste = self.dossiers_encours().order_by('date_debut')[0].poste
578 except:
579 pass
580 return poste
9afaa55e 581
b5cc0357 582 prefix_implantation = "rh_dossiers__poste__implantation__region"
fa1f7426 583
aff1a4c6
PP
584 def get_regions(self):
585 regions = []
586 for d in self.dossiers.all():
587 regions.append(d.poste.implantation.region)
588 return regions
589
590
7abc6d45 591class EmployePiece(models.Model):
fa1f7426
EMS
592 """
593 Documents relatifs à un employé.
7abc6d45 594 Ex.: CV...
595 """
fa1f7426
EMS
596 employe = models.ForeignKey(
597 'Employe', db_column='employe', related_name="pieces",
598 verbose_name=u"employé"
599 )
600 nom = models.CharField(max_length=255)
601 fichier = models.FileField(
602 u"fichier", upload_to=employe_piece_dispatch, storage=storage_prive
603 )
7abc6d45 604
6e4600ef 605 class Meta:
606 ordering = ['nom']
f9e54d59
PP
607 verbose_name = u"Employé pièce"
608 verbose_name_plural = u"Employé pièces"
609
6e4600ef 610 def __unicode__(self):
611 return u'%s' % (self.nom)
612
fa1f7426 613
07b40eda 614class EmployeCommentaire(Commentaire):
fa1f7426
EMS
615 employe = models.ForeignKey(
616 'Employe', db_column='employe', related_name='+'
617 )
9afaa55e 618
b343eb3d
PP
619 class Meta:
620 verbose_name = u"Employé commentaire"
621 verbose_name_plural = u"Employé commentaires"
622
2d4d2fcf 623
e9bbd6ba 624LIEN_PARENTE_CHOICES = (
625 ('Conjoint', 'Conjoint'),
626 ('Conjointe', 'Conjointe'),
627 ('Fille', 'Fille'),
628 ('Fils', 'Fils'),
629)
630
fa1f7426 631
d6985a3a 632class AyantDroit(AUFMetadata):
fa1f7426
EMS
633 """
634 Personne en relation avec un Employe.
6e4600ef 635 """
9afaa55e 636 # Identification
e9bbd6ba 637 nom = models.CharField(max_length=255)
fa1f7426
EMS
638 prenom = models.CharField(u"prénom", max_length=255)
639 nom_affichage = models.CharField(
640 u"nom d'affichage", max_length=255, null=True, blank=True
641 )
642 nationalite = models.ForeignKey(
643 ref.Pays, to_field='code', db_column='nationalite',
644 related_name='ayantdroits_nationalite',
645 verbose_name=u"nationalité", null=True, blank=True
646 )
647 date_naissance = models.DateField(
648 u"Date de naissance", help_text=HELP_TEXT_DATE,
649 validators=[validate_date_passee], null=True, blank=True
650 )
2d4d2fcf 651 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
ca1a7b76 652
9afaa55e 653 # Relation
fa1f7426
EMS
654 employe = models.ForeignKey(
655 'Employe', db_column='employe', related_name='ayantdroits',
656 verbose_name=u"Employé"
657 )
658 lien_parente = models.CharField(
659 u"lien de parenté", max_length=10, choices=LIEN_PARENTE_CHOICES,
660 null=True, blank=True
661 )
6e4600ef 662
663 class Meta:
a2c3ad52 664 ordering = ['nom', ]
c1195471
OL
665 verbose_name = u"Ayant droit"
666 verbose_name_plural = u"Ayants droit"
ca1a7b76 667
6e4600ef 668 def __unicode__(self):
2de29065 669 return u'%s %s' % (self.nom.upper(), self.prenom, )
83b7692b 670
aff1a4c6 671 prefix_implantation = "employe__dossiers__poste__implantation__region"
fa1f7426 672
aff1a4c6
PP
673 def get_regions(self):
674 regions = []
675 for d in self.employe.dossiers.all():
676 regions.append(d.poste.implantation.region)
677 return regions
678
679
07b40eda 680class AyantDroitCommentaire(Commentaire):
fa1f7426
EMS
681 ayant_droit = models.ForeignKey(
682 'AyantDroit', db_column='ayant_droit', related_name='+'
683 )
83b7692b 684
2d4d2fcf 685
83b7692b 686### DOSSIER
687
688STATUT_RESIDENCE_CHOICES = (
689 ('local', 'Local'),
690 ('expat', 'Expatrié'),
691)
692
693COMPTE_COMPTA_CHOICES = (
694 ('coda', 'CODA'),
695 ('scs', 'SCS'),
696 ('aucun', 'Aucun'),
697)
698
fa1f7426 699
b78bbaf3 700class Dossier_(AUFMetadata, DevisableMixin):
fa1f7426
EMS
701 """
702 Le Dossier regroupe les informations relatives à l'occupation
6e4600ef 703 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
704 par un Employe.
ca1a7b76 705
6e4600ef 706 Plusieurs Contrats peuvent être associés au Dossier.
707 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
708 lequel aucun Dossier n'existe est un poste vacant.
709 """
3f5cbabe
OL
710
711 objects = DossierManager()
712
63e17dff 713 # TODO: OneToOne ??
eb6bf568 714 statut = models.ForeignKey('Statut', related_name='+', null=True)
fa1f7426
EMS
715 organisme_bstg = models.ForeignKey(
716 'OrganismeBstg', db_column='organisme_bstg', related_name='+',
717 verbose_name=u"organisme",
718 help_text=(
719 u"Si détaché (DET) ou mis à disposition (MAD), "
720 u"préciser l'organisme."
721 ), null=True, blank=True
722 )
ca1a7b76 723
83b7692b 724 # Recrutement
2d4d2fcf 725 remplacement = models.BooleanField(default=False)
fa1f7426
EMS
726 remplacement_de = models.ForeignKey(
727 'self', related_name='+', help_text=u"Taper le nom de l'employé",
728 null=True, blank=True
729 )
730 statut_residence = models.CharField(
731 u"statut", max_length=10, default='local', null=True,
732 choices=STATUT_RESIDENCE_CHOICES
733 )
ca1a7b76 734
83b7692b 735 # Rémunération
fa1f7426
EMS
736 classement = models.ForeignKey(
737 'Classement', db_column='classement', related_name='+', null=True,
738 blank=True
739 )
740 regime_travail = models.DecimalField(
741 u"régime de travail", max_digits=12, null=True, decimal_places=2,
742 default=REGIME_TRAVAIL_DEFAULT, help_text="% du temps complet"
743 )
744 regime_travail_nb_heure_semaine = models.DecimalField(
745 u"nb. heures par semaine", max_digits=12,
746 decimal_places=2, null=True,
747 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
748 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
749 )
7abc6d45 750
751 # Occupation du Poste par cet Employe (anciennement "mandat")
fa1f7426
EMS
752 date_debut = models.DateField(u"date de début d'occupation de poste")
753 date_fin = models.DateField(
754 u"Date de fin d'occupation de poste", null=True, blank=True
755 )
ca1a7b76 756
2d4d2fcf 757 # Comptes
758 # TODO?
ca1a7b76 759
6e4600ef 760 class Meta:
37868f0b 761 abstract = True
49449367 762 ordering = ['employe__nom', ]
3f5f3898 763 verbose_name = u"Dossier"
8c1ae2b3 764 verbose_name_plural = "Dossiers"
ca1a7b76 765
65f9fac8 766 def salaire_theorique(self):
767 annee = date.today().year
768 coeff = self.classement.coefficient
769 implantation = self.poste.implantation
770 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
ca1a7b76 771
65f9fac8 772 montant = coeff * point.valeur
773 devise = point.devise
fa1f7426 774 return {'montant': montant, 'devise': devise}
ca1a7b76 775
83b7692b 776 def __unicode__(self):
8c1ae2b3 777 poste = self.poste.nom
778 if self.employe.genre == 'F':
ca1a7b76 779 poste = self.poste.nom_feminin
8c1ae2b3 780 return u'%s - %s' % (self.employe, poste)
83b7692b 781
aff1a4c6 782 prefix_implantation = "poste__implantation__region"
fa1f7426 783
aff1a4c6
PP
784 def get_regions(self):
785 return [self.poste.implantation.region]
786
3ebc0952 787 def remunerations(self):
838bc59d
OL
788 key = "%s_remunerations" % self._meta.app_label
789 remunerations = getattr(self, key)
790 return remunerations.all().order_by('-date_debut')
3ebc0952 791
02e69aa2 792 def remunerations_en_cours(self):
838bc59d
OL
793 q = Q(date_fin__exact=None) | Q(date_fin__gt=datetime.date.today())
794 return self.remunerations().all().filter(q).order_by('date_debut')
02e69aa2 795
09aa8374
OL
796 def get_salaire(self):
797 try:
fa1f7426
EMS
798 return [r for r in self.remunerations().order_by('-date_debut')
799 if r.type_id == 1][0]
09aa8374
OL
800 except:
801 return None
3ebc0952 802
838bc59d
OL
803 def get_salaire_euros(self):
804 tx = self.taux_devise()
805 return (float)(tx) * (float)(self.salaire)
806
807 def get_remunerations_brutes(self):
808 """
809 1 Salaire de base
810 3 Indemnité de base
811 4 Indemnité d'expatriation
812 5 Indemnité pour frais
813 6 Indemnité de logement
814 7 Indemnité de fonction
815 8 Indemnité de responsabilité
816 9 Indemnité de transport
817 10 Indemnité compensatrice
818 11 Indemnité de subsistance
819 12 Indemnité différentielle
820 13 Prime d'installation
821 14 Billet d'avion
822 15 Déménagement
823 16 Indemnité de départ
824 18 Prime de 13ième mois
825 19 Prime d'intérim
826 """
fa1f7426
EMS
827 ids = [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
828 return [r for r in self.remunerations_en_cours().all()
829 if r.type_id in ids]
838bc59d
OL
830
831 def get_charges_salariales(self):
832 """
833 20 Charges salariales ?
834 """
fa1f7426
EMS
835 ids = [20]
836 return [r for r in self.remunerations_en_cours().all()
837 if r.type_id in ids]
838bc59d 838
838bc59d
OL
839 def get_charges_patronales(self):
840 """
841 17 Charges patronales
842 """
fa1f7426
EMS
843 ids = [17]
844 return [r for r in self.remunerations_en_cours().all()
845 if r.type_id in ids]
838bc59d 846
552d0db7
OL
847 def get_remunerations_tierces(self):
848 """
849 2 Salaire MAD
850 """
fa1f7426
EMS
851 return [r for r in self.remunerations_en_cours().all()
852 if r.type_id in (2,)]
552d0db7
OL
853
854 # DEVISE LOCALE
855
856 def get_total_local_charges_salariales(self):
bc17b82c 857 devise = self.poste.get_devise()
552d0db7
OL
858 total = 0.0
859 for r in self.get_charges_salariales():
bc17b82c
OL
860 if r.devise != devise:
861 return None
862 total += float(r.montant)
552d0db7
OL
863 return total
864
865 def get_total_local_charges_patronales(self):
bc17b82c 866 devise = self.poste.get_devise()
552d0db7
OL
867 total = 0.0
868 for r in self.get_charges_patronales():
bc17b82c
OL
869 if r.devise != devise:
870 return None
552d0db7
OL
871 total += float(r.montant)
872 return total
873
874 def get_local_salaire_brut(self):
875 """
876 somme des rémuérations brutes
877 """
878 devise = self.poste.get_devise()
879 total = 0.0
880 for r in self.get_remunerations_brutes():
881 if r.devise != devise:
882 return None
883 total += float(r.montant)
884 return total
885
886 def get_local_salaire_net(self):
887 """
888 salaire brut - charges salariales
889 """
890 devise = self.poste.get_devise()
891 total_charges = 0.0
892 for r in self.get_charges_salariales():
893 if r.devise != devise:
894 return None
895 total_charges += float(r.montant)
896 return self.get_local_salaire_brut() - total_charges
897
898 def get_local_couts_auf(self):
899 """
900 salaire net + charges patronales
901 """
902 devise = self.poste.get_devise()
903 total_charges = 0.0
904 for r in self.get_charges_patronales():
905 if r.devise != devise:
906 return None
907 total_charges += float(r.montant)
908 return self.get_local_salaire_net() + total_charges
909
910 def get_total_local_remunerations_tierces(self):
911 devise = self.poste.get_devise()
912 total = 0.0
913 for r in self.get_remunerations_tierces():
914 if r.devise != devise:
915 return None
916 total += float(r.montant)
917 return total
918
919 # DEVISE EURO
920
921 def get_total_charges_salariales(self):
922 total = 0.0
923 for r in self.get_charges_salariales():
924 total += r.montant_euros()
925 return total
926
838bc59d
OL
927 def get_total_charges_patronales(self):
928 total = 0.0
929 for r in self.get_charges_patronales():
930 total += r.montant_euros()
931 return total
932
933 def get_salaire_brut(self):
934 """
935 somme des rémuérations brutes
936 """
937 total = 0.0
938 for r in self.get_remunerations_brutes():
939 total += r.montant_euros()
940 return total
941
942 def get_salaire_net(self):
943 """
944 salaire brut - charges salariales
945 """
946 total_charges = 0.0
947 for r in self.get_charges_salariales():
948 total_charges += r.montant_euros()
949 return self.get_salaire_brut() - total_charges
950
951 def get_couts_auf(self):
952 """
953 salaire net + charges patronales
954 """
955 total_charges = 0.0
956 for r in self.get_charges_patronales():
957 total_charges += r.montant_euros()
958 return self.get_salaire_net() + total_charges
959
838bc59d
OL
960 def get_total_remunerations_tierces(self):
961 total = 0.0
962 for r in self.get_remunerations_tierces():
963 total += r.montant_euros()
964 return total
965
5db1c5a3
DB
966 def premier_contrat(self):
967 """contrat avec plus petite date de début"""
968 try:
db265492
EMS
969 contrat = self.rh_contrats.exclude(date_debut=None) \
970 .order_by('date_debut')[0]
5db1c5a3
DB
971 except IndexError, Contrat.DoesNotExist:
972 contrat = None
973 return contrat
db265492 974
5db1c5a3
DB
975 def dernier_contrat(self):
976 """contrat avec plus grande date de fin"""
977 try:
db265492
EMS
978 contrat = self.rh_contrats.exclude(date_debut=None) \
979 .order_by('-date_debut')[0]
5db1c5a3
DB
980 except IndexError, Contrat.DoesNotExist:
981 contrat = None
982 return contrat
983
bfb5e43e
EMS
984 def actif(self):
985 today = date.today()
986 return (self.date_debut is None or self.date_debut <= today) \
987 and (self.date_fin is None or self.date_fin >= today) \
988 and not (self.date_fin is None and self.date_debut is None)
989
22343fe7 990
37868f0b
NC
991class Dossier(Dossier_):
992 __doc__ = Dossier_.__doc__
4ba84959
EMS
993 poste = models.ForeignKey(
994 Poste, db_column='poste', related_name='rh_dossiers',
0b0545bd 995 help_text=u"Taper le nom du poste ou du type de poste",
4ba84959 996 )
fa1f7426
EMS
997 employe = models.ForeignKey(
998 'Employe', db_column='employe',
999 help_text=u"Taper le nom de l'employé",
4ba84959
EMS
1000 related_name='rh_dossiers', verbose_name=u"employé"
1001 )
fa1f7426 1002 principal = models.BooleanField(
c1f5d83c 1003 u"dossier principal", default=True,
fa1f7426
EMS
1004 help_text=(
1005 u"Ce dossier est pour le principal poste occupé par l'employé"
1006 )
1007 )
37868f0b
NC
1008
1009
fc917340 1010class DossierPiece_(models.Model):
fa1f7426
EMS
1011 """
1012 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
7abc6d45 1013 Ex.: Lettre de motivation.
1014 """
fa1f7426
EMS
1015 nom = models.CharField(max_length=255)
1016 fichier = models.FileField(
1017 upload_to=dossier_piece_dispatch, storage=storage_prive
1018 )
83b7692b 1019
6e4600ef 1020 class Meta:
fc917340 1021 abstract = True
6e4600ef 1022 ordering = ['nom']
ca1a7b76 1023
6e4600ef 1024 def __unicode__(self):
1025 return u'%s' % (self.nom)
1026
fa1f7426 1027
fc917340 1028class DossierPiece(DossierPiece_):
fa1f7426 1029 dossier = models.ForeignKey(
4ba84959 1030 Dossier, db_column='dossier', related_name='rh_dossierpieces'
fa1f7426
EMS
1031 )
1032
fc917340 1033
4ba84959
EMS
1034class DossierCommentaire(Commentaire):
1035 dossier = models.ForeignKey(
1036 Dossier, db_column='dossier', related_name='commentaires'
1037 )
fc917340 1038
fa1f7426 1039
e84c8ef1 1040class DossierComparaison_(models.Model, DevisableMixin):
1d0f4eef
OL
1041 """
1042 Photo d'une comparaison salariale au moment de l'embauche.
1043 """
16b1454e
OL
1044 objects = DossierComparaisonManager()
1045
fa1f7426
EMS
1046 implantation = models.ForeignKey(
1047 ref.Implantation, related_name="+", null=True, blank=True
1048 )
1d0f4eef
OL
1049 poste = models.CharField(max_length=255, null=True, blank=True)
1050 personne = models.CharField(max_length=255, null=True, blank=True)
1051 montant = models.IntegerField(null=True)
fa1f7426
EMS
1052 devise = models.ForeignKey(
1053 'Devise', related_name='+', null=True, blank=True
1054 )
1d0f4eef 1055
fc917340
OL
1056 class Meta:
1057 abstract = True
1058
3b14230d
OL
1059 def __unicode__(self):
1060 return "%s (%s)" % (self.poste, self.personne)
1061
1d0f4eef 1062
fc917340 1063class DossierComparaison(DossierComparaison_):
4ba84959
EMS
1064 dossier = models.ForeignKey(
1065 Dossier, related_name='rh_comparaisons'
1066 )
2d4d2fcf 1067
fa1f7426 1068
07b40eda 1069### RÉMUNÉRATION
ca1a7b76 1070
d6985a3a 1071class RemunerationMixin(AUFMetadata):
fa1f7426 1072
9afaa55e 1073 # Identification
fa1f7426
EMS
1074 type = models.ForeignKey(
1075 'TypeRemuneration', db_column='type', related_name='+',
1076 verbose_name=u"type de rémunération"
1077 )
1078 type_revalorisation = models.ForeignKey(
1079 'TypeRevalorisation', db_column='type_revalorisation',
1080 related_name='+', verbose_name=u"type de revalorisation",
1081 null=True, blank=True
1082 )
1083 montant = models.DecimalField(
1084 null=True, blank=True,
1085 default=0, max_digits=12, decimal_places=2
1086 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1087 devise = models.ForeignKey('Devise', db_column='devise', related_name='+')
1088
2d4d2fcf 1089 # commentaire = precision
1090 commentaire = models.CharField(max_length=255, null=True, blank=True)
fa1f7426 1091
2d4d2fcf 1092 # date_debut = anciennement date_effectif
fa1f7426
EMS
1093 date_debut = models.DateField(u"date de début", null=True, blank=True)
1094 date_fin = models.DateField(u"date de fin", null=True, blank=True)
ca1a7b76 1095
fc62be5d
EMS
1096 objects = RemunerationManager()
1097
ca1a7b76 1098 class Meta:
2d4d2fcf 1099 abstract = True
6e4600ef 1100 ordering = ['type__nom', '-date_fin']
ca1a7b76 1101
6e4600ef 1102 def __unicode__(self):
1103 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
ca1a7b76 1104
fa1f7426 1105
e84c8ef1 1106class Remuneration_(RemunerationMixin, DevisableMixin):
fa1f7426
EMS
1107 """
1108 Structure de rémunération (données budgétaires) en situation normale
ca1a7b76
EMS
1109 pour un Dossier. Si un Evenement existe, utiliser la structure de
1110 rémunération EvenementRemuneration de cet événement.
2d4d2fcf 1111 """
83b7692b 1112
1113 def montant_mois(self):
1114 return round(self.montant / 12, 2)
1115
626beb4d 1116 def montant_avec_regime(self):
fa1f7426 1117 return round(self.montant * (self.dossier.regime_travail / 100), 2)
626beb4d 1118
83b7692b 1119 def montant_euro_mois(self):
e84c8ef1 1120 return round(self.montant_euros() / 12, 2)
ca1a7b76 1121
9afaa55e 1122 def __unicode__(self):
1123 try:
1124 devise = self.devise.code
1125 except:
1126 devise = "???"
1127 return "%s %s" % (self.montant, devise)
83b7692b 1128
6e7c919b
NC
1129 class Meta:
1130 abstract = True
c1195471
OL
1131 verbose_name = u"Rémunération"
1132 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
1133
1134
1135class Remuneration(Remuneration_):
4ba84959
EMS
1136 dossier = models.ForeignKey(
1137 Dossier, db_column='dossier', related_name='rh_remunerations'
1138 )
6e7c919b 1139
2d4d2fcf 1140
1141### CONTRATS
c41b7fcc
OL
1142
1143class ContratManager(NoDeleteManager):
1144 def get_query_set(self):
fa1f7426
EMS
1145 return super(ContratManager, self).get_query_set() \
1146 .select_related('dossier', 'dossier__poste')
1147
c41b7fcc 1148
fc917340 1149class Contrat_(AUFMetadata):
fa1f7426
EMS
1150 """
1151 Document juridique qui encadre la relation de travail d'un Employe
ca1a7b76 1152 pour un Poste particulier. Pour un Dossier (qui documente cette
2d4d2fcf 1153 relation de travail) plusieurs contrats peuvent être associés.
1154 """
c41b7fcc 1155 objects = ContratManager()
fa1f7426
EMS
1156 type_contrat = models.ForeignKey(
1157 'TypeContrat', db_column='type_contrat',
1158 verbose_name=u'type de contrat', related_name='+'
1159 )
1160 date_debut = models.DateField(u"date de début")
1161 date_fin = models.DateField(u"date de fin", null=True, blank=True)
1162 fichier = models.FileField(
1163 upload_to=contrat_dispatch, storage=storage_prive, null=True,
1164 blank=True
1165 )
6e4600ef 1166
1167 class Meta:
fc917340 1168 abstract = True
a2c3ad52 1169 ordering = ['dossier__employe__nom']
c1195471
OL
1170 verbose_name = u"Contrat"
1171 verbose_name_plural = u"Contrats"
ca1a7b76 1172
6e4600ef 1173 def __unicode__(self):
8c1ae2b3 1174 return u'%s - %s' % (self.dossier, self.id)
fc917340 1175
fa1f7426 1176
fc917340 1177class Contrat(Contrat_):
4ba84959
EMS
1178 dossier = models.ForeignKey(
1179 Dossier, db_column='dossier', related_name='rh_contrats'
1180 )
f31ddfa0 1181
83b7692b 1182
ca1a7b76 1183### RÉFÉRENCES RH
83b7692b 1184
7bf28694 1185class CategorieEmploi(AUFMetadata):
fa1f7426
EMS
1186 """
1187 Catégorie utilisée dans la gestion des Postes.
6e4600ef 1188 Catégorie supérieure à TypePoste.
1189 """
e9bbd6ba 1190 nom = models.CharField(max_length=255)
ca1a7b76 1191
8c1ae2b3 1192 class Meta:
321fe481 1193 ordering = ('nom',)
7bf28694
EMS
1194 verbose_name = u"catégorie d'emploi"
1195 verbose_name_plural = u"catégories d'emploi"
ca1a7b76 1196
6e4600ef 1197 def __unicode__(self):
321fe481
EMS
1198 return self.nom
1199
1200
1201class FamilleProfessionnelle(models.Model):
1202 """
1203 Famille professionnelle d'un poste.
1204 """
1205 nom = models.CharField(max_length=100)
1206
1207 class Meta:
1208 ordering = ('nom',)
1209 verbose_name = u'famille professionnelle'
1210 verbose_name_plural = u'familles professionnelles'
1211
1212 def __unicode__(self):
1213 return self.nom
e9bbd6ba 1214
fa1f7426 1215
d6985a3a 1216class TypePoste(AUFMetadata):
fa1f7426
EMS
1217 """
1218 Catégorie de Poste.
6e4600ef 1219 """
e9bbd6ba 1220 nom = models.CharField(max_length=255)
fa1f7426
EMS
1221 nom_feminin = models.CharField(u"nom féminin", max_length=255)
1222 is_responsable = models.BooleanField(
1223 u"poste de responsabilité", default=False
1224 )
7bf28694
EMS
1225 categorie_emploi = models.ForeignKey(
1226 CategorieEmploi, db_column='categorie_emploi', related_name='+',
1227 verbose_name=u"catégorie d'emploi"
fa1f7426 1228 )
321fe481
EMS
1229 famille_professionnelle = models.ForeignKey(
1230 FamilleProfessionnelle, related_name='types_de_poste',
1231 verbose_name=u"famille professionnelle", blank=True, null=True
1232 )
e9bbd6ba 1233
6e4600ef 1234 class Meta:
1235 ordering = ['nom']
c1195471
OL
1236 verbose_name = u"Type de poste"
1237 verbose_name_plural = u"Types de poste"
ca1a7b76 1238
e9bbd6ba 1239 def __unicode__(self):
6e4600ef 1240 return u'%s' % (self.nom)
e9bbd6ba 1241
e9bbd6ba 1242TYPE_PAIEMENT_CHOICES = (
a3e3bde0
JPC
1243 (u'Régulier', u'Régulier'),
1244 (u'Ponctuel', u'Ponctuel'),
e9bbd6ba 1245)
1246
1247NATURE_REMUNERATION_CHOICES = (
a3e3bde0
JPC
1248 (u'Accessoire', u'Accessoire'),
1249 (u'Charges', u'Charges'),
1250 (u'Indemnité', u'Indemnité'),
1251 (u'RAS', u'Rémunération autre source'),
1252 (u'Traitement', u'Traitement'),
e9bbd6ba 1253)
1254
fa1f7426 1255
d6985a3a 1256class TypeRemuneration(AUFMetadata):
fa1f7426
EMS
1257 """
1258 Catégorie de Remuneration.
6e4600ef 1259 """
7ba822a6
OL
1260 objects = TypeRemunerationManager()
1261
e9bbd6ba 1262 nom = models.CharField(max_length=255)
fa1f7426
EMS
1263 type_paiement = models.CharField(
1264 u"type de paiement", max_length=30, choices=TYPE_PAIEMENT_CHOICES
1265 )
1266 nature_remuneration = models.CharField(
1267 u"nature de la rémunération", max_length=30,
1268 choices=NATURE_REMUNERATION_CHOICES
1269 )
7ba822a6 1270 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
ca1a7b76 1271
8c1ae2b3 1272 class Meta:
1273 ordering = ['nom']
c1195471
OL
1274 verbose_name = u"Type de rémunération"
1275 verbose_name_plural = u"Types de rémunération"
9afaa55e 1276
1277 def __unicode__(self):
7ba822a6
OL
1278 if self.archive:
1279 archive = u"(archivé)"
1280 else:
fa1f7426 1281 archive = ""
7ba822a6 1282 return u'%s %s' % (self.nom, archive)
ca1a7b76 1283
fa1f7426 1284
d6985a3a 1285class TypeRevalorisation(AUFMetadata):
fa1f7426
EMS
1286 """
1287 Justification du changement de la Remuneration.
6e4600ef 1288 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 1289 """
e9bbd6ba 1290 nom = models.CharField(max_length=255)
ca1a7b76 1291
8c1ae2b3 1292 class Meta:
1293 ordering = ['nom']
c1195471
OL
1294 verbose_name = u"Type de revalorisation"
1295 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 1296
1297 def __unicode__(self):
6e4600ef 1298 return u'%s' % (self.nom)
ca1a7b76 1299
fa1f7426 1300
d6985a3a 1301class Service(AUFMetadata):
fa1f7426
EMS
1302 """
1303 Unité administrative où les Postes sont rattachés.
6e4600ef 1304 """
7ba822a6
OL
1305 objects = ServiceManager()
1306
f614ca5c 1307 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
6e4600ef 1308 nom = models.CharField(max_length=255)
ca1a7b76 1309
9afaa55e 1310 class Meta:
1311 ordering = ['nom']
c1195471
OL
1312 verbose_name = u"Service"
1313 verbose_name_plural = u"Services"
e9bbd6ba 1314
6e4600ef 1315 def __unicode__(self):
41ced34a
OL
1316 if self.archive:
1317 archive = u"(archivé)"
1318 else:
fa1f7426 1319 archive = ""
41ced34a 1320 return u'%s %s' % (self.nom, archive)
6e4600ef 1321
e9bbd6ba 1322
1323TYPE_ORGANISME_CHOICES = (
1324 ('MAD', 'Mise à disposition'),
1325 ('DET', 'Détachement'),
1326)
1327
fa1f7426 1328
d6985a3a 1329class OrganismeBstg(AUFMetadata):
fa1f7426
EMS
1330 """
1331 Organisation d'où provient un Employe mis à disposition (MAD) de
6e4600ef 1332 ou détaché (DET) à l'AUF à titre gratuit.
ca1a7b76 1333
6e4600ef 1334 (BSTG = bien et service à titre gratuit.)
1335 """
e9bbd6ba 1336 nom = models.CharField(max_length=255)
1337 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
ca1a7b76 1338 pays = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 1339 db_column='pays',
1340 related_name='organismes_bstg',
1341 null=True, blank=True)
9afaa55e 1342
1343 class Meta:
1344 ordering = ['type', 'nom']
c1195471
OL
1345 verbose_name = u"Organisme BSTG"
1346 verbose_name_plural = u"Organismes BSTG"
9afaa55e 1347
6e4600ef 1348 def __unicode__(self):
8c1ae2b3 1349 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 1350
aff1a4c6 1351 prefix_implantation = "pays__region"
fa1f7426 1352
aff1a4c6
PP
1353 def get_regions(self):
1354 return [self.pays.region]
1355
1356
d6985a3a 1357class Statut(AUFMetadata):
fa1f7426
EMS
1358 """
1359 Statut de l'Employe dans le cadre d'un Dossier particulier.
6e4600ef 1360 """
9afaa55e 1361 # Identification
fa1f7426
EMS
1362 code = models.CharField(
1363 max_length=25, unique=True,
1364 help_text=(
1365 u"Saisir un code court mais lisible pour ce statut : "
1366 u"le code est utilisé pour associer les statuts aux autres "
1367 u"données tout en demeurant plus lisible qu'un identifiant "
1368 u"numérique."
1369 )
1370 )
e9bbd6ba 1371 nom = models.CharField(max_length=255)
e9bbd6ba 1372
6e4600ef 1373 class Meta:
1374 ordering = ['code']
c1195471
OL
1375 verbose_name = u"Statut d'employé"
1376 verbose_name_plural = u"Statuts d'employé"
ca1a7b76 1377
9afaa55e 1378 def __unicode__(self):
1379 return u'%s : %s' % (self.code, self.nom)
1380
83b7692b 1381
e9bbd6ba 1382TYPE_CLASSEMENT_CHOICES = (
6e4600ef 1383 ('S', 'S -Soutien'),
1384 ('T', 'T - Technicien'),
1385 ('P', 'P - Professionel'),
1386 ('C', 'C - Cadre'),
1387 ('D', 'D - Direction'),
1388 ('SO', 'SO - Sans objet [expatriés]'),
1389 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 1390)
83b7692b 1391
fa1f7426 1392
952ecb37
OL
1393class ClassementManager(models.Manager):
1394 """
1395 Ordonner les spcéfiquement les classements.
1396 """
1397 def get_query_set(self):
1398 qs = super(self.__class__, self).get_query_set()
fa1f7426
EMS
1399 qs = qs.extra(select={
1400 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1401 })
6559f73b 1402 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
952ecb37
OL
1403 return qs.all()
1404
6e7c919b 1405
d6985a3a 1406class Classement_(AUFMetadata):
fa1f7426
EMS
1407 """
1408 Éléments de classement de la
6e4600ef 1409 "Grille générique de classement hiérarchique".
ca1a7b76
EMS
1410
1411 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
6e4600ef 1412 classement dans la grille. Le classement donne le coefficient utilisé dans:
1413
1414 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1415 """
952ecb37
OL
1416 objects = ClassementManager()
1417
9afaa55e 1418 # Identification
e9bbd6ba 1419 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
fa1f7426
EMS
1420 echelon = models.IntegerField(u"échelon", blank=True, default=0)
1421 degre = models.IntegerField(u"degré", blank=True, default=0)
1422 coefficient = models.FloatField(u"coefficient", default=0, null=True)
1423
9afaa55e 1424 # Méta
6e4600ef 1425 # annee # au lieu de date_debut et date_fin
1426 commentaire = models.TextField(null=True, blank=True)
ca1a7b76 1427
6e4600ef 1428 class Meta:
6e7c919b 1429 abstract = True
fa1f7426 1430 ordering = ['type', 'echelon', 'degre', 'coefficient']
c1195471
OL
1431 verbose_name = u"Classement"
1432 verbose_name_plural = u"Classements"
e9bbd6ba 1433
1434 def __unicode__(self):
22343fe7 1435 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
e9bbd6ba 1436
fa1f7426 1437
6e7c919b
NC
1438class Classement(Classement_):
1439 __doc__ = Classement_.__doc__
1440
1441
d6985a3a 1442class TauxChange_(AUFMetadata):
fa1f7426
EMS
1443 """
1444 Taux de change de la devise vers l'euro (EUR)
6e4600ef 1445 pour chaque année budgétaire.
7abc6d45 1446 """
9afaa55e 1447 # Identification
8d3e2fff 1448 devise = models.ForeignKey('Devise', db_column='devise')
fa1f7426
EMS
1449 annee = models.IntegerField(u"année")
1450 taux = models.FloatField(u"taux vers l'euro")
6e7c919b 1451
6e4600ef 1452 class Meta:
6e7c919b 1453 abstract = True
8c1ae2b3 1454 ordering = ['-annee', 'devise__code']
c1195471
OL
1455 verbose_name = u"Taux de change"
1456 verbose_name_plural = u"Taux de change"
ca1a7b76 1457
6e4600ef 1458 def __unicode__(self):
8c1ae2b3 1459 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1460
6e7c919b
NC
1461
1462class TauxChange(TauxChange_):
1463 __doc__ = TauxChange_.__doc__
1464
fa1f7426 1465
701f3bea 1466class ValeurPointManager(NoDeleteManager):
105dd778 1467
701f3bea 1468 def get_query_set(self):
fa1f7426
EMS
1469 return super(ValeurPointManager, self).get_query_set() \
1470 .select_related('devise', 'implantation')
701f3bea 1471
6e7c919b 1472
d6985a3a 1473class ValeurPoint_(AUFMetadata):
fa1f7426
EMS
1474 """
1475 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
ca1a7b76 1476 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1477 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1478
1479 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1480 """
ca1a7b76 1481
09aa8374 1482 actuelles = ValeurPointManager()
701f3bea 1483
8277a35b 1484 valeur = models.FloatField(null=True)
f614ca5c 1485 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
ca1a7b76 1486 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1487 db_column='implantation',
1488 related_name='%(app_label)s_valeur_point')
9afaa55e 1489 # Méta
e9bbd6ba 1490 annee = models.IntegerField()
9afaa55e 1491
6e4600ef 1492 class Meta:
701f3bea 1493 ordering = ['-annee', 'implantation__nom']
6e7c919b 1494 abstract = True
c1195471
OL
1495 verbose_name = u"Valeur du point"
1496 verbose_name_plural = u"Valeurs du point"
6e0bbb73 1497
9afaa55e 1498 def __unicode__(self):
fa1f7426
EMS
1499 return u'%s %s %s [%s] %s' % (
1500 self.devise.code, self.annee, self.valeur,
1501 self.implantation.nom_court, self.devise.nom
1502 )
6e7c919b
NC
1503
1504
1505class ValeurPoint(ValeurPoint_):
1506 __doc__ = ValeurPoint_.__doc__
1507
e9bbd6ba 1508
d6985a3a 1509class Devise(AUFMetadata):
6e4600ef 1510 """
fa1f7426
EMS
1511 Devise monétaire.
1512 """
4bdadf8b
OL
1513 objects = DeviseManager()
1514
edb35076 1515 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
fa1f7426 1516 code = models.CharField(max_length=10, unique=True)
e9bbd6ba 1517 nom = models.CharField(max_length=255)
1518
6e4600ef 1519 class Meta:
1520 ordering = ['code']
c1195471
OL
1521 verbose_name = u"Devise"
1522 verbose_name_plural = u"Devises"
ca1a7b76 1523
e9bbd6ba 1524 def __unicode__(self):
1525 return u'%s - %s' % (self.code, self.nom)
1526
fa1f7426 1527
d6985a3a 1528class TypeContrat(AUFMetadata):
fa1f7426
EMS
1529 """
1530 Type de contrat.
6e4600ef 1531 """
e9bbd6ba 1532 nom = models.CharField(max_length=255)
6e4600ef 1533 nom_long = models.CharField(max_length=255)
49f9f116 1534
8c1ae2b3 1535 class Meta:
1536 ordering = ['nom']
c1195471
OL
1537 verbose_name = u"Type de contrat"
1538 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1539
9afaa55e 1540 def __unicode__(self):
1541 return u'%s' % (self.nom)
ca1a7b76
EMS
1542
1543
2d4d2fcf 1544### AUTRES
1545
8c8ffc4f 1546class ResponsableImplantationProxy(ref.Implantation):
7bf28694 1547
6fb68b2f
DB
1548 def save(self):
1549 pass
1550
8c8ffc4f
OL
1551 class Meta:
1552 proxy = True
1553 verbose_name = u"Responsable d'implantation"
1554 verbose_name_plural = u"Responsables d'implantation"
1555
1556
1557class ResponsableImplantation(models.Model):
fa1f7426
EMS
1558 """
1559 Le responsable d'une implantation.
30be56d5 1560 Anciennement géré sur le Dossier du responsable.
1561 """
fa1f7426
EMS
1562 employe = models.ForeignKey(
1563 'Employe', db_column='employe', related_name='+', null=True,
1564 blank=True
1565 )
1566 implantation = models.OneToOneField(
1567 "ResponsableImplantationProxy", db_column='implantation',
1568 related_name='responsable', unique=True
1569 )
30be56d5 1570
1571 def __unicode__(self):
1572 return u'%s : %s' % (self.implantation, self.employe)
ca1a7b76 1573
30be56d5 1574 class Meta:
1575 ordering = ['implantation__nom']
8c1ae2b3 1576 verbose_name = "Responsable d'implantation"
1577 verbose_name_plural = "Responsables d'implantation"