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