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