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