fix implantation reverse
[auf_rh_dae.git] / project / dae / models.py
CommitLineData
bd28238f 1# -=- encoding: utf-8 -=-
3f3cf5f3 2
36341125 3import os
5633fa41 4from django.conf import settings
36341125 5from django.core.files.storage import FileSystemStorage
a9c281dd
OL
6from django.db import models
7import reversion
afc204bf 8from workflow import PosteWorkflow, DossierWorkflow
515124ec 9from workflow import DOSSIER_ETAT_DRH_FINALISATION
f258e4e7 10from managers import DossierManager, PosteManager
bd28238f 11import datamaster_modeles.models as ref
a9c281dd 12from rh_v1 import models as rh
bd28238f 13
bd28238f 14
2d4d2fcf 15# Constantes
16HELP_TEXT_DATE = "format: aaaa-mm-jj"
17REGIME_TRAVAIL_DEFAULT=100.00
18REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT=35.00
bd28238f 19
bd28238f 20
d766bf2c 21# Upload de fichiers
2d4d2fcf 22storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
23 base_url=settings.PRIVE_MEDIA_URL)
36341125
OL
24
25def poste_piece_dispatch(instance, filename):
fe13cd48 26 path = "poste/%s/%s" % (instance.poste_id, filename)
36341125
OL
27 return path
28
d766bf2c 29def dossier_piece_dispatch(instance, filename):
fe13cd48 30 path = "dossier/%s/%s" % (instance.dossier_id, filename)
d766bf2c
OL
31 return path
32
36341125 33
2d4d2fcf 34### POSTE
35
36POSTE_APPEL_CHOICES = (
37 ('interne', 'Interne'),
38 ('externe', 'Externe'),
39)
c3be904d
OL
40POSTE_ACTION = (
41 ('N', u"Nouveau poste"),
42 ('M', u"Poste existant"),
43 ('E', u"Évolution de poste"),
44)
45
36341125 46
1c7d67ce 47
8fa94e8b 48class Poste(PosteWorkflow, models.Model):
c3be904d
OL
49
50 type_intervention = models.CharField(max_length=1, choices=POSTE_ACTION, default='N')
51
bd28238f 52 # Modèle existant
5d680e84 53 id_rh = models.ForeignKey(rh.Poste, null=True, related_name='+',
2d4d2fcf 54 editable=False,
55 verbose_name="Mise à jour du poste")
ce110fb9 56 nom = models.CharField(verbose_name="Titre du poste", max_length=255)
5d680e84
NC
57 implantation = models.ForeignKey(ref.Implantation)
58 type_poste = models.ForeignKey(rh.TypePoste, null=True, related_name='+')
98d51b59 59 service = models.ForeignKey(rh.Service, related_name='+',
2d4d2fcf 60 verbose_name=u"Direction/Service/Pôle support")
98d51b59 61 responsable = models.ForeignKey(rh.Poste, related_name='+',
2d4d2fcf 62 verbose_name="Poste du responsable")
9a85768a 63
2d4d2fcf 64 # Contrat
5d680e84 65 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
2d4d2fcf 66 default=REGIME_TRAVAIL_DEFAULT,
67 verbose_name="Temps de travail",
68 help_text="% du temps complet")
5d680e84 69 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 70 decimal_places=2,
71 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
72 verbose_name="Nb. heures par semaine")
bd28238f
NC
73
74 # Recrutement
154677c3 75 local = models.BooleanField(verbose_name="Local", default=True, blank=True)
2d4d2fcf 76 expatrie = models.BooleanField(verbose_name="Expatrié", default=False,
77 blank=True)
a3508c67 78 mise_a_disposition = models.BooleanField(verbose_name="Mise à disposition")
98d51b59 79 appel = models.CharField(max_length=10, default='interne',
2d4d2fcf 80 verbose_name="Appel à candidature",
81 choices=POSTE_APPEL_CHOICES)
bd28238f
NC
82
83 # Rémunération
2d4d2fcf 84 classement_min = models.ForeignKey(rh.Classement, related_name='+',
85 blank=True, null=True)
86 classement_max = models.ForeignKey(rh.Classement, related_name='+',
87 blank=True, null=True)
88 valeur_point_min = models.ForeignKey(rh.ValeurPoint, related_name='+',
89 blank=True, null=True)
90 valeur_point_max = models.ForeignKey(rh.ValeurPoint, related_name='+',
91 blank=True, null=True)
3d627bfd 92 devise_min = models.ForeignKey(rh.Devise, default=5, related_name='+')
93 devise_max = models.ForeignKey(rh.Devise, default=5, related_name='+')
5d680e84
NC
94 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
95 default=0)
5f61bccb
OL
96 salaire_max = models.DecimalField(max_digits=12, decimal_places=2, default=0)
97 indemn_expat_min = models.DecimalField(max_digits=12, decimal_places=2, default=0)
98 indemn_expat_max = models.DecimalField(max_digits=12, decimal_places=2, default=0)
99 indemn_fct_min = models.DecimalField(max_digits=12, decimal_places=2, default=0)
100 indemn_fct_max = models.DecimalField(max_digits=12, decimal_places=2, default=0)
101 charges_patronales_min = models.DecimalField(max_digits=12, decimal_places=2, default=0)
102 charges_patronales_max = models.DecimalField(max_digits=12, decimal_places=2, default=0)
103 autre_min = models.DecimalField(max_digits=12, decimal_places=2, default=0)
104 autre_max = models.DecimalField(max_digits=12, decimal_places=2, default=0)
5d680e84
NC
105
106 # Comparatifs de rémunération
107 devise_comparaison = models.ForeignKey(rh.Devise, related_name='+',
a3508c67 108 default=5)
5d680e84
NC
109 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
110 null=True, blank=True)
111 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
112 null=True, blank=True)
113 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
114 null=True, blank=True)
115 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
116 null=True, blank=True)
117 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
118 null=True, blank=True)
119 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
120 null=True, blank=True)
121 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
122 null=True, blank=True)
123 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
124 null=True, blank=True)
125 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
126 null=True, blank=True)
127 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
128 null=True, blank=True)
bd28238f 129
2e092e0c
OL
130 # Justification
131 justification = models.TextField()
132
bd28238f 133 # Méta
5d680e84
NC
134 date_creation = models.DateTimeField(auto_now_add=True)
135 date_modification = models.DateTimeField(auto_now=True)
98d51b59 136 date_debut = models.DateField(verbose_name="Date de début",
2d4d2fcf 137 help_text=HELP_TEXT_DATE)
9fb2ccd9 138 date_fin = models.DateField(null=True, blank=True,
139 verbose_name="Date de fin",
2d4d2fcf 140 help_text=HELP_TEXT_DATE)
bd28238f
NC
141 actif = models.BooleanField(default=True)
142
1c7d67ce
OL
143 # Managers
144 objects = PosteManager()
145
03858ba5
OL
146 def _get_key(self):
147 """
2d4d2fcf 148 Les vues sont montées selon une clef spéciale
149 pour identifier la provenance du poste.
150 Cette méthode fournit un moyen de reconstruire cette clef
151 afin de générer les URLs.
03858ba5
OL
152 """
153 return "dae-%s" % self.id
154 key = property(_get_key)
155
f3333b0e
OL
156 def get_dossiers(self):
157 """
158 Liste tous les anciens dossiers liés à ce poste.
2d4d2fcf 159 (Le nom de la relation sur le rh.Poste est mal choisi
160 poste1 au lieu de dossier1)
f3333b0e 161 Note1 : seulement le dosssier principal fait l'objet de la recherche.
2d4d2fcf 162 Note2 : les dossiers sont retournés du plus récent au plus vieux.
163 (Ce test est fait en fonction du id,
164 car les dates de création sont absentes de rh v1).
f3333b0e
OL
165 """
166 if self.id_rh is None:
167 return []
168 postes = [p for p in self.id_rh.poste1.all()]
169 return sorted(postes, key=lambda poste: poste.id, reverse=True)
170
171 def get_complement_nom(self):
172 """
2d4d2fcf 173 Inspecte les modèles rh v1 pour trouver dans le dernier dossier
174 un complément de titre de poste.
f3333b0e
OL
175 """
176 dossiers = self.get_dossiers()
177 if len(dossiers) > 0:
178 nom = dossiers[0].complement1
179 else:
180 nom = ""
a9c281dd 181 return nom
f3333b0e
OL
182
183 def get_employe(self):
184 """
185 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
186 """
187 dossiers = self.get_dossiers()
188 if len(dossiers) > 0:
189 return dossiers[0].employe
190 else:
191 return None
192
179f6b49 193 def get_default_devise(self):
2d4d2fcf 194 """Récupère la devise par défaut en fonction de l'implantation
195 (EUR autrement)
196 """
179f6b49 197 try:
6e4600ef 198 implantation_devise = rh.TauxChange.objects \
199 .filter(implantation=self.implantation)[0].devise
179f6b49
OL
200 except:
201 implantation_devise = 5 # EUR
202 return implantation_devise
203
c0413a6f
OL
204 #####################
205 # Classement de poste
206 #####################
207
208 def get_couts_minimum(self):
5f61bccb 209 return (float)(self.salaire_min + self.indemn_expat_min + + self.indemn_fct_min + self.charges_patronales_min + self.autre_min)
c0413a6f
OL
210
211 def get_taux_minimum(self):
b6825282
OL
212 taux_changes = rh.TauxChange.objects.filter(devise=self.devise_min).order_by('annee')
213 for t in taux_changes:
214 if t.implantation == self.implantation:
215 return t.taux
216 if len(taux_changes) > 0:
217 return taux_changes[0].taux
218 else:
12c7f8a7 219 raise Exception('Taux indisponible pour la devise %s (%s)' % (self.devise_min, self.implantation))
c0413a6f
OL
220
221 def get_couts_minimum_euros(self):
222 return self.get_couts_minimum() * self.get_taux_minimum()
223
224 def get_couts_maximum(self):
5f61bccb 225 return (float)(self.salaire_max + self.indemn_expat_max + + self.indemn_fct_max + self.charges_patronales_max + self.autre_max)
c0413a6f
OL
226
227 def get_taux_maximum(self):
b6825282
OL
228 taux_changes = rh.TauxChange.objects.filter(devise=self.devise_max).order_by('annee')
229 for t in taux_changes:
230 if t.implantation == self.implantation:
231 return t.taux
232 if len(taux_changes) > 0:
233 return taux_changes[0].taux
234 else:
12c7f8a7 235 raise Exception('Taux indisponible pour la devise %s (%s)' % (self.devise_max, self.implantation))
c0413a6f
OL
236
237 def get_couts_maximum_euros(self):
238 return self.get_couts_maximum() * self.get_taux_maximum()
239
12c7f8a7
OL
240
241 def show_taux_minimum(self):
242 try:
243 return self.get_taux_minimum()
244 except Exception, e:
245 return e
246
247 def show_couts_minimum_euros(self):
248 try:
249 return self.get_couts_minimum_euros()
250 except Exception, e:
251 return e
252
253 def show_taux_maximum(self):
254 try:
255 return self.get_taux_maximum()
256 except Exception, e:
257 return e
258
259 def show_couts_maximum_euros(self):
260 try:
261 return self.get_couts_maximum_euros()
262 except Exception, e:
263 return e
264
265
c0413a6f
OL
266 ######################
267 # Comparaison de poste
268 ######################
a3fee9c5
OL
269
270 def est_comparable(self):
271 """
272 Si on a au moins une valeur de saisie dans les comparaisons, alors le poste
273 est comparable.
274 """
275 if self.comp_universite_min is None and \
276 self.comp_fonctionpub_min is None and \
277 self.comp_locale_min is None and \
278 self.comp_ong_min is None and \
279 self.comp_autre_min is None and \
280 self.comp_universite_max is None and \
281 self.comp_fonctionpub_max is None and \
282 self.comp_locale_max is None and \
283 self.comp_ong_max is None and \
284 self.comp_autre_max is None:
285 return False
286 else:
287 return True
c0413a6f 288
a3fee9c5 289
c0413a6f
OL
290 def get_taux_comparaison(self):
291 try:
292 return rh.TauxChange.objects.filter(implantation=self.implantation, devise=self.devise_comparaison)[0].taux
293 except:
294 return 1
295
296 def get_comp_universite_min_euros(self):
297 return (float)(self.comp_universite_min) * self.get_taux_comparaison()
298
299 def get_comp_fonctionpub_min_euros(self):
300 return (float)(self.comp_fonctionpub_min) * self.get_taux_comparaison()
301
302 def get_comp_locale_min_euros(self):
303 return (float)(self.comp_locale_min) * self.get_taux_comparaison()
304
305 def get_comp_ong_min_euros(self):
306 return (float)(self.comp_ong_min) * self.get_taux_comparaison()
307
308 def get_comp_autre_min_euros(self):
309 return (float)(self.comp_autre_min) * self.get_taux_comparaison()
310
311 def get_comp_universite_max_euros(self):
312 return (float)(self.comp_universite_max) * self.get_taux_comparaison()
313
314 def get_comp_fonctionpub_max_euros(self):
315 return (float)(self.comp_fonctionpub_max) * self.get_taux_comparaison()
316
317 def get_comp_locale_max_euros(self):
318 return (float)(self.comp_locale_max) * self.get_taux_comparaison()
319
320 def get_comp_ong_max_euros(self):
321 return (float)(self.comp_ong_max) * self.get_taux_comparaison()
322
323 def get_comp_autre_max_euros(self):
324 return (float)(self.comp_autre_max) * self.get_taux_comparaison()
325
a9e52624 326
5d680e84 327 def __unicode__(self):
f3333b0e 328 """
2d4d2fcf 329 Cette fonction est consommatrice SQL car elle cherche les dossiers
330 qui ont été liés à celui-ci.
f3333b0e
OL
331 """
332 complement_nom_poste = self.get_complement_nom()
c7a4aa2b
OL
333 if complement_nom_poste is None:
334 complement_nom_poste = ""
f3333b0e
OL
335 data = (
336 self.implantation,
337 self.type_poste.nom,
338 self.nom,
f3333b0e 339 )
a7c68130 340 return u'%s - %s (%s)' % data
5d680e84 341
bd28238f 342
a9c281dd
OL
343# Tester l'enregistrement car les models.py sont importés au complet
344if not reversion.is_registered(Poste):
345 reversion.register(Poste)
346
bd28238f 347
5d680e84
NC
348POSTE_FINANCEMENT_CHOICES = (
349 ('A', 'A - Frais de personnel'),
350 ('B', 'B - Projet(s)-Titre(s)'),
351 ('C', 'C - Autre')
352)
bd28238f 353
5d680e84 354class PosteFinancement(models.Model):
5d680e84
NC
355 poste = models.ForeignKey('Poste', related_name='financements')
356 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
43d04712 357 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
358 help_text="ex.: 33.33 % (décimale avec point)")
359 commentaire = models.TextField(
360 help_text="Spécifiez la source de financement.")
bd28238f 361
43d04712 362 class Meta:
363 ordering = ['type']
bd28238f 364
c0413a6f
OL
365 def __unicode__(self):
366 return u"%s %s %s" % (self.get_type_display(), self.pourcentage, self.commentaire)
367
c589d980 368
2d4d2fcf 369class PostePiece(models.Model):
370 """Documents relatifs au Poste
371 Ex.: Description de poste
372 """
373 poste = models.ForeignKey("Poste")
374 nom = models.CharField(verbose_name="Nom", max_length=255)
375 fichier = models.FileField(verbose_name="Fichier",
376 upload_to=poste_piece_dispatch,
377 storage=storage_prive)
378
068d1462
OL
379class PosteComparaison(models.Model):
380 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
e61bfeb0 381 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
a42c20ef
OL
382 statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name='Statut', null=True, blank=True, )
383 classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name='Classement', null=True, blank=True, )
068d1462
OL
384 nom = models.CharField(verbose_name="Poste", max_length=255, null=True, blank=True)
385 montant = models.IntegerField(null=True)
386 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
1d0f4eef
OL
387
388 def taux_devise(self):
389 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
390 if len(liste_taux) == 0:
391 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
392 else:
393 return liste_taux[0].taux
394
395 def montant_euros(self):
396 return round(float(self.montant) * float(self.taux_devise()), 2)
068d1462
OL
397
398
2d4d2fcf 399### EMPLOYÉ/PERSONNE
400
401# TODO : migration pour m -> M, f -> F
c589d980 402
bd28238f 403GENRE_CHOICES = (
139686f2
NC
404 ('m', 'Homme'),
405 ('f', 'Femme'),
bd28238f
NC
406)
407
bd28238f 408class Employe(models.Model):
bd28238f
NC
409
410 # Modèle existant
da3ca955 411 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
412 verbose_name='Employé')
bd28238f 413 nom = models.CharField(max_length=255)
da3ca955 414 prenom = models.CharField(max_length=255, verbose_name='Prénom')
07b40eda 415 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
bd28238f 416
139686f2 417 def __unicode__(self):
2d4d2fcf 418 return u'%s %s' % (self.prenom, self.nom.upper())
139686f2 419
bd28238f 420
2d4d2fcf 421### DOSSIER
422
423STATUT_RESIDENCE_CHOICES = (
424 ('local', 'Local'),
425 ('expat', 'Expatrié'),
426)
427
bd28238f 428COMPTE_COMPTA_CHOICES = (
494ff2be
NC
429 ('coda', 'CODA'),
430 ('scs', 'SCS'),
431 ('aucun', 'Aucun'),
bd28238f
NC
432)
433
afc204bf 434class Dossier(DossierWorkflow, models.Model):
bd28238f
NC
435
436 # Modèle existant
139686f2 437 employe = models.ForeignKey('Employe', related_name='+', editable=False)
4e4a3545 438 poste = models.ForeignKey('Poste', related_name='dossiers', editable=False)
5d680e84 439 statut = models.ForeignKey(rh.Statut, related_name='+')
dfc2c344 440 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
1f109689 441 null=True, blank=True,
dfc2c344 442 verbose_name="Organisme",
443 help_text="Si détaché (DET) ou mis à disposition (MAD), \
444 préciser l'organisme.",
445 related_name='+')
0288adb5
OL
446 organisme_bstg_autre = models.CharField(max_length=255,
447 verbose_name="Autre organisme",
448 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
449 null=True,
450 blank=True,)
bd28238f 451
139686f2
NC
452 # Données antérieures de l'employé
453 statut_anterieur = models.ForeignKey(
454 rh.Statut, related_name='+', null=True, blank=True,
da3ca955 455 verbose_name='Statut antérieur')
139686f2
NC
456 classement_anterieur = models.ForeignKey(
457 rh.Classement, related_name='+', null=True, blank=True,
458 verbose_name='Classement précédent')
459 salaire_anterieur = models.DecimalField(
460 max_digits=12, decimal_places=2, null=True, default=None,
461 blank=True, verbose_name='Salaire précédent')
462
463 # Données du titulaire précédent
464 employe_anterieur = models.ForeignKey(
465 rh.Employe, related_name='+', null=True, blank=True,
466 verbose_name='Employé précédent')
467 statut_titulaire_anterieur = models.ForeignKey(
468 rh.Statut, related_name='+', null=True, blank=True,
469 verbose_name='Statut titulaire précédent')
470 classement_titulaire_anterieur = models.ForeignKey(
471 rh.Classement, related_name='+', null=True, blank=True,
472 verbose_name='Classement titulaire précédent')
473 salaire_titulaire_anterieur = models.DecimalField(
474 max_digits=12, decimal_places=2, default=None, null=True,
475 blank=True, verbose_name='Salaire titulaire précédent')
494ff2be 476
bd28238f
NC
477 # Recrutement
478 remplacement = models.BooleanField()
4d25e2ba 479 statut_residence = models.CharField(max_length=10, default='local',
2d4d2fcf 480 verbose_name="Statut",
481 choices=STATUT_RESIDENCE_CHOICES)
bd28238f
NC
482
483 # Rémunération
494ff2be 484 classement = models.ForeignKey(rh.Classement, related_name='+',
2d4d2fcf 485 null=True, blank=True,
486 verbose_name='Classement proposé')
494ff2be 487 salaire = models.DecimalField(max_digits=12, decimal_places=2,
2d4d2fcf 488 verbose_name='Salaire de base',
489 null=True, default=None)
e8e75458 490 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
2d4d2fcf 491 regime_travail = models.DecimalField(max_digits=12,
492 decimal_places=2,
493 default=REGIME_TRAVAIL_DEFAULT,
494 verbose_name="Régime de travail",
495 help_text="% du temps complet")
139686f2 496 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 497 decimal_places=2,
498 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
499 verbose_name="Nb. heures par semaine")
bd28238f
NC
500
501 # Contrat
5d680e84 502 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
4d25e2ba 503 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
1f109689 504 contrat_date_fin = models.DateField(null=True, blank=True,
505 help_text="format: aaaa-mm-jj")
bd28238f 506
29dffede
OL
507 # Justifications
508 justif_nouveau_statut_label = u'Justifier le statut que ce type de poste nécessite (national, expatrié, màd ou détachement)'
509 justif_nouveau_statut = models.TextField(verbose_name=justif_nouveau_statut_label, null=True, blank=True)
a83daab3 510 justif_nouveau_tmp_remplacement_label = u"Si l'employé effectue un remplacement temporaire, préciser"
29dffede 511 justif_nouveau_tmp_remplacement = models.TextField(verbose_name=justif_nouveau_tmp_remplacement_label, null=True, blank=True)
a83daab3 512 justif_nouveau_salaire_label = u"Si le salaire de l'employé ne correspond pas au classement du poste ou est différent du salaire antérieur, justifier "
29dffede 513 justif_nouveau_salaire = models.TextField(verbose_name=justif_nouveau_salaire_label, null=True, blank=True)
a83daab3 514 justif_nouveau_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
29dffede
OL
515 justif_nouveau_commentaire = models.TextField(verbose_name=justif_nouveau_commentaire_label, null=True, blank=True)
516 justif_rempl_type_contrat_label = u"Changement de type de contrat, ex : d'un CDD en CDI"
517 justif_rempl_type_contrat = models.TextField(verbose_name=justif_rempl_type_contrat_label, null=True, blank=True)
a83daab3 518 justif_rempl_statut_employe_label = u"Si le statut de l'employé a été modifié pour ce poste ; ex :national, expatrié, màd, détachement ? Si oui, justifier"
29dffede 519 justif_rempl_statut_employe = models.TextField(verbose_name=justif_rempl_statut_employe_label, null=True, blank=True)
a83daab3 520 justif_rempl_evaluation_label = u"L'évaluation de l'employé est-elle favorable? Préciser"
29dffede
OL
521 justif_rempl_evaluation = models.TextField(verbose_name=justif_rempl_evaluation_label, null=True, blank=True)
522 justif_rempl_salaire_label = u"Si le salaire de l'employé est modifié et/ou ne correspond pas à son classement, justifier"
523 justif_rempl_salaire = models.TextField(verbose_name=justif_rempl_salaire_label, null=True, blank=True)
a83daab3 524 justif_rempl_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
29dffede
OL
525 justif_rempl_commentaire = models.TextField(verbose_name=justif_rempl_commentaire_label, null=True, blank=True)
526
bd28238f 527 # Comptes
dfc2c344 528 compte_compta = models.CharField(max_length=10, default='aucun',
529 verbose_name=u'Compte comptabilité',
530 choices=COMPTE_COMPTA_CHOICES)
bd28238f 531 compte_courriel = models.BooleanField()
0140cbd2 532
533 # Méta
534 date_creation = models.DateTimeField(auto_now_add=True)
e4f56614
OL
535
536 # Managers
537 objects = DossierManager()
538
aec2c91e 539 def __unicode__(self):
e4f56614 540 return u'[%s] %s - %s' % (self.poste.implantation, self.poste.nom, self.employe)
bd28238f 541
b1baa306
OL
542 def get_salaire_euros(self):
543 try:
55cc28de 544 tx = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.poste.implantation)[0].taux
b1baa306 545 except:
55cc28de 546 tx = 0
b1baa306
OL
547 return (float)(tx) * (float)(self.salaire)
548
bf6fbbcf 549 def get_remunerations_brutes(self):
57bd966c 550 """
bf6fbbcf
OL
551 1 Salaire de base
552 3 Indemnité de base
553 4 Indemnité d'expatriation
554 5 Indemnité pour frais
555 6 Indemnité de logement
556 7 Indemnité de fonction
557 8 Indemnité de responsabilité
558 9 Indemnité de transport
559 10 Indemnité compensatrice
560 11 Indemnité de subsistance
561 12 Indemnité différentielle
562 13 Prime d'installation
563 14 Billet d'avion
564 15 Déménagement
565 16 Indemnité de départ
566 18 Prime de 13ième mois
567 19 Prime d'intérim
57bd966c 568 """
bf6fbbcf
OL
569 ids = [1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19]
570 return [r for r in self.remuneration_set.all() if r.type_id in ids]
571
572 def get_charges_salariales(self):
573 """
574 20 Charges salariales ?
575 """
576 ids = [20, ]
577 return [r for r in self.remuneration_set.all() if r.type_id in ids]
578
579 def get_total_charges_salariales(self):
580 total = 0.0
581 for r in self.get_charges_salariales():
582 total += r.montant_euro()
583 return total
584
585 def get_charges_patronales(self):
586 """
587 17 Charges patronales
588 """
589 ids = [17, ]
590 return [r for r in self.remuneration_set.all() if r.type_id in ids]
591
592 def get_total_charges_patronales(self):
a9e52624 593 total = 0.0
bf6fbbcf 594 for r in self.get_charges_patronales():
a9e52624
OL
595 total += r.montant_euro()
596 return total
57bd966c 597
bf6fbbcf
OL
598 def get_salaire_brut(self):
599 """
600 somme des rémuérations brutes
601 """
602 total = 0.0
603 for r in self.get_remunerations_brutes():
604 total += r.montant_euro()
605 return total
606
607 def get_salaire_net(self):
608 """
609 salaire brut - charges salariales
610 """
611 total_charges = 0.0
612 for r in self.get_charges_salariales():
613 total_charges += r.montant_euro()
614 return self.get_salaire_brut() - total_charges
615
616 def get_couts_auf(self):
617 """
618 salaire net + charges patronales
619 """
620 total_charges = 0.0
621 for r in self.get_charges_patronales():
622 total_charges += r.montant_euro()
623 return self.get_salaire_net() + total_charges
624
625 def get_remunerations_tierces(self):
57bd966c 626 """
bf6fbbcf 627 2 Salaire MAD
57bd966c
OL
628 """
629 return [r for r in self.remuneration_set.all() if r.type_id in (2, )]
630
bf6fbbcf 631 def get_total_remunerations_tierces(self):
a9e52624 632 total = 0.0
bf6fbbcf 633 for r in self.get_remunerations_tierces():
a9e52624
OL
634 total += r.montant_euro()
635 return total
636
bd28238f 637
0140cbd2 638# Tester l'enregistrement car les models.py sont importés au complet
639if not reversion.is_registered(Dossier):
640 reversion.register(Dossier)
bd28238f 641
2d4d2fcf 642class DossierPiece(models.Model):
643 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
644 Ex.: Lettre de motivation.
645 """
646 dossier = models.ForeignKey("Dossier")
647 nom = models.CharField(verbose_name="Nom", max_length=255)
648 fichier = models.FileField(verbose_name="Fichier",
649 upload_to=dossier_piece_dispatch,
650 storage=storage_prive)
651
652
03b395db
OL
653class DossierComparaison(models.Model):
654 """
655 Photo d'une comparaison salariale au moment de l'embauche.
656 """
657 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
119bc407
OL
658 statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name='Statut', null=True, blank=True, )
659 classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name='Classement', null=True, blank=True, )
94d70519 660 implantation = models.ForeignKey(ref.Implantation, related_name='+', null=True, blank=True)
03b395db
OL
661 poste = models.CharField(max_length=255, null=True, blank=True)
662 personne = models.CharField(max_length=255, null=True, blank=True)
663 montant = models.IntegerField(null=True)
664 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
1d0f4eef
OL
665
666 def taux_devise(self):
667 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
668 if len(liste_taux) == 0:
669 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
670 else:
671 return liste_taux[0].taux
672
673 def montant_euros(self):
674 return round(float(self.montant) * float(self.taux_devise()), 2)
675
03b395db 676
c589d980 677
2d4d2fcf 678### RÉMUNÉRATION
679
bd28238f 680class Remuneration(models.Model):
5d680e84 681 # Identification
bd28238f 682 dossier = models.ForeignKey('Dossier', db_column='dossier')
cb1d62b5
NC
683 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
684 related_name='+')
cb1d62b5
NC
685 montant = models.DecimalField(max_digits=12, decimal_places=2,
686 null=True) # Annuel
494ff2be 687 devise = models.ForeignKey(rh.Devise, to_field='code',
5d680e84 688 db_column='devise', related_name='+')
cb1d62b5 689 precision = models.CharField(max_length=255, null=True, blank=True)
bd28238f 690
5d680e84 691 # Méta
bd28238f 692 date_creation = models.DateField(auto_now_add=True)
2d4d2fcf 693 user_creation = models.IntegerField(null=True, blank=True) # TODO : user
bd28238f 694
ee91bc95
NC
695 def montant_mois(self):
696 return round(self.montant / 12, 2)
697
698 def taux_devise(self):
dc4b78a7
OL
699 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
700 if len(liste_taux) == 0:
701 raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
702 else:
703 return liste_taux[0].taux
ee91bc95
NC
704
705 def montant_euro(self):
a9e52624 706 return round(float(self.montant) * float(self.taux_devise()), 2)
ee91bc95
NC
707
708 def montant_euro_mois(self):
709 return round(self.montant_euro() / 12, 2)