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