Fix for total employe
[auf_rh_dae.git] / project / dae / models.py
CommitLineData
bd28238f 1# -=- encoding: utf-8 -=-
3f3cf5f3 2
fb0ed970 3import os
0316268d 4from decimal import Decimal
b9098c33 5from dateutil.relativedelta import relativedelta
fb0ed970
EMS
6from datetime import date, timedelta
7
5633fa41 8from django.conf import settings
36341125 9from django.core.files.storage import FileSystemStorage
a9c281dd 10from django.db import models
fb0ed970 11from django.db.models import Q
5a1f75cb 12
75f0e87b
DB
13import reversion
14
1d521201
BS
15from auf.django.references import models as ref
16
17from project.dae.managers import (
18 PosteManager, DossierManager, ImplantationManager)
5a1f75cb
EMS
19from project.dae.workflow import PosteWorkflow, DossierWorkflow
20from project.dae.workflow import \
21 DOSSIER_ETAT_DRH_FINALISATION, DOSSIER_ETAT_REGION_FINALISATION, \
22 DOSSIER_ETAT_FINALISE
4ba84959
EMS
23from project.rh import models as rh
24from project.rh.models import HELP_TEXT_DATE
bd28238f 25
d766bf2c 26# Upload de fichiers
34dad720 27UPLOAD_STORAGE = FileSystemStorage(settings.PRIVE_MEDIA_ROOT)
d766bf2c 28
36341125 29
2d4d2fcf 30### POSTE
31
32POSTE_APPEL_CHOICES = (
33 ('interne', 'Interne'),
34 ('externe', 'Externe'),
35)
c3be904d
OL
36POSTE_ACTION = (
37 ('N', u"Nouveau poste"),
38 ('M', u"Poste existant"),
39 ('E', u"Évolution de poste"),
40)
41
36341125 42
ae5c920b 43class DeviseException(Exception):
5a1f75cb 44 silent_variable_failure = True
ae5c920b 45
1c7d67ce 46
3f5cbabe 47class Poste(PosteWorkflow, rh.Poste_):
c3be904d 48
5a1f75cb
EMS
49 type_intervention = models.CharField(
50 max_length=1, choices=POSTE_ACTION, default='N'
51 )
c3be904d 52
bd28238f 53 # Modèle existant
8b08fd5a 54 id_rh = models.ForeignKey(
de151a1e 55 rh.Poste, null=True, related_name='postes_dae', editable=False,
8b08fd5a
EMS
56 verbose_name=u"Mise à jour du poste"
57 )
bd28238f
NC
58
59 # Rémunération
5a1f75cb
EMS
60 indemn_expat_min = models.DecimalField(
61 max_digits=13, decimal_places=2, default=0
62 )
63 indemn_expat_max = models.DecimalField(
64 max_digits=12, decimal_places=2, default=0
65 )
66 indemn_fct_min = models.DecimalField(
67 max_digits=12, decimal_places=2, default=0
68 )
69 indemn_fct_max = models.DecimalField(
70 max_digits=12, decimal_places=2, default=0
71 )
72 charges_patronales_min = models.DecimalField(
73 max_digits=12, decimal_places=2, default=0
74 )
75 charges_patronales_max = models.DecimalField(
76 max_digits=12, decimal_places=2, default=0
77 )
bd28238f 78
e37247db
BS
79 @property
80 def total_employe_min(self):
81 res = ((self.salaire_min or Decimal('0')) +
82 (self.indemn_fct_min or Decimal('0')) +
83 (self.indemn_expat_min or Decimal('0'))
84 )
85 return res
86
87 @property
88 def total_employe_max(self):
89 return ((self.salaire_max or Decimal('0')) +
90 (self.indemn_fct_max or Decimal('0')) +
91 (self.indemn_expat_max or Decimal('0'))
92 )
93
1c7d67ce
OL
94 # Managers
95 objects = PosteManager()
96
0316268d 97 @property
d48b850c
BS
98 def traitement_min_cad(self):
99 return ((self.salaire_min or Decimal('0')) +
0316268d
BS
100 (self.indemn_fct_min or Decimal('0')))
101
102 @property
d48b850c
BS
103 def traitement_max_cad(self):
104 return ((self.salaire_max or Decimal('0')) +
0316268d 105 (self.indemn_fct_max or Decimal('0')))
d48b850c 106
03858ba5
OL
107 def _get_key(self):
108 """
1bc84af4 109 Les vues sont montées selon une clef spéciale
2d4d2fcf 110 pour identifier la provenance du poste.
1bc84af4 111 Cette méthode fournit un moyen de reconstruire cette clef
2d4d2fcf 112 afin de générer les URLs.
03858ba5
OL
113 """
114 return "dae-%s" % self.id
115 key = property(_get_key)
116
f3333b0e
OL
117 def get_dossiers(self):
118 """
119 Liste tous les anciens dossiers liés à ce poste.
1bc84af4 120 (Le nom de la relation sur le rh.Poste est mal choisi
2d4d2fcf 121 poste1 au lieu de dossier1)
f3333b0e 122 Note1 : seulement le dosssier principal fait l'objet de la recherche.
1bc84af4
EMS
123 Note2 : les dossiers sont retournés du plus récent au plus vieux.
124 (Ce test est fait en fonction du id,
2d4d2fcf 125 car les dates de création sont absentes de rh v1).
f3333b0e
OL
126 """
127 if self.id_rh is None:
128 return []
16b1454e 129 return self.id_rh.rh_dossiers.all()
428e3c0b 130
47b60f16 131 def dans_rh(self):
8b08fd5a
EMS
132 """
133 Retourne le poste RH s'il existe.
134 """
135 return self.id_rh
47b60f16 136
fb0ed970 137 def importer_dans_rh(self):
8b08fd5a
EMS
138 """
139 Importe le poste DAE dans un poste RH existant ou nouveau.
140 """
3e30a959
EMS
141 dans_rh = self.dans_rh()
142 poste_rh = dans_rh or rh.Poste()
fb0ed970
EMS
143 poste_rh.nom = self.nom
144 poste_rh.implantation = self.implantation
145 poste_rh.type_poste = self.type_poste
146 poste_rh.service = self.service
147 poste_rh.responsable = self.responsable
148 poste_rh.regime_travail = self.regime_travail
149 poste_rh.regime_travail_nb_heure_semaine = \
150 self.regime_travail_nb_heure_semaine
151 poste_rh.local = self.local
152 poste_rh.expatrie = self.expatrie
153 poste_rh.mise_a_disposition = self.mise_a_disposition
154 poste_rh.appel = self.appel
155 poste_rh.classement_min = self.classement_min
156 poste_rh.classement_max = self.classement_max
157 poste_rh.valeur_point_min = self.valeur_point_min
158 poste_rh.valeur_point_max = self.valeur_point_max
159 poste_rh.devise_min = self.devise_min
160 poste_rh.devise_max = self.devise_max
161 poste_rh.salaire_min = self.salaire_min
162 poste_rh.salaire_max = self.salaire_max
d4a03f25
EMS
163 poste_rh.indemn_min = self.indemn_fct_min
164 poste_rh.indemn_max = self.indemn_fct_max
165 poste_rh.autre_min = \
166 self.indemn_expat_min + self.charges_patronales_min + \
167 self.autre_min
168 poste_rh.autre_max = \
169 self.indemn_expat_max + self.charges_patronales_max + \
170 self.autre_max
fb0ed970
EMS
171 poste_rh.devise_comparaison = self.devise_comparaison
172 poste_rh.comp_locale_min = self.comp_locale_min
173 poste_rh.comp_locale_max = self.comp_locale_max
174 poste_rh.comp_universite_min = self.comp_universite_min
175 poste_rh.comp_universite_max = self.comp_universite_max
176 poste_rh.comp_fonctionpub_min = self.comp_fonctionpub_min
177 poste_rh.comp_fonctionpub_max = self.comp_fonctionpub_max
178 poste_rh.comp_ong_min = self.comp_ong_min
179 poste_rh.comp_ong_max = self.comp_ong_max
180 poste_rh.comp_autre_min = self.comp_autre_min
181 poste_rh.comp_autre_max = self.comp_autre_max
182 poste_rh.justification = self.justification
3e30a959
EMS
183 if not dans_rh:
184 poste_rh.date_debut = self.date_debut
185 poste_rh.date_fin = self.date_fin
fb0ed970
EMS
186 poste_rh.save()
187
188 for piece in self.dae_pieces.all():
189 piece_rh = poste_rh.rh_pieces.create(
190 poste=poste_rh,
191 nom=piece.nom
192 )
7ad3b930
OL
193 if not settings.DEBUG:
194 piece_rh.fichier.save(
195 os.path.basename(piece.fichier.name), piece.fichier
fb0ed970
EMS
196 )
197
198 rh.PosteComparaison.objects.filter(poste=poste_rh).delete()
199 for comp in self.dae_comparaisons_internes.all():
200 poste_rh.rh_comparaisons_internes.create(
201 implantation=comp.implantation,
202 nom=comp.nom,
203 montant=comp.montant,
204 devise=comp.devise
205 )
206
207 rh.PosteFinancement.objects.filter(poste=poste_rh).delete()
208 for financement in self.dae_financements.all():
209 poste_rh.rh_financements.create(
210 type=financement.type,
211 pourcentage=financement.pourcentage,
212 commentaire=financement.commentaire
213 )
214
8b08fd5a
EMS
215 self.id_rh = poste_rh
216 self.save()
fb0ed970
EMS
217 return poste_rh
218
f3333b0e
OL
219 def get_employe(self):
220 """
221 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
222 """
223 dossiers = self.get_dossiers()
224 if len(dossiers) > 0:
225 return dossiers[0].employe
226 else:
227 return None
228
179f6b49 229 def get_default_devise(self):
1bc84af4 230 """Récupère la devise par défaut en fonction de l'implantation
2d4d2fcf 231 (EUR autrement)
232 """
179f6b49 233 try:
6e4600ef 234 implantation_devise = rh.TauxChange.objects \
235 .filter(implantation=self.implantation)[0].devise
179f6b49 236 except:
5a1f75cb 237 implantation_devise = 5 # EUR
179f6b49
OL
238 return implantation_devise
239
c0413a6f
OL
240 #####################
241 # Classement de poste
242 #####################
243
244 def get_couts_minimum(self):
5a1f75cb
EMS
245 return self.salaire_min + self.indemn_expat_min + \
246 self.indemn_fct_min + self.charges_patronales_min + \
247 self.autre_min
83b94a87
EMS
248
249 def get_salaire_minimum(self):
250 return self.get_couts_minimum() - self.charges_patronales_min
c0413a6f
OL
251
252 def get_taux_minimum(self):
4e439a89 253 if self.devise_min.code == 'EUR':
5a1f75cb 254 return 1
2455f48d 255 liste_taux = self.devise_min.tauxchange_set.order_by('-annee')
4e439a89 256 if len(liste_taux) == 0:
5a1f75cb
EMS
257 raise DeviseException(
258 u"La devise %s n'a pas de taux pour l'implantation %s" %
259 (self.devise_min, self.implantation))
b6825282 260 else:
4e439a89 261 return liste_taux[0].taux
c0413a6f
OL
262
263 def get_couts_minimum_euros(self):
fa5b95ed 264 return float(self.get_couts_minimum()) * self.get_taux_minimum()
c0413a6f 265
83b94a87 266 def get_salaire_minimum_euros(self):
fa5b95ed 267 return float(self.get_salaire_minimum()) * self.get_taux_minimum()
83b94a87 268
c0413a6f 269 def get_couts_maximum(self):
5a1f75cb
EMS
270 return self.salaire_max + self.indemn_expat_max + \
271 self.indemn_fct_max + self.charges_patronales_max + \
272 self.autre_max
83b94a87
EMS
273
274 def get_salaire_maximum(self):
275 return self.get_couts_maximum() - self.charges_patronales_max
c0413a6f
OL
276
277 def get_taux_maximum(self):
4e439a89 278 if self.devise_max.code == 'EUR':
5a1f75cb 279 return 1
2455f48d 280 liste_taux = self.devise_max.tauxchange_set.order_by('-annee')
4e439a89 281 if len(liste_taux) == 0:
5a1f75cb
EMS
282 raise DeviseException(
283 u"La devise %s n'a pas de taux pour l'implantation %s" %
284 (self.devise_max, self.implantation)
285 )
b6825282 286 else:
4e439a89 287 return liste_taux[0].taux
c0413a6f
OL
288
289 def get_couts_maximum_euros(self):
fa5b95ed 290 return float(self.get_couts_maximum()) * self.get_taux_maximum()
c0413a6f 291
83b94a87 292 def get_salaire_maximum_euros(self):
fa5b95ed 293 return float(self.get_salaire_maximum()) * self.get_taux_maximum()
12c7f8a7
OL
294
295 def show_taux_minimum(self):
296 try:
297 return self.get_taux_minimum()
83b94a87 298 except DeviseException, e:
12c7f8a7
OL
299 return e
300
301 def show_couts_minimum_euros(self):
302 try:
303 return self.get_couts_minimum_euros()
83b94a87
EMS
304 except DeviseException, e:
305 return e
306
307 def show_salaire_minimum_euros(self):
308 try:
309 return self.get_salaire_minimum_euros()
310 except DeviseException, e:
12c7f8a7
OL
311 return e
312
313 def show_taux_maximum(self):
314 try:
315 return self.get_taux_maximum()
83b94a87 316 except DeviseException, e:
12c7f8a7
OL
317 return e
318
319 def show_couts_maximum_euros(self):
320 try:
321 return self.get_couts_maximum_euros()
83b94a87
EMS
322 except DeviseException, e:
323 return e
324
325 def show_salaire_maximum_euros(self):
326 try:
327 return self.get_salaire_maximum_euros()
328 except DeviseException, e:
12c7f8a7
OL
329 return e
330
c0413a6f 331 # Comparaison de poste
a3fee9c5
OL
332 def est_comparable(self):
333 """
5a1f75cb
EMS
334 Si on a au moins une valeur de saisie dans les comparaisons, alors
335 le poste est comparable.
a3fee9c5
OL
336 """
337 if self.comp_universite_min is None and \
338 self.comp_fonctionpub_min is None and \
339 self.comp_locale_min is None and \
340 self.comp_ong_min is None and \
341 self.comp_autre_min is None and \
342 self.comp_universite_max is None and \
343 self.comp_fonctionpub_max is None and \
344 self.comp_locale_max is None and \
345 self.comp_ong_max is None and \
346 self.comp_autre_max is None:
347 return False
348 else:
349 return True
1bc84af4 350
c0413a6f
OL
351 def get_taux_comparaison(self):
352 try:
5a1f75cb
EMS
353 return rh.TauxChange.objects \
354 .filter(devise=self.devise_comparaison)[0].taux
c0413a6f
OL
355 except:
356 return 1
357
358 def get_comp_universite_min_euros(self):
359 return (float)(self.comp_universite_min) * self.get_taux_comparaison()
360
361 def get_comp_fonctionpub_min_euros(self):
362 return (float)(self.comp_fonctionpub_min) * self.get_taux_comparaison()
363
364 def get_comp_locale_min_euros(self):
365 return (float)(self.comp_locale_min) * self.get_taux_comparaison()
366
367 def get_comp_ong_min_euros(self):
368 return (float)(self.comp_ong_min) * self.get_taux_comparaison()
369
370 def get_comp_autre_min_euros(self):
371 return (float)(self.comp_autre_min) * self.get_taux_comparaison()
372
373 def get_comp_universite_max_euros(self):
374 return (float)(self.comp_universite_max) * self.get_taux_comparaison()
375
376 def get_comp_fonctionpub_max_euros(self):
377 return (float)(self.comp_fonctionpub_max) * self.get_taux_comparaison()
378
379 def get_comp_locale_max_euros(self):
380 return (float)(self.comp_locale_max) * self.get_taux_comparaison()
381
382 def get_comp_ong_max_euros(self):
383 return (float)(self.comp_ong_max) * self.get_taux_comparaison()
384
385 def get_comp_autre_max_euros(self):
386 return (float)(self.comp_autre_max) * self.get_taux_comparaison()
387
5d680e84 388 def __unicode__(self):
f3333b0e 389 """
1bc84af4 390 Cette fonction est consommatrice SQL car elle cherche les dossiers
2d4d2fcf 391 qui ont été liés à celui-ci.
f3333b0e 392 """
5ac79652
OL
393 try:
394 data = (
395 self.implantation,
396 self.type_poste.nom,
397 self.nom,
398 )
399 except:
400 return self.nom
a7c68130 401 return u'%s - %s (%s)' % data
5d680e84 402
cea09938
EMS
403reversion.register(Poste, format='xml', follow=[
404 'dae_financements', 'dae_pieces', 'dae_comparaisons_internes'
405])
a9c281dd 406
5d680e84
NC
407POSTE_FINANCEMENT_CHOICES = (
408 ('A', 'A - Frais de personnel'),
409 ('B', 'B - Projet(s)-Titre(s)'),
410 ('C', 'C - Autre')
411)
bd28238f 412
5a1f75cb 413
317ce433 414class PosteFinancement(rh.PosteFinancement_):
4ba84959
EMS
415 poste = models.ForeignKey(
416 Poste, db_column='poste', related_name='dae_financements'
417 )
1d0f4eef 418
cea09938
EMS
419reversion.register(PosteFinancement, format='xml')
420
5a1f75cb 421
317ce433 422class PostePiece(rh.PostePiece_):
4ba84959
EMS
423 poste = models.ForeignKey(
424 Poste, db_column='poste', related_name='dae_pieces'
425 )
068d1462 426
cea09938
EMS
427reversion.register(PostePiece, format='xml')
428
5a1f75cb 429
317ce433 430class PosteComparaison(rh.PosteComparaison_):
4ba84959
EMS
431 poste = models.ForeignKey(
432 Poste, related_name='dae_comparaisons_internes'
433 )
5a1f75cb
EMS
434 statut = models.ForeignKey(
435 rh.Statut, related_name='+', verbose_name=u'Statut', null=True,
436 blank=True
437 )
438 classement = models.ForeignKey(
439 rh.Classement, related_name='+', verbose_name=u'Classement',
440 null=True, blank=True
441 )
068d1462 442
cea09938
EMS
443reversion.register(PosteComparaison, format='xml')
444
b9098c33
BS
445
446class PosteComparaisonRemuneration(rh.Remuneration_):
447 poste_comparaison = models.ForeignKey(
448 PosteComparaison, related_name='poste_comparaison_remunerations'
449 )
450
451reversion.register(PosteComparaisonRemuneration, format='xml')
452
453
2d4d2fcf 454### EMPLOYÉ/PERSONNE
455
456# TODO : migration pour m -> M, f -> F
c589d980 457
bd28238f 458GENRE_CHOICES = (
139686f2
NC
459 ('m', 'Homme'),
460 ('f', 'Femme'),
bd28238f
NC
461)
462
5a1f75cb 463
cea09938 464class Employe(models.Model):
bd28238f
NC
465
466 # Modèle existant
428e3c0b 467 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
c1195471 468 verbose_name=u'Employé')
bd28238f 469 nom = models.CharField(max_length=255)
c1195471 470 prenom = models.CharField(max_length=255, verbose_name=u'Prénom')
07b40eda 471 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
bd28238f 472
139686f2 473 def __unicode__(self):
2d4d2fcf 474 return u'%s %s' % (self.prenom, self.nom.upper())
139686f2 475
47b60f16 476 def dans_rh(self):
8b08fd5a
EMS
477 """
478 Retourne l'employé RH associé à cet employé DAE.
479 """
480 return self.id_rh
47b60f16 481
fb0ed970 482 def importer_dans_rh(self):
8b08fd5a
EMS
483 """
484 Importe l'employé DAE dans un employé RH existant ou nouveau.
485 """
486 employe_rh = self.dans_rh() or rh.Employe.objects.create(
fb0ed970
EMS
487 nom=self.nom,
488 prenom=self.prenom,
489 genre=self.genre
490 )
8b08fd5a
EMS
491 self.id_rh = employe_rh
492 self.save()
493 return employe_rh
fb0ed970 494
cea09938
EMS
495reversion.register(Employe, format='xml')
496
bd28238f 497
2d4d2fcf 498### DOSSIER
499
500STATUT_RESIDENCE_CHOICES = (
501 ('local', 'Local'),
502 ('expat', 'Expatrié'),
503)
504
16b1454e 505class Dossier(DossierWorkflow, rh.Dossier_):
5a1f75cb 506 poste = models.ForeignKey(
4ba84959 507 'Poste', db_column='poste', related_name='dae_dossiers'
5a1f75cb
EMS
508 )
509 employe = models.ForeignKey(
510 'Employe', db_column='employe',
4ba84959 511 related_name='rh_dossiers', verbose_name=u"Employé"
5a1f75cb 512 )
0288adb5 513 organisme_bstg_autre = models.CharField(max_length=255,
c1195471 514 verbose_name=u"Autre organisme",
0288adb5
OL
515 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
516 null=True,
517 blank=True,)
bd28238f 518
8b08fd5a
EMS
519 # Lien avec le dossier
520 dossier_rh = models.ForeignKey(
521 rh.Dossier, related_name='dossiers_dae', null=True, blank=True
522 )
523
139686f2
NC
524 # Données antérieures de l'employé
525 statut_anterieur = models.ForeignKey(
526 rh.Statut, related_name='+', null=True, blank=True,
c1195471 527 verbose_name=u'Statut antérieur')
139686f2
NC
528 classement_anterieur = models.ForeignKey(
529 rh.Classement, related_name='+', null=True, blank=True,
c1195471 530 verbose_name=u'Classement précédent')
139686f2
NC
531 salaire_anterieur = models.DecimalField(
532 max_digits=12, decimal_places=2, null=True, default=None,
481fbd33 533 blank=True, verbose_name=u'Salaire précédent')
5a1f75cb
EMS
534 devise_anterieur = models.ForeignKey(
535 rh.Devise, related_name='+', null=True, blank=True
536 )
537 type_contrat_anterieur = models.ForeignKey(
538 rh.TypeContrat, related_name='+', null=True, blank=True,
539 verbose_name=u'Type contrat antérieur'
540 )
139686f2
NC
541
542 # Données du titulaire précédent
543 employe_anterieur = models.ForeignKey(
544 rh.Employe, related_name='+', null=True, blank=True,
c1195471 545 verbose_name=u'Employé précédent')
139686f2
NC
546 statut_titulaire_anterieur = models.ForeignKey(
547 rh.Statut, related_name='+', null=True, blank=True,
c1195471 548 verbose_name=u'Statut titulaire précédent')
139686f2
NC
549 classement_titulaire_anterieur = models.ForeignKey(
550 rh.Classement, related_name='+', null=True, blank=True,
c1195471 551 verbose_name=u'Classement titulaire précédent')
139686f2
NC
552 salaire_titulaire_anterieur = models.DecimalField(
553 max_digits=12, decimal_places=2, default=None, null=True,
481fbd33 554 blank=True, verbose_name=u'Salaire titulaire précédent')
5a1f75cb
EMS
555 devise_titulaire_anterieur = models.ForeignKey(
556 rh.Devise, related_name='+', null=True, blank=True
557 )
494ff2be 558
bd28238f 559 # Rémunération
16b1454e 560 salaire = models.DecimalField(max_digits=13, decimal_places=2,
c1195471 561 verbose_name=u'Salaire de base',
2d4d2fcf 562 null=True, default=None)
e8e75458 563 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
bd28238f
NC
564
565 # Contrat
5d680e84 566 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
b666864e 567 contrat_date_debut = models.DateField(help_text=HELP_TEXT_DATE)
1f109689 568 contrat_date_fin = models.DateField(null=True, blank=True,
b666864e 569 help_text=HELP_TEXT_DATE)
bd28238f 570
29dffede 571 # Justifications
5a1f75cb
EMS
572 justif_nouveau_statut_label = u'Justifier le statut que ce type ' \
573 u'de poste nécessite (national, expatrié, màd ou détachement)'
574 justif_nouveau_statut = models.TextField(
575 verbose_name=justif_nouveau_statut_label, null=True, blank=True
576 )
577 justif_nouveau_tmp_remplacement_label = u"Si l'employé effectue un " \
578 u"remplacement temporaire, préciser"
579 justif_nouveau_tmp_remplacement = models.TextField(
580 verbose_name=justif_nouveau_tmp_remplacement_label, null=True,
581 blank=True
582 )
583 justif_nouveau_salaire_label = u"Si le salaire de l'employé ne " \
584 u"correspond pas au classement du poste ou est différent " \
585 u"du salaire antérieur, justifier "
586 justif_nouveau_salaire = models.TextField(
587 verbose_name=justif_nouveau_salaire_label, null=True, blank=True
588 )
a83daab3 589 justif_nouveau_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
5a1f75cb
EMS
590 justif_nouveau_commentaire = models.TextField(
591 verbose_name=justif_nouveau_commentaire_label, null=True, blank=True
592 )
593 justif_rempl_type_contrat_label = \
594 u"Changement de type de contrat, ex : d'un CDD en CDI"
595 justif_rempl_type_contrat = models.TextField(
596 verbose_name=justif_rempl_type_contrat_label, null=True, blank=True
597 )
598 justif_rempl_statut_employe_label = \
599 u"Si le statut de l'employé a été modifié pour ce poste ; " \
600 u"ex : national, expatrié, màd, détachement ? Si oui, justifier"
601 justif_rempl_statut_employe = models.TextField(
602 verbose_name=justif_rempl_statut_employe_label, null=True, blank=True
603 )
604 justif_rempl_evaluation_label = \
605 u"L'évaluation de l'employé est-elle favorable? Préciser"
606 justif_rempl_evaluation = models.TextField(
607 verbose_name=justif_rempl_evaluation_label, null=True, blank=True
608 )
609 justif_rempl_salaire_label = \
610 u"Si le salaire de l'employé est modifié et/ou ne correspond " \
611 u"pas à son classement, justifier"
612 justif_rempl_salaire = models.TextField(
613 verbose_name=justif_rempl_salaire_label, null=True, blank=True
614 )
a83daab3 615 justif_rempl_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
5a1f75cb
EMS
616 justif_rempl_commentaire = models.TextField(
617 verbose_name=justif_rempl_commentaire_label, null=True, blank=True
618 )
29dffede 619
c3f0b49f 620 # DAE numérisée
5a1f75cb
EMS
621 dae_numerisee = models.FileField(
622 upload_to='dae/dae_numerisee', storage=UPLOAD_STORAGE, blank=True,
623 null=True, verbose_name="DAE numérisée"
624 )
c3f0b49f 625
e4f56614
OL
626 # Managers
627 objects = DossierManager()
428e3c0b 628
16b1454e 629 def __init__(self, *args, **kwargs):
5a1f75cb
EMS
630 # Bouchon pour créer une date fictive necessaire pour valider un
631 # dossier à cause de l'héritage
16b1454e
OL
632 super(rh.Dossier_, self).__init__(*args, **kwargs)
633 super(DossierWorkflow, self).__init__(*args, **kwargs)
634 import datetime
635 self.date_debut = datetime.datetime.today()
636
aec2c91e 637 def __unicode__(self):
5a1f75cb
EMS
638 return u'[%s] %s - %s' % (
639 self.poste.implantation, self.poste.nom, self.employe
640 )
bd28238f 641
47b60f16
EMS
642 def dans_rh(self):
643 """
644 Retourne le dossier associé dans le système RH ou ``None`` s'il n'y
645 en a pas.
646 """
8b08fd5a
EMS
647 if self.dossier_rh:
648 return self.dossier_rh
fb0ed970 649 today = date.today()
47b60f16
EMS
650 poste_rh = self.poste.dans_rh()
651 if poste_rh is None:
652 return None
653 employe_rh = self.employe.dans_rh()
654 if employe_rh is None:
655 return None
fb0ed970 656 try:
47b60f16 657 return rh.Dossier.objects.get(
fb0ed970
EMS
658 Q(date_debut=None) | Q(date_debut__lte=today),
659 Q(date_fin=None) | Q(date_fin__gte=today),
660 poste=poste_rh,
661 employe=employe_rh
662 )
663 except rh.Dossier.DoesNotExist:
47b60f16
EMS
664 return None
665
666 def importer_dans_rh(self):
8b08fd5a
EMS
667 """
668 Importe les données du dossier DAE dans un dossier RH existant ou
669 nouveau.
670 """
47b60f16
EMS
671 poste_rh = self.poste.importer_dans_rh()
672 employe_rh = self.employe.importer_dans_rh()
673 dossier_rh = self.dans_rh() or \
674 rh.Dossier(poste=poste_rh, employe=employe_rh)
fb0ed970
EMS
675
676 dossier_rh.statut = self.statut
677 dossier_rh.organisme_bstg = self.organisme_bstg
678 dossier_rh.remplacement = self.remplacement
679 dossier_rh.remplacement_de = self.remplacement_de
680 dossier_rh.statut_residence = self.statut_residence
681 dossier_rh.classement = self.classement
682 dossier_rh.regime_travail = self.regime_travail
9623a926 683 dossier_rh.est_cadre = self.est_cadre
89d105e3
BS
684 dossier_rh.compte_compta = self.compte_compta
685 dossier_rh.compte_courriel = self.compte_courriel
fb0ed970
EMS
686 dossier_rh.regime_travail_nb_heure_semaine = \
687 self.regime_travail_nb_heure_semaine
d4a03f25 688 dossier_rh.date_debut = self.contrat_date_debut
fb0ed970
EMS
689 dossier_rh.save()
690
691 rh.DossierComparaison.objects.filter(dossier=dossier_rh).delete()
692 for comp in self.dae_comparaisons.all():
693 dossier_rh.rh_comparaisons.create(
694 implantation=comp.implantation,
695 poste=comp.poste,
696 personne=comp.personne,
697 montant=comp.montant,
698 devise=comp.devise
699 )
700
d4a03f25
EMS
701 for contrat in self.dae_contrats.all():
702 contrat_rh = dossier_rh.rh_contrats.create(
fb0ed970
EMS
703 type_contrat=self.type_contrat,
704 date_debut=self.contrat_date_debut,
705 date_fin=self.contrat_date_fin,
706 )
d4a03f25
EMS
707 contrat_rh.fichier.save(
708 os.path.basename(contrat.fichier.name), contrat.fichier
709 )
fb0ed970
EMS
710
711 for piece in self.dae_dossierpieces.all():
712 piece_rh = dossier_rh.rh_dossierpieces.create(
713 nom=piece.nom
714 )
7ad3b930
OL
715 if not settings.DEBUG:
716 piece_rh.fichier.save(
717 os.path.basename(piece.fichier.name), piece.fichier
718 )
fb0ed970 719
d4a03f25
EMS
720 if self.dae_numerisee:
721 dae_numerisee_rh = dossier_rh.rh_dossierpieces.create(
722 nom=u'DAE numérisée'
723 )
66fefd2f
OL
724 if not settings.DEBUG:
725 dae_numerisee_rh.fichier.save(
726 os.path.basename(self.dae_numerisee.name),
727 self.dae_numerisee
728 )
d4a03f25 729
fb0ed970
EMS
730 # Fermer les rémunérations qui commencent avant le début du contrat
731 dossier_rh.rh_remunerations.filter(
732 Q(date_debut=None) | Q(date_debut__lt=self.contrat_date_debut),
733 Q(date_fin=None) | Q(date_fin__gte=self.contrat_date_debut)
734 ).update(date_fin=self.contrat_date_debut - timedelta(1))
735
736 # Effacer les rémunérations qui commencent à la date du contrat
737 dossier_rh.rh_remunerations \
738 .filter(date_debut=self.contrat_date_debut) \
739 .delete()
740
741 for remun in self.dae_remunerations.all():
742 dossier_rh.rh_remunerations.get_or_create(
743 type=remun.type,
744 type_revalorisation=remun.type_revalorisation,
745 montant=remun.montant,
746 devise=remun.devise,
747 commentaire=remun.commentaire,
748 date_debut=self.contrat_date_debut,
749 date_fin=self.contrat_date_fin
750 )
751
8b08fd5a
EMS
752 # Enregistrer le lien avec le dossier RH
753 self.dossier_rh = dossier_rh
754 self.save()
755
fb0ed970
EMS
756 return dossier_rh
757
0316268d
BS
758 def get_dossier_precedent_titulaire(self):
759 try:
760 rh_poste = self.poste.id_rh
761 except rh.Poste.DoesNotExist:
762 return None
763
764 try:
765 precedent_employe = self.employe_anterieur
766 except rh.Employe.DoesNotExist:
767 return None
768
769 qs = rh.Dossier.objects.filter(
770 poste=rh_poste,
771 employe=precedent_employe,
772 ).order_by(
773 '-principal', 'date_fin')
774
775 if qs.count() == 0:
776 return None
777
778 # Retourne en le premier du queryset si la date de fin est None
779 # Sinon, retourne le plus récent selon la date de fin.
780 first = qs[0]
781 if first.date_fin == None:
782 return first
783 else:
784 return qs.order_by('-principal', '-date_fin')[0]
785
eed93931 786 def get_salaire_anterieur_euros(self):
88a0e98d
OL
787 if self.devise_anterieur is None:
788 return None
03ff41e3
OL
789 try:
790 taux = self.taux_devise(self.devise_anterieur)
791 except Exception, e:
792 return e
793 if not taux:
794 return None
795 return int(round(float(self.salaire_anterieur) * float(taux), 2))
796
eed93931 797 def get_salaire_titulaire_anterieur_euros(self):
16b1454e
OL
798 if self.devise_titulaire_anterieur is None:
799 return None
03ff41e3 800 try:
88a0e98d 801 taux = self.taux_devise(self.devise_titulaire_anterieur)
03ff41e3
OL
802 except Exception, e:
803 return e
804 if not taux:
16b1454e 805 return None
5a1f75cb
EMS
806 return int(round(
807 float(self.salaire_titulaire_anterieur) * float(taux), 2
808 ))
eed93931 809
c511cd1f
EMS
810 def valide(self):
811 return self.etat in (DOSSIER_ETAT_REGION_FINALISATION,
812 DOSSIER_ETAT_DRH_FINALISATION,
813 DOSSIER_ETAT_FINALISE)
814
cea09938
EMS
815reversion.register(Dossier, format='xml', follow=[
816 'dae_dossierpieces', 'dae_comparaisons', 'dae_remunerations',
817 'dae_contrats'
818])
bd28238f 819
5a1f75cb 820
a4125771 821class DossierPiece(rh.DossierPiece_):
4ba84959
EMS
822 """
823 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
2d4d2fcf 824 Ex.: Lettre de motivation.
825 """
4ba84959
EMS
826 dossier = models.ForeignKey(
827 Dossier, db_column='dossier', related_name='dae_dossierpieces'
828 )
2d4d2fcf 829
cea09938
EMS
830reversion.register(DossierPiece, format='xml')
831
5a1f75cb 832
a4125771 833class DossierComparaison(rh.DossierComparaison_):
03b395db
OL
834 """
835 Photo d'une comparaison salariale au moment de l'embauche.
836 """
4ba84959
EMS
837 dossier = models.ForeignKey(
838 Dossier, related_name='dae_comparaisons'
839 )
5a1f75cb
EMS
840 statut = models.ForeignKey(
841 rh.Statut, related_name='+', verbose_name='Statut', null=True,
842 blank=True
843 )
844 classement = models.ForeignKey(
845 rh.Classement, related_name='+', verbose_name='Classement',
846 null=True, blank=True
847 )
03b395db 848
cea09938
EMS
849reversion.register(DossierComparaison, format='xml')
850
b9098c33
BS
851class DossierComparaisonRemuneration(rh.Remuneration_):
852 dossier_comparaison = models.ForeignKey(
853 DossierComparaison, related_name='dossier_comparaison_remunerations'
854 )
855
856reversion.register(DossierComparaisonRemuneration, format='xml')
c589d980 857
2d4d2fcf 858### RÉMUNÉRATION
859
a4125771 860class Remuneration(rh.Remuneration_):
4ba84959
EMS
861 dossier = models.ForeignKey(
862 Dossier, db_column='dossier', related_name='dae_remunerations'
863 )
c1834169 864
cea09938
EMS
865reversion.register(Remuneration, format='xml')
866
5a1f75cb 867
c1834169
EMS
868### CONTRATS
869
a4125771 870class Contrat(rh.Contrat_):
4ba84959
EMS
871 dossier = models.ForeignKey(
872 Dossier, db_column='dossier', related_name='dae_contrats'
873 )
cea09938
EMS
874
875reversion.register(Contrat, format='xml')
f80b83c6 876
fb4ded65
OL
877
878class ProxyDossierStatut(Dossier):
879 class Meta:
880 proxy = True
881 verbose_name = "Statut du dossier"
882 verbose_name_plural = "Statut des dossiers"
883
884
885class ProxyPosteStatut(Poste):
886 class Meta:
887 proxy = True
888 verbose_name = "Statut du poste"
889 verbose_name_plural = "Statut des postes"
1d521201
BS
890
891
892class ProxyImplantation(ref.Implantation):
893
894 # Ajout d'un manager pour ref.Implantation
895 dae_manager = ImplantationManager()
896
897 class Meta:
898 proxy = True