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