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