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