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