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