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