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