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