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