[#3129] Surligner les sélections dans le rapport de masse salariale
[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(
169 'Service', db_column='service', related_name='+',
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
22343fe7 970
37868f0b
NC
971class Dossier(Dossier_):
972 __doc__ = Dossier_.__doc__
0b0545bd
OL
973 poste = models.ForeignKey('%s.Poste' % app_context(),
974 db_column='poste',
975 related_name='%(app_label)s_dossiers',
976 help_text=u"Taper le nom du poste ou du type de poste",
977 )
fa1f7426
EMS
978 employe = models.ForeignKey(
979 'Employe', db_column='employe',
980 help_text=u"Taper le nom de l'employé",
981 related_name='%(app_label)s_dossiers', verbose_name=u"employé")
982 principal = models.BooleanField(
c1f5d83c 983 u"dossier principal", default=True,
fa1f7426
EMS
984 help_text=(
985 u"Ce dossier est pour le principal poste occupé par l'employé"
986 )
987 )
37868f0b
NC
988
989
fc917340 990class DossierPiece_(models.Model):
fa1f7426
EMS
991 """
992 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
7abc6d45 993 Ex.: Lettre de motivation.
994 """
fa1f7426
EMS
995 dossier = models.ForeignKey(
996 '%s.Dossier' % app_context(),
997 db_column='dossier', related_name='%(app_label)s_dossierpieces'
998 )
999 nom = models.CharField(max_length=255)
1000 fichier = models.FileField(
1001 upload_to=dossier_piece_dispatch, storage=storage_prive
1002 )
83b7692b 1003
6e4600ef 1004 class Meta:
fc917340 1005 abstract = True
6e4600ef 1006 ordering = ['nom']
ca1a7b76 1007
6e4600ef 1008 def __unicode__(self):
1009 return u'%s' % (self.nom)
1010
fa1f7426 1011
fc917340 1012class DossierPiece(DossierPiece_):
a4125771 1013 pass
fc917340 1014
fa1f7426 1015
fc917340 1016class DossierCommentaire_(Commentaire):
fa1f7426
EMS
1017 dossier = models.ForeignKey(
1018 '%s.Dossier' % app_context(), db_column='dossier', related_name='+'
1019 )
1020
fc917340
OL
1021 class Meta:
1022 abstract = True
1023
fa1f7426 1024
fc917340 1025class DossierCommentaire(DossierCommentaire_):
a4125771 1026 pass
fc917340 1027
fa1f7426 1028
e84c8ef1 1029class DossierComparaison_(models.Model, DevisableMixin):
1d0f4eef
OL
1030 """
1031 Photo d'une comparaison salariale au moment de l'embauche.
1032 """
fa1f7426
EMS
1033 dossier = models.ForeignKey(
1034 '%s.Dossier' % app_context(), related_name='%(app_label)s_comparaisons'
1035 )
16b1454e
OL
1036 objects = DossierComparaisonManager()
1037
fa1f7426
EMS
1038 implantation = models.ForeignKey(
1039 ref.Implantation, related_name="+", null=True, blank=True
1040 )
1d0f4eef
OL
1041 poste = models.CharField(max_length=255, null=True, blank=True)
1042 personne = models.CharField(max_length=255, null=True, blank=True)
1043 montant = models.IntegerField(null=True)
fa1f7426
EMS
1044 devise = models.ForeignKey(
1045 'Devise', related_name='+', null=True, blank=True
1046 )
1d0f4eef 1047
fc917340
OL
1048 class Meta:
1049 abstract = True
1050
3b14230d
OL
1051 def __unicode__(self):
1052 return "%s (%s)" % (self.poste, self.personne)
1053
1d0f4eef 1054
fc917340 1055class DossierComparaison(DossierComparaison_):
a4125771 1056 pass
2d4d2fcf 1057
fa1f7426 1058
07b40eda 1059### RÉMUNÉRATION
ca1a7b76 1060
d6985a3a 1061class RemunerationMixin(AUFMetadata):
fa1f7426
EMS
1062 dossier = models.ForeignKey(
1063 '%s.Dossier' % app_context(), db_column='dossier',
1064 related_name='%(app_label)s_remunerations'
1065 )
1066
9afaa55e 1067 # Identification
fa1f7426
EMS
1068 type = models.ForeignKey(
1069 'TypeRemuneration', db_column='type', related_name='+',
1070 verbose_name=u"type de rémunération"
1071 )
1072 type_revalorisation = models.ForeignKey(
1073 'TypeRevalorisation', db_column='type_revalorisation',
1074 related_name='+', verbose_name=u"type de revalorisation",
1075 null=True, blank=True
1076 )
1077 montant = models.DecimalField(
1078 null=True, blank=True,
1079 default=0, max_digits=12, decimal_places=2
1080 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1081 devise = models.ForeignKey('Devise', db_column='devise', related_name='+')
1082
2d4d2fcf 1083 # commentaire = precision
1084 commentaire = models.CharField(max_length=255, null=True, blank=True)
fa1f7426 1085
2d4d2fcf 1086 # date_debut = anciennement date_effectif
fa1f7426
EMS
1087 date_debut = models.DateField(u"date de début", null=True, blank=True)
1088 date_fin = models.DateField(u"date de fin", null=True, blank=True)
ca1a7b76
EMS
1089
1090 class Meta:
2d4d2fcf 1091 abstract = True
6e4600ef 1092 ordering = ['type__nom', '-date_fin']
ca1a7b76 1093
6e4600ef 1094 def __unicode__(self):
1095 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
ca1a7b76 1096
fa1f7426 1097
e84c8ef1 1098class Remuneration_(RemunerationMixin, DevisableMixin):
fa1f7426
EMS
1099 """
1100 Structure de rémunération (données budgétaires) en situation normale
ca1a7b76
EMS
1101 pour un Dossier. Si un Evenement existe, utiliser la structure de
1102 rémunération EvenementRemuneration de cet événement.
2d4d2fcf 1103 """
83b7692b 1104
1105 def montant_mois(self):
1106 return round(self.montant / 12, 2)
1107
626beb4d 1108 def montant_avec_regime(self):
fa1f7426 1109 return round(self.montant * (self.dossier.regime_travail / 100), 2)
626beb4d 1110
83b7692b 1111 def montant_euro_mois(self):
e84c8ef1 1112 return round(self.montant_euros() / 12, 2)
ca1a7b76 1113
9afaa55e 1114 def __unicode__(self):
1115 try:
1116 devise = self.devise.code
1117 except:
1118 devise = "???"
1119 return "%s %s" % (self.montant, devise)
83b7692b 1120
6e7c919b
NC
1121 class Meta:
1122 abstract = True
c1195471
OL
1123 verbose_name = u"Rémunération"
1124 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
1125
1126
1127class Remuneration(Remuneration_):
a4125771 1128 pass
6e7c919b 1129
2d4d2fcf 1130
1131### CONTRATS
c41b7fcc
OL
1132
1133class ContratManager(NoDeleteManager):
1134 def get_query_set(self):
fa1f7426
EMS
1135 return super(ContratManager, self).get_query_set() \
1136 .select_related('dossier', 'dossier__poste')
1137
c41b7fcc 1138
fc917340 1139class Contrat_(AUFMetadata):
fa1f7426
EMS
1140 """
1141 Document juridique qui encadre la relation de travail d'un Employe
ca1a7b76 1142 pour un Poste particulier. Pour un Dossier (qui documente cette
2d4d2fcf 1143 relation de travail) plusieurs contrats peuvent être associés.
1144 """
c41b7fcc 1145 objects = ContratManager()
fa1f7426
EMS
1146 dossier = models.ForeignKey(
1147 '%s.Dossier' % app_context(), db_column='dossier',
1148 related_name='%(app_label)s_contrats'
1149 )
1150 type_contrat = models.ForeignKey(
1151 'TypeContrat', db_column='type_contrat',
1152 verbose_name=u'type de contrat', related_name='+'
1153 )
1154 date_debut = models.DateField(u"date de début")
1155 date_fin = models.DateField(u"date de fin", null=True, blank=True)
1156 fichier = models.FileField(
1157 upload_to=contrat_dispatch, storage=storage_prive, null=True,
1158 blank=True
1159 )
6e4600ef 1160
1161 class Meta:
fc917340 1162 abstract = True
a2c3ad52 1163 ordering = ['dossier__employe__nom']
c1195471
OL
1164 verbose_name = u"Contrat"
1165 verbose_name_plural = u"Contrats"
ca1a7b76 1166
6e4600ef 1167 def __unicode__(self):
8c1ae2b3 1168 return u'%s - %s' % (self.dossier, self.id)
fc917340 1169
fa1f7426 1170
fc917340 1171class Contrat(Contrat_):
a4125771 1172 pass
2d4d2fcf 1173
1174### ÉVÉNEMENTS
1175
a4125771 1176#class Evenement_(AUFMetadata):
fa1f7426
EMS
1177# """
1178# Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
1179# d'un Dossier qui vient altérer des informations normales liées à un
1180# Dossier (ex.: la Remuneration).
1181#
a4125771 1182# Ex.: congé de maternité, maladie...
fa1f7426 1183#
a4125771
OL
1184# Lors de ces situations exceptionnelles, l'Employe a un régime de travail
1185# différent et une rémunération en conséquence. On souhaite toutefois
1186# conserver le Dossier intact afin d'éviter une re-saisie des données lors
1187# du retour à la normale.
1188# """
fa1f7426
EMS
1189# dossier = models.ForeignKey(
1190# '%s.Dossier' % app_context(), db_column='dossier',
1191# related_name='+'
1192# )
a4125771
OL
1193# nom = models.CharField(max_length=255)
1194# date_debut = models.DateField(verbose_name = u"Date de début")
1195# date_fin = models.DateField(verbose_name = u"Date de fin",
1196# null=True, blank=True)
1197#
1198# class Meta:
1199# abstract = True
1200# ordering = ['nom']
1201# verbose_name = u"Évènement"
1202# verbose_name_plural = u"Évènements"
fa1f7426 1203#
a4125771
OL
1204# def __unicode__(self):
1205# return u'%s' % (self.nom)
1206#
1207#
1208#class Evenement(Evenement_):
1209# __doc__ = Evenement_.__doc__
1210#
fa1f7426 1211#
a4125771 1212#class EvenementRemuneration_(RemunerationMixin):
fa1f7426
EMS
1213# """
1214# Structure de rémunération liée à un Evenement qui remplace
a4125771
OL
1215# temporairement la Remuneration normale d'un Dossier, pour toute la durée
1216# de l'Evenement.
1217# """
1218# evenement = models.ForeignKey("Evenement", db_column='evenement',
1219# related_name='+',
1220# verbose_name = u"Évènement")
1221# # TODO : le champ dossier hérité de Remuneration doit être dérivé
1222# # de l'Evenement associé
1223#
1224# class Meta:
1225# abstract = True
1226# ordering = ['evenement', 'type__nom', '-date_fin']
1227# verbose_name = u"Évènement - rémunération"
1228# verbose_name_plural = u"Évènements - rémunérations"
1229#
1230#
1231#class EvenementRemuneration(EvenementRemuneration_):
1232# __doc__ = EvenementRemuneration_.__doc__
1233#
1234# class Meta:
1235# abstract = True
1236#
1237#
1238#class EvenementRemuneration(EvenementRemuneration_):
1239# __doc__ = EvenementRemuneration_.__doc__
865ca9e0 1240# TODO? class ContratPiece(models.Model):
f31ddfa0 1241
83b7692b 1242
ca1a7b76 1243### RÉFÉRENCES RH
83b7692b 1244
7bf28694 1245class CategorieEmploi(AUFMetadata):
fa1f7426
EMS
1246 """
1247 Catégorie utilisée dans la gestion des Postes.
6e4600ef 1248 Catégorie supérieure à TypePoste.
1249 """
e9bbd6ba 1250 nom = models.CharField(max_length=255)
ca1a7b76 1251
8c1ae2b3 1252 class Meta:
321fe481 1253 ordering = ('nom',)
7bf28694
EMS
1254 verbose_name = u"catégorie d'emploi"
1255 verbose_name_plural = u"catégories d'emploi"
ca1a7b76 1256
6e4600ef 1257 def __unicode__(self):
321fe481
EMS
1258 return self.nom
1259
1260
1261class FamilleProfessionnelle(models.Model):
1262 """
1263 Famille professionnelle d'un poste.
1264 """
1265 nom = models.CharField(max_length=100)
1266
1267 class Meta:
1268 ordering = ('nom',)
1269 verbose_name = u'famille professionnelle'
1270 verbose_name_plural = u'familles professionnelles'
1271
1272 def __unicode__(self):
1273 return self.nom
e9bbd6ba 1274
fa1f7426 1275
d6985a3a 1276class TypePoste(AUFMetadata):
fa1f7426
EMS
1277 """
1278 Catégorie de Poste.
6e4600ef 1279 """
e9bbd6ba 1280 nom = models.CharField(max_length=255)
fa1f7426
EMS
1281 nom_feminin = models.CharField(u"nom féminin", max_length=255)
1282 is_responsable = models.BooleanField(
1283 u"poste de responsabilité", default=False
1284 )
7bf28694
EMS
1285 categorie_emploi = models.ForeignKey(
1286 CategorieEmploi, db_column='categorie_emploi', related_name='+',
1287 verbose_name=u"catégorie d'emploi"
fa1f7426 1288 )
321fe481
EMS
1289 famille_professionnelle = models.ForeignKey(
1290 FamilleProfessionnelle, related_name='types_de_poste',
1291 verbose_name=u"famille professionnelle", blank=True, null=True
1292 )
e9bbd6ba 1293
6e4600ef 1294 class Meta:
1295 ordering = ['nom']
c1195471
OL
1296 verbose_name = u"Type de poste"
1297 verbose_name_plural = u"Types de poste"
ca1a7b76 1298
e9bbd6ba 1299 def __unicode__(self):
6e4600ef 1300 return u'%s' % (self.nom)
e9bbd6ba 1301
e9bbd6ba 1302TYPE_PAIEMENT_CHOICES = (
a3e3bde0
JPC
1303 (u'Régulier', u'Régulier'),
1304 (u'Ponctuel', u'Ponctuel'),
e9bbd6ba 1305)
1306
1307NATURE_REMUNERATION_CHOICES = (
a3e3bde0
JPC
1308 (u'Accessoire', u'Accessoire'),
1309 (u'Charges', u'Charges'),
1310 (u'Indemnité', u'Indemnité'),
1311 (u'RAS', u'Rémunération autre source'),
1312 (u'Traitement', u'Traitement'),
e9bbd6ba 1313)
1314
fa1f7426 1315
d6985a3a 1316class TypeRemuneration(AUFMetadata):
fa1f7426
EMS
1317 """
1318 Catégorie de Remuneration.
6e4600ef 1319 """
7ba822a6
OL
1320 objects = TypeRemunerationManager()
1321
e9bbd6ba 1322 nom = models.CharField(max_length=255)
fa1f7426
EMS
1323 type_paiement = models.CharField(
1324 u"type de paiement", max_length=30, choices=TYPE_PAIEMENT_CHOICES
1325 )
1326 nature_remuneration = models.CharField(
1327 u"nature de la rémunération", max_length=30,
1328 choices=NATURE_REMUNERATION_CHOICES
1329 )
7ba822a6 1330 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
ca1a7b76 1331
8c1ae2b3 1332 class Meta:
1333 ordering = ['nom']
c1195471
OL
1334 verbose_name = u"Type de rémunération"
1335 verbose_name_plural = u"Types de rémunération"
9afaa55e 1336
1337 def __unicode__(self):
7ba822a6
OL
1338 if self.archive:
1339 archive = u"(archivé)"
1340 else:
fa1f7426 1341 archive = ""
7ba822a6 1342 return u'%s %s' % (self.nom, archive)
ca1a7b76 1343
fa1f7426 1344
d6985a3a 1345class TypeRevalorisation(AUFMetadata):
fa1f7426
EMS
1346 """
1347 Justification du changement de la Remuneration.
6e4600ef 1348 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 1349 """
e9bbd6ba 1350 nom = models.CharField(max_length=255)
ca1a7b76 1351
8c1ae2b3 1352 class Meta:
1353 ordering = ['nom']
c1195471
OL
1354 verbose_name = u"Type de revalorisation"
1355 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 1356
1357 def __unicode__(self):
6e4600ef 1358 return u'%s' % (self.nom)
ca1a7b76 1359
fa1f7426 1360
d6985a3a 1361class Service(AUFMetadata):
fa1f7426
EMS
1362 """
1363 Unité administrative où les Postes sont rattachés.
6e4600ef 1364 """
7ba822a6
OL
1365 objects = ServiceManager()
1366
f614ca5c 1367 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
6e4600ef 1368 nom = models.CharField(max_length=255)
ca1a7b76 1369
9afaa55e 1370 class Meta:
1371 ordering = ['nom']
c1195471
OL
1372 verbose_name = u"Service"
1373 verbose_name_plural = u"Services"
e9bbd6ba 1374
6e4600ef 1375 def __unicode__(self):
41ced34a
OL
1376 if self.archive:
1377 archive = u"(archivé)"
1378 else:
fa1f7426 1379 archive = ""
41ced34a 1380 return u'%s %s' % (self.nom, archive)
6e4600ef 1381
e9bbd6ba 1382
1383TYPE_ORGANISME_CHOICES = (
1384 ('MAD', 'Mise à disposition'),
1385 ('DET', 'Détachement'),
1386)
1387
fa1f7426 1388
d6985a3a 1389class OrganismeBstg(AUFMetadata):
fa1f7426
EMS
1390 """
1391 Organisation d'où provient un Employe mis à disposition (MAD) de
6e4600ef 1392 ou détaché (DET) à l'AUF à titre gratuit.
ca1a7b76 1393
6e4600ef 1394 (BSTG = bien et service à titre gratuit.)
1395 """
e9bbd6ba 1396 nom = models.CharField(max_length=255)
1397 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
ca1a7b76 1398 pays = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 1399 db_column='pays',
1400 related_name='organismes_bstg',
1401 null=True, blank=True)
9afaa55e 1402
1403 class Meta:
1404 ordering = ['type', 'nom']
c1195471
OL
1405 verbose_name = u"Organisme BSTG"
1406 verbose_name_plural = u"Organismes BSTG"
9afaa55e 1407
6e4600ef 1408 def __unicode__(self):
8c1ae2b3 1409 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 1410
aff1a4c6 1411 prefix_implantation = "pays__region"
fa1f7426 1412
aff1a4c6
PP
1413 def get_regions(self):
1414 return [self.pays.region]
1415
1416
d6985a3a 1417class Statut(AUFMetadata):
fa1f7426
EMS
1418 """
1419 Statut de l'Employe dans le cadre d'un Dossier particulier.
6e4600ef 1420 """
9afaa55e 1421 # Identification
fa1f7426
EMS
1422 code = models.CharField(
1423 max_length=25, unique=True,
1424 help_text=(
1425 u"Saisir un code court mais lisible pour ce statut : "
1426 u"le code est utilisé pour associer les statuts aux autres "
1427 u"données tout en demeurant plus lisible qu'un identifiant "
1428 u"numérique."
1429 )
1430 )
e9bbd6ba 1431 nom = models.CharField(max_length=255)
e9bbd6ba 1432
6e4600ef 1433 class Meta:
1434 ordering = ['code']
c1195471
OL
1435 verbose_name = u"Statut d'employé"
1436 verbose_name_plural = u"Statuts d'employé"
ca1a7b76 1437
9afaa55e 1438 def __unicode__(self):
1439 return u'%s : %s' % (self.code, self.nom)
1440
83b7692b 1441
e9bbd6ba 1442TYPE_CLASSEMENT_CHOICES = (
6e4600ef 1443 ('S', 'S -Soutien'),
1444 ('T', 'T - Technicien'),
1445 ('P', 'P - Professionel'),
1446 ('C', 'C - Cadre'),
1447 ('D', 'D - Direction'),
1448 ('SO', 'SO - Sans objet [expatriés]'),
1449 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 1450)
83b7692b 1451
fa1f7426 1452
952ecb37
OL
1453class ClassementManager(models.Manager):
1454 """
1455 Ordonner les spcéfiquement les classements.
1456 """
1457 def get_query_set(self):
1458 qs = super(self.__class__, self).get_query_set()
fa1f7426
EMS
1459 qs = qs.extra(select={
1460 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1461 })
6559f73b 1462 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
952ecb37
OL
1463 return qs.all()
1464
6e7c919b 1465
d6985a3a 1466class Classement_(AUFMetadata):
fa1f7426
EMS
1467 """
1468 Éléments de classement de la
6e4600ef 1469 "Grille générique de classement hiérarchique".
ca1a7b76
EMS
1470
1471 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
6e4600ef 1472 classement dans la grille. Le classement donne le coefficient utilisé dans:
1473
1474 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1475 """
952ecb37
OL
1476 objects = ClassementManager()
1477
9afaa55e 1478 # Identification
e9bbd6ba 1479 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
fa1f7426
EMS
1480 echelon = models.IntegerField(u"échelon", blank=True, default=0)
1481 degre = models.IntegerField(u"degré", blank=True, default=0)
1482 coefficient = models.FloatField(u"coefficient", default=0, null=True)
1483
9afaa55e 1484 # Méta
6e4600ef 1485 # annee # au lieu de date_debut et date_fin
1486 commentaire = models.TextField(null=True, blank=True)
ca1a7b76 1487
6e4600ef 1488 class Meta:
6e7c919b 1489 abstract = True
fa1f7426 1490 ordering = ['type', 'echelon', 'degre', 'coefficient']
c1195471
OL
1491 verbose_name = u"Classement"
1492 verbose_name_plural = u"Classements"
e9bbd6ba 1493
1494 def __unicode__(self):
22343fe7 1495 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
e9bbd6ba 1496
fa1f7426 1497
6e7c919b
NC
1498class Classement(Classement_):
1499 __doc__ = Classement_.__doc__
1500
1501
d6985a3a 1502class TauxChange_(AUFMetadata):
fa1f7426
EMS
1503 """
1504 Taux de change de la devise vers l'euro (EUR)
6e4600ef 1505 pour chaque année budgétaire.
7abc6d45 1506 """
9afaa55e 1507 # Identification
8d3e2fff 1508 devise = models.ForeignKey('Devise', db_column='devise')
fa1f7426
EMS
1509 annee = models.IntegerField(u"année")
1510 taux = models.FloatField(u"taux vers l'euro")
6e7c919b 1511
6e4600ef 1512 class Meta:
6e7c919b 1513 abstract = True
8c1ae2b3 1514 ordering = ['-annee', 'devise__code']
c1195471
OL
1515 verbose_name = u"Taux de change"
1516 verbose_name_plural = u"Taux de change"
ca1a7b76 1517
6e4600ef 1518 def __unicode__(self):
8c1ae2b3 1519 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1520
6e7c919b
NC
1521
1522class TauxChange(TauxChange_):
1523 __doc__ = TauxChange_.__doc__
1524
fa1f7426 1525
701f3bea 1526class ValeurPointManager(NoDeleteManager):
105dd778 1527
701f3bea 1528 def get_query_set(self):
fa1f7426
EMS
1529 return super(ValeurPointManager, self).get_query_set() \
1530 .select_related('devise', 'implantation')
701f3bea 1531
6e7c919b 1532
d6985a3a 1533class ValeurPoint_(AUFMetadata):
fa1f7426
EMS
1534 """
1535 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
ca1a7b76 1536 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1537 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1538
1539 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1540 """
ca1a7b76 1541
09aa8374 1542 actuelles = ValeurPointManager()
701f3bea 1543
8277a35b 1544 valeur = models.FloatField(null=True)
f614ca5c 1545 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
ca1a7b76 1546 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1547 db_column='implantation',
1548 related_name='%(app_label)s_valeur_point')
9afaa55e 1549 # Méta
e9bbd6ba 1550 annee = models.IntegerField()
9afaa55e 1551
6e4600ef 1552 class Meta:
701f3bea 1553 ordering = ['-annee', 'implantation__nom']
6e7c919b 1554 abstract = True
c1195471
OL
1555 verbose_name = u"Valeur du point"
1556 verbose_name_plural = u"Valeurs du point"
6e0bbb73 1557
9afaa55e 1558 def __unicode__(self):
fa1f7426
EMS
1559 return u'%s %s %s [%s] %s' % (
1560 self.devise.code, self.annee, self.valeur,
1561 self.implantation.nom_court, self.devise.nom
1562 )
6e7c919b
NC
1563
1564
1565class ValeurPoint(ValeurPoint_):
1566 __doc__ = ValeurPoint_.__doc__
1567
e9bbd6ba 1568
d6985a3a 1569class Devise(AUFMetadata):
6e4600ef 1570 """
fa1f7426
EMS
1571 Devise monétaire.
1572 """
4bdadf8b
OL
1573 objects = DeviseManager()
1574
edb35076 1575 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
fa1f7426 1576 code = models.CharField(max_length=10, unique=True)
e9bbd6ba 1577 nom = models.CharField(max_length=255)
1578
6e4600ef 1579 class Meta:
1580 ordering = ['code']
c1195471
OL
1581 verbose_name = u"Devise"
1582 verbose_name_plural = u"Devises"
ca1a7b76 1583
e9bbd6ba 1584 def __unicode__(self):
1585 return u'%s - %s' % (self.code, self.nom)
1586
fa1f7426 1587
d6985a3a 1588class TypeContrat(AUFMetadata):
fa1f7426
EMS
1589 """
1590 Type de contrat.
6e4600ef 1591 """
e9bbd6ba 1592 nom = models.CharField(max_length=255)
6e4600ef 1593 nom_long = models.CharField(max_length=255)
49f9f116 1594
8c1ae2b3 1595 class Meta:
1596 ordering = ['nom']
c1195471
OL
1597 verbose_name = u"Type de contrat"
1598 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1599
9afaa55e 1600 def __unicode__(self):
1601 return u'%s' % (self.nom)
ca1a7b76
EMS
1602
1603
2d4d2fcf 1604### AUTRES
1605
8c8ffc4f 1606class ResponsableImplantationProxy(ref.Implantation):
7bf28694 1607
8c8ffc4f
OL
1608 class Meta:
1609 proxy = True
1610 verbose_name = u"Responsable d'implantation"
1611 verbose_name_plural = u"Responsables d'implantation"
1612
1613
1614class ResponsableImplantation(models.Model):
fa1f7426
EMS
1615 """
1616 Le responsable d'une implantation.
30be56d5 1617 Anciennement géré sur le Dossier du responsable.
1618 """
fa1f7426
EMS
1619 employe = models.ForeignKey(
1620 'Employe', db_column='employe', related_name='+', null=True,
1621 blank=True
1622 )
1623 implantation = models.OneToOneField(
1624 "ResponsableImplantationProxy", db_column='implantation',
1625 related_name='responsable', unique=True
1626 )
30be56d5 1627
1628 def __unicode__(self):
1629 return u'%s : %s' % (self.implantation, self.employe)
ca1a7b76 1630
30be56d5 1631 class Meta:
1632 ordering = ['implantation__nom']
8c1ae2b3 1633 verbose_name = "Responsable d'implantation"
1634 verbose_name_plural = "Responsables d'implantation"