0befb6d17d50a8b822a669c48f147abb29fc0ef2
[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 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:
226 raise DeviseException('Taux indisponible pour la devise %s (%s)' % (self.devise_min, self.implantation))
227
228 def get_couts_minimum_euros(self):
229 return float(self.get_couts_minimum()) * self.get_taux_minimum()
230
231 def get_salaire_minimum_euros(self):
232 return float(self.get_salaire_minimum()) * self.get_taux_minimum()
233
234 def get_couts_maximum(self):
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
239
240 def get_taux_maximum(self):
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:
248 raise DeviseException('Taux indisponible pour la devise %s (%s)' % (self.devise_max, self.implantation))
249
250 def get_couts_maximum_euros(self):
251 return float(self.get_couts_maximum()) * self.get_taux_maximum()
252
253 def get_salaire_maximum_euros(self):
254 return float(self.get_salaire_maximum()) * self.get_taux_maximum()
255
256 def show_taux_minimum(self):
257 try:
258 return self.get_taux_minimum()
259 except DeviseException, e:
260 return e
261
262 def show_couts_minimum_euros(self):
263 try:
264 return self.get_couts_minimum_euros()
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:
272 return e
273
274 def show_taux_maximum(self):
275 try:
276 return self.get_taux_maximum()
277 except DeviseException, e:
278 return e
279
280 def show_couts_maximum_euros(self):
281 try:
282 return self.get_couts_maximum_euros()
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:
290 return e
291
292
293 ######################
294 # Comparaison de poste
295 ######################
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
315
316
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
353
354 def __unicode__(self):
355 """
356 Cette fonction est consommatrice SQL car elle cherche les dossiers
357 qui ont été liés à celui-ci.
358 """
359 complement_nom_poste = self.get_complement_nom()
360 if complement_nom_poste is None:
361 complement_nom_poste = ""
362 data = (
363 self.implantation,
364 self.type_poste.nom,
365 self.nom,
366 )
367 return u'%s - %s (%s)' % data
368
369
370 # Tester l'enregistrement car les models.py sont importés au complet
371 if not reversion.is_registered(Poste):
372 reversion.register(Poste)
373
374
375 POSTE_FINANCEMENT_CHOICES = (
376 ('A', 'A - Frais de personnel'),
377 ('B', 'B - Projet(s)-Titre(s)'),
378 ('C', 'C - Autre')
379 )
380
381 class PosteFinancement(models.Model):
382 poste = models.ForeignKey('Poste', related_name='financements')
383 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
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.")
388
389 class Meta:
390 ordering = ['type']
391
392 def __unicode__(self):
393 return u"%s %s %s" % (self.get_type_display(), self.pourcentage, self.commentaire)
394
395
396 class 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
406 class PosteComparaison(models.Model):
407 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
408 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
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, )
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)
414
415 def taux_devise(self):
416 if self.devise.code == 'EUR':
417 return 1
418 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
419 if len(liste_taux) == 0:
420 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
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)
426
427
428 ### EMPLOYÉ/PERSONNE
429
430 # TODO : migration pour m -> M, f -> F
431
432 GENRE_CHOICES = (
433 ('m', 'Homme'),
434 ('f', 'Femme'),
435 )
436
437 class Employe(models.Model):
438
439 # Modèle existant
440 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
441 verbose_name='Employé')
442 nom = models.CharField(max_length=255)
443 prenom = models.CharField(max_length=255, verbose_name='Prénom')
444 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
445
446 def __unicode__(self):
447 return u'%s %s' % (self.prenom, self.nom.upper())
448
449
450 ### DOSSIER
451
452 STATUT_RESIDENCE_CHOICES = (
453 ('local', 'Local'),
454 ('expat', 'Expatrié'),
455 )
456
457 COMPTE_COMPTA_CHOICES = (
458 ('coda', 'CODA'),
459 ('scs', 'SCS'),
460 ('aucun', 'Aucun'),
461 )
462
463 class Dossier(DossierWorkflow, models.Model):
464
465 # Modèle existant
466 employe = models.ForeignKey('Employe', related_name='+', editable=False)
467 poste = models.ForeignKey('Poste', related_name='dossiers', editable=False)
468 statut = models.ForeignKey(rh.Statut, related_name='+')
469 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
470 null=True, blank=True,
471 verbose_name="Organisme",
472 help_text="Si détaché (DET) ou mis à disposition (MAD), \
473 préciser l'organisme.",
474 related_name='+')
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,)
480
481 # Données antérieures de l'employé
482 statut_anterieur = models.ForeignKey(
483 rh.Statut, related_name='+', null=True, blank=True,
484 verbose_name='Statut antérieur')
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')
491 devise_anterieur = models.ForeignKey(rh.Devise, related_name='+', null=True, blank=True)
492 type_contrat_anterieur = models.ForeignKey(rh.TypeContrat, related_name='+', null=True, blank=True, verbose_name=u'Type contrat antérieur', )
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')
507 devise_titulaire_anterieur = models.ForeignKey(rh.Devise, related_name='+', null=True, blank=True)
508
509 # Recrutement
510 remplacement = models.BooleanField()
511 statut_residence = models.CharField(max_length=10, default='local',
512 verbose_name="Statut",
513 choices=STATUT_RESIDENCE_CHOICES)
514
515 # Rémunération
516 classement = models.ForeignKey(rh.Classement, related_name='+',
517 null=True, blank=True,
518 verbose_name='Classement proposé')
519 salaire = models.DecimalField(max_digits=12, decimal_places=2,
520 verbose_name='Salaire de base',
521 null=True, default=None)
522 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
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")
528 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
529 decimal_places=2,
530 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
531 verbose_name="Nb. heures par semaine")
532
533 # Contrat
534 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
535 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
536 contrat_date_fin = models.DateField(null=True, blank=True,
537 help_text="format: aaaa-mm-jj")
538
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)
542 justif_nouveau_tmp_remplacement_label = u"Si l'employé effectue un remplacement temporaire, préciser"
543 justif_nouveau_tmp_remplacement = models.TextField(verbose_name=justif_nouveau_tmp_remplacement_label, null=True, blank=True)
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 "
545 justif_nouveau_salaire = models.TextField(verbose_name=justif_nouveau_salaire_label, null=True, blank=True)
546 justif_nouveau_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
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)
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"
551 justif_rempl_statut_employe = models.TextField(verbose_name=justif_rempl_statut_employe_label, null=True, blank=True)
552 justif_rempl_evaluation_label = u"L'évaluation de l'employé est-elle favorable? Préciser"
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)
556 justif_rempl_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
557 justif_rempl_commentaire = models.TextField(verbose_name=justif_rempl_commentaire_label, null=True, blank=True)
558
559 # Comptes
560 compte_compta = models.CharField(max_length=10, default='aucun',
561 verbose_name=u'Compte comptabilité',
562 choices=COMPTE_COMPTA_CHOICES)
563 compte_courriel = models.BooleanField()
564
565 # Méta
566 date_creation = models.DateTimeField(auto_now_add=True)
567
568 # Managers
569 objects = DossierManager()
570
571 def __unicode__(self):
572 return u'[%s] %s - %s' % (self.poste.implantation, self.poste.nom, self.employe)
573
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
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
603 def get_salaire_euros(self):
604 tx = self.taux_devise()
605 return (float)(tx) * (float)(self.salaire)
606
607 def get_remunerations_brutes(self):
608 """
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
626 """
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):
651 total = 0.0
652 for r in self.get_charges_patronales():
653 total += r.montant_euro()
654 return total
655
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):
684 """
685 2 Salaire MAD
686 """
687 return [r for r in self.remuneration_set.all() if r.type_id in (2, )]
688
689 def get_total_remunerations_tierces(self):
690 total = 0.0
691 for r in self.get_remunerations_tierces():
692 total += r.montant_euro()
693 return total
694
695
696 # Tester l'enregistrement car les models.py sont importés au complet
697 if not reversion.is_registered(Dossier):
698 reversion.register(Dossier)
699
700 class 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
711 class DossierComparaison(models.Model):
712 """
713 Photo d'une comparaison salariale au moment de l'embauche.
714 """
715 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
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, )
718 implantation = models.ForeignKey(ref.Implantation, related_name='+', null=True, blank=True)
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)
723
724 def taux_devise(self):
725 if self.devise.code == 'EUR':
726 return 1
727 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
728 if len(liste_taux) == 0:
729 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
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
736
737
738 ### RÉMUNÉRATION
739
740 class Remuneration(models.Model):
741 # Identification
742 dossier = models.ForeignKey('Dossier', db_column='dossier')
743 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
744 related_name='+')
745 montant = models.DecimalField(max_digits=12, decimal_places=2,
746 null=True) # Annuel
747 devise = models.ForeignKey(rh.Devise, to_field='code',
748 db_column='devise', related_name='+')
749 precision = models.CharField(max_length=255, null=True, blank=True)
750
751 # Méta
752 date_creation = models.DateField(auto_now_add=True)
753 user_creation = models.IntegerField(null=True, blank=True) # TODO : user
754
755 def montant_mois(self):
756 return round(self.montant / 12, 2)
757
758 def taux_devise(self):
759 if self.devise.code == 'EUR':
760 return 1
761 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
762 if len(liste_taux) == 0:
763 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
764 else:
765 return liste_taux[0].taux
766
767 def montant_euro(self):
768 return round(float(self.montant) * float(self.taux_devise()), 2)
769
770 def montant_euro_mois(self):
771 return round(self.montant_euro() / 12, 2)