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