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