#2076
[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):
b6825282
OL
216 taux_changes = rh.TauxChange.objects.filter(devise=self.devise_min).order_by('annee')
217 for t in taux_changes:
218 if t.implantation == self.implantation:
219 return t.taux
220 if len(taux_changes) > 0:
221 return taux_changes[0].taux
222 else:
ae5c920b 223 raise DeviseException('Taux indisponible pour la devise %s (%s)' % (self.devise_min, self.implantation))
c0413a6f
OL
224
225 def get_couts_minimum_euros(self):
226 return self.get_couts_minimum() * self.get_taux_minimum()
227
228 def get_couts_maximum(self):
5f61bccb 229 return (float)(self.salaire_max + self.indemn_expat_max + + self.indemn_fct_max + self.charges_patronales_max + self.autre_max)
c0413a6f
OL
230
231 def get_taux_maximum(self):
b6825282
OL
232 taux_changes = rh.TauxChange.objects.filter(devise=self.devise_max).order_by('annee')
233 for t in taux_changes:
234 if t.implantation == self.implantation:
235 return t.taux
236 if len(taux_changes) > 0:
237 return taux_changes[0].taux
238 else:
ae5c920b 239 raise DeviseException('Taux indisponible pour la devise %s (%s)' % (self.devise_max, self.implantation))
c0413a6f
OL
240
241 def get_couts_maximum_euros(self):
242 return self.get_couts_maximum() * self.get_taux_maximum()
243
12c7f8a7
OL
244
245 def show_taux_minimum(self):
246 try:
247 return self.get_taux_minimum()
248 except Exception, e:
249 return e
250
251 def show_couts_minimum_euros(self):
252 try:
253 return self.get_couts_minimum_euros()
254 except Exception, e:
255 return e
256
257 def show_taux_maximum(self):
258 try:
259 return self.get_taux_maximum()
260 except Exception, e:
261 return e
262
263 def show_couts_maximum_euros(self):
264 try:
265 return self.get_couts_maximum_euros()
266 except Exception, e:
267 return e
268
269
c0413a6f
OL
270 ######################
271 # Comparaison de poste
272 ######################
a3fee9c5
OL
273
274 def est_comparable(self):
275 """
276 Si on a au moins une valeur de saisie dans les comparaisons, alors le poste
277 est comparable.
278 """
279 if self.comp_universite_min is None and \
280 self.comp_fonctionpub_min is None and \
281 self.comp_locale_min is None and \
282 self.comp_ong_min is None and \
283 self.comp_autre_min is None and \
284 self.comp_universite_max is None and \
285 self.comp_fonctionpub_max is None and \
286 self.comp_locale_max is None and \
287 self.comp_ong_max is None and \
288 self.comp_autre_max is None:
289 return False
290 else:
291 return True
c0413a6f 292
a3fee9c5 293
c0413a6f
OL
294 def get_taux_comparaison(self):
295 try:
296 return rh.TauxChange.objects.filter(implantation=self.implantation, devise=self.devise_comparaison)[0].taux
297 except:
298 return 1
299
300 def get_comp_universite_min_euros(self):
301 return (float)(self.comp_universite_min) * self.get_taux_comparaison()
302
303 def get_comp_fonctionpub_min_euros(self):
304 return (float)(self.comp_fonctionpub_min) * self.get_taux_comparaison()
305
306 def get_comp_locale_min_euros(self):
307 return (float)(self.comp_locale_min) * self.get_taux_comparaison()
308
309 def get_comp_ong_min_euros(self):
310 return (float)(self.comp_ong_min) * self.get_taux_comparaison()
311
312 def get_comp_autre_min_euros(self):
313 return (float)(self.comp_autre_min) * self.get_taux_comparaison()
314
315 def get_comp_universite_max_euros(self):
316 return (float)(self.comp_universite_max) * self.get_taux_comparaison()
317
318 def get_comp_fonctionpub_max_euros(self):
319 return (float)(self.comp_fonctionpub_max) * self.get_taux_comparaison()
320
321 def get_comp_locale_max_euros(self):
322 return (float)(self.comp_locale_max) * self.get_taux_comparaison()
323
324 def get_comp_ong_max_euros(self):
325 return (float)(self.comp_ong_max) * self.get_taux_comparaison()
326
327 def get_comp_autre_max_euros(self):
328 return (float)(self.comp_autre_max) * self.get_taux_comparaison()
329
a9e52624 330
5d680e84 331 def __unicode__(self):
f3333b0e 332 """
2d4d2fcf 333 Cette fonction est consommatrice SQL car elle cherche les dossiers
334 qui ont été liés à celui-ci.
f3333b0e
OL
335 """
336 complement_nom_poste = self.get_complement_nom()
c7a4aa2b
OL
337 if complement_nom_poste is None:
338 complement_nom_poste = ""
f3333b0e
OL
339 data = (
340 self.implantation,
341 self.type_poste.nom,
342 self.nom,
f3333b0e 343 )
a7c68130 344 return u'%s - %s (%s)' % data
5d680e84 345
bd28238f 346
a9c281dd
OL
347# Tester l'enregistrement car les models.py sont importés au complet
348if not reversion.is_registered(Poste):
349 reversion.register(Poste)
350
bd28238f 351
5d680e84
NC
352POSTE_FINANCEMENT_CHOICES = (
353 ('A', 'A - Frais de personnel'),
354 ('B', 'B - Projet(s)-Titre(s)'),
355 ('C', 'C - Autre')
356)
bd28238f 357
5d680e84 358class PosteFinancement(models.Model):
5d680e84
NC
359 poste = models.ForeignKey('Poste', related_name='financements')
360 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
43d04712 361 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
362 help_text="ex.: 33.33 % (décimale avec point)")
363 commentaire = models.TextField(
364 help_text="Spécifiez la source de financement.")
bd28238f 365
43d04712 366 class Meta:
367 ordering = ['type']
bd28238f 368
c0413a6f
OL
369 def __unicode__(self):
370 return u"%s %s %s" % (self.get_type_display(), self.pourcentage, self.commentaire)
371
c589d980 372
2d4d2fcf 373class PostePiece(models.Model):
374 """Documents relatifs au Poste
375 Ex.: Description de poste
376 """
377 poste = models.ForeignKey("Poste")
378 nom = models.CharField(verbose_name="Nom", max_length=255)
379 fichier = models.FileField(verbose_name="Fichier",
380 upload_to=poste_piece_dispatch,
381 storage=storage_prive)
382
068d1462
OL
383class PosteComparaison(models.Model):
384 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
a42c20ef
OL
385 statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name='Statut', null=True, blank=True, )
386 classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name='Classement', null=True, blank=True, )
068d1462
OL
387 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True)
388 nom = models.CharField(verbose_name="Poste", max_length=255, null=True, blank=True)
389 montant = models.IntegerField(null=True)
390 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
1d0f4eef
OL
391
392 def taux_devise(self):
ae5c920b
OL
393 if self.devise.code == 'EUR':
394 return 1
1d0f4eef
OL
395 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
396 if len(liste_taux) == 0:
ae5c920b 397 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
1d0f4eef
OL
398 else:
399 return liste_taux[0].taux
400
401 def montant_euros(self):
402 return round(float(self.montant) * float(self.taux_devise()), 2)
068d1462
OL
403
404
2d4d2fcf 405### EMPLOYÉ/PERSONNE
406
407# TODO : migration pour m -> M, f -> F
c589d980 408
bd28238f 409GENRE_CHOICES = (
139686f2
NC
410 ('m', 'Homme'),
411 ('f', 'Femme'),
bd28238f
NC
412)
413
bd28238f 414class Employe(models.Model):
bd28238f
NC
415
416 # Modèle existant
da3ca955 417 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
418 verbose_name='Employé')
bd28238f 419 nom = models.CharField(max_length=255)
da3ca955 420 prenom = models.CharField(max_length=255, verbose_name='Prénom')
07b40eda 421 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
bd28238f 422
139686f2 423 def __unicode__(self):
2d4d2fcf 424 return u'%s %s' % (self.prenom, self.nom.upper())
139686f2 425
bd28238f 426
2d4d2fcf 427### DOSSIER
428
429STATUT_RESIDENCE_CHOICES = (
430 ('local', 'Local'),
431 ('expat', 'Expatrié'),
432)
433
bd28238f 434COMPTE_COMPTA_CHOICES = (
494ff2be
NC
435 ('coda', 'CODA'),
436 ('scs', 'SCS'),
437 ('aucun', 'Aucun'),
bd28238f
NC
438)
439
afc204bf 440class Dossier(DossierWorkflow, models.Model):
bd28238f
NC
441
442 # Modèle existant
139686f2 443 employe = models.ForeignKey('Employe', related_name='+', editable=False)
4e4a3545 444 poste = models.ForeignKey('Poste', related_name='dossiers', editable=False)
5d680e84 445 statut = models.ForeignKey(rh.Statut, related_name='+')
dfc2c344 446 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
1f109689 447 null=True, blank=True,
dfc2c344 448 verbose_name="Organisme",
449 help_text="Si détaché (DET) ou mis à disposition (MAD), \
450 préciser l'organisme.",
451 related_name='+')
0288adb5
OL
452 organisme_bstg_autre = models.CharField(max_length=255,
453 verbose_name="Autre organisme",
454 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
455 null=True,
456 blank=True,)
bd28238f 457
139686f2
NC
458 # Données antérieures de l'employé
459 statut_anterieur = models.ForeignKey(
460 rh.Statut, related_name='+', null=True, blank=True,
da3ca955 461 verbose_name='Statut antérieur')
139686f2
NC
462 classement_anterieur = models.ForeignKey(
463 rh.Classement, related_name='+', null=True, blank=True,
464 verbose_name='Classement précédent')
465 salaire_anterieur = models.DecimalField(
466 max_digits=12, decimal_places=2, null=True, default=None,
467 blank=True, verbose_name='Salaire précédent')
f8c7c0b8 468 devise_anterieur = models.ForeignKey(rh.Devise, related_name='+', null=True, blank=True)
67c15007 469 type_contrat_anterieur = models.ForeignKey(rh.TypeContrat, related_name='+', null=True, blank=True, verbose_name=u'Type contrat antérieur', )
139686f2
NC
470
471 # Données du titulaire précédent
472 employe_anterieur = models.ForeignKey(
473 rh.Employe, related_name='+', null=True, blank=True,
474 verbose_name='Employé précédent')
475 statut_titulaire_anterieur = models.ForeignKey(
476 rh.Statut, related_name='+', null=True, blank=True,
477 verbose_name='Statut titulaire précédent')
478 classement_titulaire_anterieur = models.ForeignKey(
479 rh.Classement, related_name='+', null=True, blank=True,
480 verbose_name='Classement titulaire précédent')
481 salaire_titulaire_anterieur = models.DecimalField(
482 max_digits=12, decimal_places=2, default=None, null=True,
483 blank=True, verbose_name='Salaire titulaire précédent')
f8c7c0b8 484 devise_titulaire_anterieur = models.ForeignKey(rh.Devise, related_name='+', null=True, blank=True)
494ff2be 485
bd28238f
NC
486 # Recrutement
487 remplacement = models.BooleanField()
4d25e2ba 488 statut_residence = models.CharField(max_length=10, default='local',
2d4d2fcf 489 verbose_name="Statut",
490 choices=STATUT_RESIDENCE_CHOICES)
bd28238f
NC
491
492 # Rémunération
494ff2be 493 classement = models.ForeignKey(rh.Classement, related_name='+',
2d4d2fcf 494 null=True, blank=True,
495 verbose_name='Classement proposé')
494ff2be 496 salaire = models.DecimalField(max_digits=12, decimal_places=2,
2d4d2fcf 497 verbose_name='Salaire de base',
498 null=True, default=None)
e8e75458 499 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
2d4d2fcf 500 regime_travail = models.DecimalField(max_digits=12,
501 decimal_places=2,
502 default=REGIME_TRAVAIL_DEFAULT,
503 verbose_name="Régime de travail",
504 help_text="% du temps complet")
139686f2 505 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 506 decimal_places=2,
507 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
508 verbose_name="Nb. heures par semaine")
bd28238f
NC
509
510 # Contrat
5d680e84 511 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
4d25e2ba 512 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
1f109689 513 contrat_date_fin = models.DateField(null=True, blank=True,
514 help_text="format: aaaa-mm-jj")
bd28238f 515
29dffede
OL
516 # Justifications
517 justif_nouveau_statut_label = u'Justifier le statut que ce type de poste nécessite (national, expatrié, màd ou détachement)'
518 justif_nouveau_statut = models.TextField(verbose_name=justif_nouveau_statut_label, null=True, blank=True)
a83daab3 519 justif_nouveau_tmp_remplacement_label = u"Si l'employé effectue un remplacement temporaire, préciser"
29dffede 520 justif_nouveau_tmp_remplacement = models.TextField(verbose_name=justif_nouveau_tmp_remplacement_label, null=True, blank=True)
a83daab3 521 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 522 justif_nouveau_salaire = models.TextField(verbose_name=justif_nouveau_salaire_label, null=True, blank=True)
a83daab3 523 justif_nouveau_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
29dffede
OL
524 justif_nouveau_commentaire = models.TextField(verbose_name=justif_nouveau_commentaire_label, null=True, blank=True)
525 justif_rempl_type_contrat_label = u"Changement de type de contrat, ex : d'un CDD en CDI"
526 justif_rempl_type_contrat = models.TextField(verbose_name=justif_rempl_type_contrat_label, null=True, blank=True)
a83daab3 527 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 528 justif_rempl_statut_employe = models.TextField(verbose_name=justif_rempl_statut_employe_label, null=True, blank=True)
a83daab3 529 justif_rempl_evaluation_label = u"L'évaluation de l'employé est-elle favorable? Préciser"
29dffede
OL
530 justif_rempl_evaluation = models.TextField(verbose_name=justif_rempl_evaluation_label, null=True, blank=True)
531 justif_rempl_salaire_label = u"Si le salaire de l'employé est modifié et/ou ne correspond pas à son classement, justifier"
532 justif_rempl_salaire = models.TextField(verbose_name=justif_rempl_salaire_label, null=True, blank=True)
a83daab3 533 justif_rempl_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
29dffede
OL
534 justif_rempl_commentaire = models.TextField(verbose_name=justif_rempl_commentaire_label, null=True, blank=True)
535
bd28238f 536 # Comptes
dfc2c344 537 compte_compta = models.CharField(max_length=10, default='aucun',
538 verbose_name=u'Compte comptabilité',
539 choices=COMPTE_COMPTA_CHOICES)
bd28238f 540 compte_courriel = models.BooleanField()
0140cbd2 541
542 # Méta
543 date_creation = models.DateTimeField(auto_now_add=True)
e4f56614
OL
544
545 # Managers
546 objects = DossierManager()
547
aec2c91e 548 def __unicode__(self):
e4f56614 549 return u'[%s] %s - %s' % (self.poste.implantation, self.poste.nom, self.employe)
bd28238f 550
a7186cbb
OL
551 def taux_devise(self):
552 if self.devise.code == 'EUR':
553 return 1
554 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.poste.implantation)
555 if len(liste_taux) == 0:
556 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.poste.implantation))
557 else:
558 return liste_taux[0].taux
559
eed93931
OL
560 def get_salaire_anterieur_euros(self):
561 if self.devise_anterieur.code == 'EUR':
562 tx = 1
563 else:
564 liste_taux = self.devise_anterieur.tauxchange_set.order_by('-annee').filter(implantation=self.poste.implantation)
565 if len(liste_taux) == 0:
566 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_anterieur, self.poste.implantation))
567 tx = liste_taux[0].taux
568 return (float)(tx) * (float)(self.salaire_anterieur)
569
570 def get_salaire_titulaire_anterieur_euros(self):
571 if self.devise_anterieur.code == 'EUR':
572 tx = 1
573 else:
574 liste_taux = self.devise_titulaire_anterieur.tauxchange_set.order_by('-annee').filter(implantation=self.poste.implantation)
575 if len(liste_taux) == 0:
576 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_titulaire_anterieur, self.poste.implantation))
577 tx = liste_taux[0].taux
578 return (float)(tx) * (float)(self.salaire_titulaire_anterieur)
579
b1baa306 580 def get_salaire_euros(self):
a7186cbb 581 tx = self.taux_devise()
b1baa306
OL
582 return (float)(tx) * (float)(self.salaire)
583
bf6fbbcf 584 def get_remunerations_brutes(self):
57bd966c 585 """
bf6fbbcf
OL
586 1 Salaire de base
587 3 Indemnité de base
588 4 Indemnité d'expatriation
589 5 Indemnité pour frais
590 6 Indemnité de logement
591 7 Indemnité de fonction
592 8 Indemnité de responsabilité
593 9 Indemnité de transport
594 10 Indemnité compensatrice
595 11 Indemnité de subsistance
596 12 Indemnité différentielle
597 13 Prime d'installation
598 14 Billet d'avion
599 15 Déménagement
600 16 Indemnité de départ
601 18 Prime de 13ième mois
602 19 Prime d'intérim
57bd966c 603 """
bf6fbbcf
OL
604 ids = [1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19]
605 return [r for r in self.remuneration_set.all() if r.type_id in ids]
606
607 def get_charges_salariales(self):
608 """
609 20 Charges salariales ?
610 """
611 ids = [20, ]
612 return [r for r in self.remuneration_set.all() if r.type_id in ids]
613
614 def get_total_charges_salariales(self):
615 total = 0.0
616 for r in self.get_charges_salariales():
617 total += r.montant_euro()
618 return total
619
620 def get_charges_patronales(self):
621 """
622 17 Charges patronales
623 """
624 ids = [17, ]
625 return [r for r in self.remuneration_set.all() if r.type_id in ids]
626
627 def get_total_charges_patronales(self):
a9e52624 628 total = 0.0
bf6fbbcf 629 for r in self.get_charges_patronales():
a9e52624
OL
630 total += r.montant_euro()
631 return total
57bd966c 632
bf6fbbcf
OL
633 def get_salaire_brut(self):
634 """
635 somme des rémuérations brutes
636 """
637 total = 0.0
638 for r in self.get_remunerations_brutes():
639 total += r.montant_euro()
640 return total
641
642 def get_salaire_net(self):
643 """
644 salaire brut - charges salariales
645 """
646 total_charges = 0.0
647 for r in self.get_charges_salariales():
648 total_charges += r.montant_euro()
649 return self.get_salaire_brut() - total_charges
650
651 def get_couts_auf(self):
652 """
653 salaire net + charges patronales
654 """
655 total_charges = 0.0
656 for r in self.get_charges_patronales():
657 total_charges += r.montant_euro()
658 return self.get_salaire_net() + total_charges
659
660 def get_remunerations_tierces(self):
57bd966c 661 """
bf6fbbcf 662 2 Salaire MAD
57bd966c
OL
663 """
664 return [r for r in self.remuneration_set.all() if r.type_id in (2, )]
665
bf6fbbcf 666 def get_total_remunerations_tierces(self):
a9e52624 667 total = 0.0
bf6fbbcf 668 for r in self.get_remunerations_tierces():
a9e52624
OL
669 total += r.montant_euro()
670 return total
671
bd28238f 672
0140cbd2 673# Tester l'enregistrement car les models.py sont importés au complet
674if not reversion.is_registered(Dossier):
675 reversion.register(Dossier)
bd28238f 676
2d4d2fcf 677class DossierPiece(models.Model):
678 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
679 Ex.: Lettre de motivation.
680 """
681 dossier = models.ForeignKey("Dossier")
682 nom = models.CharField(verbose_name="Nom", max_length=255)
683 fichier = models.FileField(verbose_name="Fichier",
684 upload_to=dossier_piece_dispatch,
685 storage=storage_prive)
686
687
03b395db
OL
688class DossierComparaison(models.Model):
689 """
690 Photo d'une comparaison salariale au moment de l'embauche.
691 """
692 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
119bc407
OL
693 statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name='Statut', null=True, blank=True, )
694 classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name='Classement', null=True, blank=True, )
03b395db
OL
695 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True)
696 poste = models.CharField(max_length=255, null=True, blank=True)
697 personne = models.CharField(max_length=255, null=True, blank=True)
698 montant = models.IntegerField(null=True)
699 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
1d0f4eef
OL
700
701 def taux_devise(self):
ae5c920b
OL
702 if self.devise.code == 'EUR':
703 return 1
0cbf1827 704 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
1d0f4eef 705 if len(liste_taux) == 0:
0cbf1827 706 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
1d0f4eef
OL
707 else:
708 return liste_taux[0].taux
709
710 def montant_euros(self):
711 return round(float(self.montant) * float(self.taux_devise()), 2)
712
03b395db 713
c589d980 714
2d4d2fcf 715### RÉMUNÉRATION
716
bd28238f 717class Remuneration(models.Model):
5d680e84 718 # Identification
bd28238f 719 dossier = models.ForeignKey('Dossier', db_column='dossier')
cb1d62b5
NC
720 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
721 related_name='+')
cb1d62b5
NC
722 montant = models.DecimalField(max_digits=12, decimal_places=2,
723 null=True) # Annuel
494ff2be 724 devise = models.ForeignKey(rh.Devise, to_field='code',
5d680e84 725 db_column='devise', related_name='+')
cb1d62b5 726 precision = models.CharField(max_length=255, null=True, blank=True)
bd28238f 727
5d680e84 728 # Méta
bd28238f 729 date_creation = models.DateField(auto_now_add=True)
2d4d2fcf 730 user_creation = models.IntegerField(null=True, blank=True) # TODO : user
bd28238f 731
ee91bc95
NC
732 def montant_mois(self):
733 return round(self.montant / 12, 2)
734
735 def taux_devise(self):
ae5c920b
OL
736 if self.devise.code == 'EUR':
737 return 1
dc4b78a7
OL
738 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
739 if len(liste_taux) == 0:
ae5c920b 740 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
dc4b78a7
OL
741 else:
742 return liste_taux[0].taux
ee91bc95
NC
743
744 def montant_euro(self):
a9e52624 745 return round(float(self.montant) * float(self.taux_devise()), 2)
ee91bc95
NC
746
747 def montant_euro_mois(self):
748 return round(self.montant_euro() / 12, 2)