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