6e0f7777199377aeb164536e9edd51c554215ea8
[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 (float)(self.salaire_min + self.indemn_expat_min + + self.indemn_fct_min + self.charges_patronales_min + self.autre_min)
214
215 def get_taux_minimum(self):
216 taux_changes = rh.TauxChange.objects.filter(devise=self.devise_min).order_by('annee')
217 for t in taux_changes:
218 if t.implantation == self.implantation:
219 return t.taux
220 if len(taux_changes) > 0:
221 return taux_changes[0].taux
222 else:
223 raise DeviseException('Taux indisponible pour la devise %s (%s)' % (self.devise_min, self.implantation))
224
225 def get_couts_minimum_euros(self):
226 return self.get_couts_minimum() * self.get_taux_minimum()
227
228 def get_couts_maximum(self):
229 return (float)(self.salaire_max + self.indemn_expat_max + + self.indemn_fct_max + self.charges_patronales_max + self.autre_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 self.get_couts_maximum() * self.get_taux_maximum()
243
244
245 def show_taux_minimum(self):
246 try:
247 return self.get_taux_minimum()
248 except Exception, e:
249 return e
250
251 def show_couts_minimum_euros(self):
252 try:
253 return self.get_couts_minimum_euros()
254 except Exception, e:
255 return e
256
257 def show_taux_maximum(self):
258 try:
259 return self.get_taux_maximum()
260 except Exception, e:
261 return e
262
263 def show_couts_maximum_euros(self):
264 try:
265 return self.get_couts_maximum_euros()
266 except Exception, e:
267 return e
268
269
270 ######################
271 # Comparaison de poste
272 ######################
273
274 def est_comparable(self):
275 """
276 Si on a au moins une valeur de saisie dans les comparaisons, alors le poste
277 est comparable.
278 """
279 if self.comp_universite_min is None and \
280 self.comp_fonctionpub_min is None and \
281 self.comp_locale_min is None and \
282 self.comp_ong_min is None and \
283 self.comp_autre_min is None and \
284 self.comp_universite_max is None and \
285 self.comp_fonctionpub_max is None and \
286 self.comp_locale_max is None and \
287 self.comp_ong_max is None and \
288 self.comp_autre_max is None:
289 return False
290 else:
291 return True
292
293
294 def get_taux_comparaison(self):
295 try:
296 return rh.TauxChange.objects.filter(implantation=self.implantation, devise=self.devise_comparaison)[0].taux
297 except:
298 return 1
299
300 def get_comp_universite_min_euros(self):
301 return (float)(self.comp_universite_min) * self.get_taux_comparaison()
302
303 def get_comp_fonctionpub_min_euros(self):
304 return (float)(self.comp_fonctionpub_min) * self.get_taux_comparaison()
305
306 def get_comp_locale_min_euros(self):
307 return (float)(self.comp_locale_min) * self.get_taux_comparaison()
308
309 def get_comp_ong_min_euros(self):
310 return (float)(self.comp_ong_min) * self.get_taux_comparaison()
311
312 def get_comp_autre_min_euros(self):
313 return (float)(self.comp_autre_min) * self.get_taux_comparaison()
314
315 def get_comp_universite_max_euros(self):
316 return (float)(self.comp_universite_max) * self.get_taux_comparaison()
317
318 def get_comp_fonctionpub_max_euros(self):
319 return (float)(self.comp_fonctionpub_max) * self.get_taux_comparaison()
320
321 def get_comp_locale_max_euros(self):
322 return (float)(self.comp_locale_max) * self.get_taux_comparaison()
323
324 def get_comp_ong_max_euros(self):
325 return (float)(self.comp_ong_max) * self.get_taux_comparaison()
326
327 def get_comp_autre_max_euros(self):
328 return (float)(self.comp_autre_max) * self.get_taux_comparaison()
329
330
331 def __unicode__(self):
332 """
333 Cette fonction est consommatrice SQL car elle cherche les dossiers
334 qui ont été liés à celui-ci.
335 """
336 complement_nom_poste = self.get_complement_nom()
337 if complement_nom_poste is None:
338 complement_nom_poste = ""
339 data = (
340 self.implantation,
341 self.type_poste.nom,
342 self.nom,
343 )
344 return u'%s - %s (%s)' % data
345
346
347 # Tester l'enregistrement car les models.py sont importés au complet
348 if not reversion.is_registered(Poste):
349 reversion.register(Poste)
350
351
352 POSTE_FINANCEMENT_CHOICES = (
353 ('A', 'A - Frais de personnel'),
354 ('B', 'B - Projet(s)-Titre(s)'),
355 ('C', 'C - Autre')
356 )
357
358 class PosteFinancement(models.Model):
359 poste = models.ForeignKey('Poste', related_name='financements')
360 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
361 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
362 help_text="ex.: 33.33 % (décimale avec point)")
363 commentaire = models.TextField(
364 help_text="Spécifiez la source de financement.")
365
366 class Meta:
367 ordering = ['type']
368
369 def __unicode__(self):
370 return u"%s %s %s" % (self.get_type_display(), self.pourcentage, self.commentaire)
371
372
373 class PostePiece(models.Model):
374 """Documents relatifs au Poste
375 Ex.: Description de poste
376 """
377 poste = models.ForeignKey("Poste")
378 nom = models.CharField(verbose_name="Nom", max_length=255)
379 fichier = models.FileField(verbose_name="Fichier",
380 upload_to=poste_piece_dispatch,
381 storage=storage_prive)
382
383 class PosteComparaison(models.Model):
384 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
385 statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name='Statut', null=True, blank=True, )
386 classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name='Classement', null=True, blank=True, )
387 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True)
388 nom = models.CharField(verbose_name="Poste", max_length=255, null=True, blank=True)
389 montant = models.IntegerField(null=True)
390 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
391
392 def taux_devise(self):
393 if self.devise.code == 'EUR':
394 return 1
395 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
396 if len(liste_taux) == 0:
397 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
398 else:
399 return liste_taux[0].taux
400
401 def montant_euros(self):
402 return round(float(self.montant) * float(self.taux_devise()), 2)
403
404
405 ### EMPLOYÉ/PERSONNE
406
407 # TODO : migration pour m -> M, f -> F
408
409 GENRE_CHOICES = (
410 ('m', 'Homme'),
411 ('f', 'Femme'),
412 )
413
414 class Employe(models.Model):
415
416 # Modèle existant
417 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
418 verbose_name='Employé')
419 nom = models.CharField(max_length=255)
420 prenom = models.CharField(max_length=255, verbose_name='Prénom')
421 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
422
423 def __unicode__(self):
424 return u'%s %s' % (self.prenom, self.nom.upper())
425
426
427 ### DOSSIER
428
429 STATUT_RESIDENCE_CHOICES = (
430 ('local', 'Local'),
431 ('expat', 'Expatrié'),
432 )
433
434 COMPTE_COMPTA_CHOICES = (
435 ('coda', 'CODA'),
436 ('scs', 'SCS'),
437 ('aucun', 'Aucun'),
438 )
439
440 class Dossier(DossierWorkflow, models.Model):
441
442 # Modèle existant
443 employe = models.ForeignKey('Employe', related_name='+', editable=False)
444 poste = models.ForeignKey('Poste', related_name='dossiers', editable=False)
445 statut = models.ForeignKey(rh.Statut, related_name='+')
446 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
447 null=True, blank=True,
448 verbose_name="Organisme",
449 help_text="Si détaché (DET) ou mis à disposition (MAD), \
450 préciser l'organisme.",
451 related_name='+')
452 organisme_bstg_autre = models.CharField(max_length=255,
453 verbose_name="Autre organisme",
454 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
455 null=True,
456 blank=True,)
457
458 # Données antérieures de l'employé
459 # la devise??
460 statut_anterieur = models.ForeignKey(
461 rh.Statut, related_name='+', null=True, blank=True,
462 verbose_name='Statut antérieur')
463 classement_anterieur = models.ForeignKey(
464 rh.Classement, related_name='+', null=True, blank=True,
465 verbose_name='Classement précédent')
466 salaire_anterieur = models.DecimalField(
467 max_digits=12, decimal_places=2, null=True, default=None,
468 blank=True, verbose_name='Salaire précédent')
469 devise_anterieur = models.ForeignKey(rh.Devise, related_name='+', null=True, default=None,)
470
471 # Données du titulaire précédent
472 employe_anterieur = models.ForeignKey(
473 rh.Employe, related_name='+', null=True, blank=True,
474 verbose_name='Employé précédent')
475 statut_titulaire_anterieur = models.ForeignKey(
476 rh.Statut, related_name='+', null=True, blank=True,
477 verbose_name='Statut titulaire précédent')
478 classement_titulaire_anterieur = models.ForeignKey(
479 rh.Classement, related_name='+', null=True, blank=True,
480 verbose_name='Classement titulaire précédent')
481 salaire_titulaire_anterieur = models.DecimalField(
482 max_digits=12, decimal_places=2, default=None, null=True,
483 blank=True, verbose_name='Salaire titulaire précédent')
484 devise_titulaire_anterieur = models.ForeignKey(rh.Devise, related_name='+', null=True, default=None,)
485
486 # Recrutement
487 remplacement = models.BooleanField()
488 statut_residence = models.CharField(max_length=10, default='local',
489 verbose_name="Statut",
490 choices=STATUT_RESIDENCE_CHOICES)
491
492 # Rémunération
493 classement = models.ForeignKey(rh.Classement, related_name='+',
494 null=True, blank=True,
495 verbose_name='Classement proposé')
496 salaire = models.DecimalField(max_digits=12, decimal_places=2,
497 verbose_name='Salaire de base',
498 null=True, default=None)
499 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
500 regime_travail = models.DecimalField(max_digits=12,
501 decimal_places=2,
502 default=REGIME_TRAVAIL_DEFAULT,
503 verbose_name="Régime de travail",
504 help_text="% du temps complet")
505 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
506 decimal_places=2,
507 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
508 verbose_name="Nb. heures par semaine")
509
510 # Contrat
511 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
512 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
513 contrat_date_fin = models.DateField(null=True, blank=True,
514 help_text="format: aaaa-mm-jj")
515
516 # Justifications
517 justif_nouveau_statut_label = u'Justifier le statut que ce type de poste nécessite (national, expatrié, màd ou détachement)'
518 justif_nouveau_statut = models.TextField(verbose_name=justif_nouveau_statut_label, null=True, blank=True)
519 justif_nouveau_tmp_remplacement_label = u"Si l'employé effectue un remplacement temporaire, préciser"
520 justif_nouveau_tmp_remplacement = models.TextField(verbose_name=justif_nouveau_tmp_remplacement_label, null=True, blank=True)
521 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 "
522 justif_nouveau_salaire = models.TextField(verbose_name=justif_nouveau_salaire_label, null=True, blank=True)
523 justif_nouveau_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
524 justif_nouveau_commentaire = models.TextField(verbose_name=justif_nouveau_commentaire_label, null=True, blank=True)
525 justif_rempl_type_contrat_label = u"Changement de type de contrat, ex : d'un CDD en CDI"
526 justif_rempl_type_contrat = models.TextField(verbose_name=justif_rempl_type_contrat_label, null=True, blank=True)
527 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"
528 justif_rempl_statut_employe = models.TextField(verbose_name=justif_rempl_statut_employe_label, null=True, blank=True)
529 justif_rempl_evaluation_label = u"L'évaluation de l'employé est-elle favorable? Préciser"
530 justif_rempl_evaluation = models.TextField(verbose_name=justif_rempl_evaluation_label, null=True, blank=True)
531 justif_rempl_salaire_label = u"Si le salaire de l'employé est modifié et/ou ne correspond pas à son classement, justifier"
532 justif_rempl_salaire = models.TextField(verbose_name=justif_rempl_salaire_label, null=True, blank=True)
533 justif_rempl_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
534 justif_rempl_commentaire = models.TextField(verbose_name=justif_rempl_commentaire_label, null=True, blank=True)
535
536 # Comptes
537 compte_compta = models.CharField(max_length=10, default='aucun',
538 verbose_name=u'Compte comptabilité',
539 choices=COMPTE_COMPTA_CHOICES)
540 compte_courriel = models.BooleanField()
541
542 # Méta
543 date_creation = models.DateTimeField(auto_now_add=True)
544
545 # Managers
546 objects = DossierManager()
547
548 def __unicode__(self):
549 return u'[%s] %s - %s' % (self.poste.implantation, self.poste.nom, self.employe)
550
551 def taux_devise(self):
552 if self.devise.code == 'EUR':
553 return 1
554 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.poste.implantation)
555 if len(liste_taux) == 0:
556 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.poste.implantation))
557 else:
558 return liste_taux[0].taux
559
560 def get_salaire_euros(self):
561 tx = self.taux_devise()
562 return (float)(tx) * (float)(self.salaire)
563
564 def get_remunerations_brutes(self):
565 """
566 1 Salaire de base
567 3 Indemnité de base
568 4 Indemnité d'expatriation
569 5 Indemnité pour frais
570 6 Indemnité de logement
571 7 Indemnité de fonction
572 8 Indemnité de responsabilité
573 9 Indemnité de transport
574 10 Indemnité compensatrice
575 11 Indemnité de subsistance
576 12 Indemnité différentielle
577 13 Prime d'installation
578 14 Billet d'avion
579 15 Déménagement
580 16 Indemnité de départ
581 18 Prime de 13ième mois
582 19 Prime d'intérim
583 """
584 ids = [1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19]
585 return [r for r in self.remuneration_set.all() if r.type_id in ids]
586
587 def get_charges_salariales(self):
588 """
589 20 Charges salariales ?
590 """
591 ids = [20, ]
592 return [r for r in self.remuneration_set.all() if r.type_id in ids]
593
594 def get_total_charges_salariales(self):
595 total = 0.0
596 for r in self.get_charges_salariales():
597 total += r.montant_euro()
598 return total
599
600 def get_charges_patronales(self):
601 """
602 17 Charges patronales
603 """
604 ids = [17, ]
605 return [r for r in self.remuneration_set.all() if r.type_id in ids]
606
607 def get_total_charges_patronales(self):
608 total = 0.0
609 for r in self.get_charges_patronales():
610 total += r.montant_euro()
611 return total
612
613 def get_salaire_brut(self):
614 """
615 somme des rémuérations brutes
616 """
617 total = 0.0
618 for r in self.get_remunerations_brutes():
619 total += r.montant_euro()
620 return total
621
622 def get_salaire_net(self):
623 """
624 salaire brut - charges salariales
625 """
626 total_charges = 0.0
627 for r in self.get_charges_salariales():
628 total_charges += r.montant_euro()
629 return self.get_salaire_brut() - total_charges
630
631 def get_couts_auf(self):
632 """
633 salaire net + charges patronales
634 """
635 total_charges = 0.0
636 for r in self.get_charges_patronales():
637 total_charges += r.montant_euro()
638 return self.get_salaire_net() + total_charges
639
640 def get_remunerations_tierces(self):
641 """
642 2 Salaire MAD
643 """
644 return [r for r in self.remuneration_set.all() if r.type_id in (2, )]
645
646 def get_total_remunerations_tierces(self):
647 total = 0.0
648 for r in self.get_remunerations_tierces():
649 total += r.montant_euro()
650 return total
651
652
653 # Tester l'enregistrement car les models.py sont importés au complet
654 if not reversion.is_registered(Dossier):
655 reversion.register(Dossier)
656
657 class DossierPiece(models.Model):
658 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
659 Ex.: Lettre de motivation.
660 """
661 dossier = models.ForeignKey("Dossier")
662 nom = models.CharField(verbose_name="Nom", max_length=255)
663 fichier = models.FileField(verbose_name="Fichier",
664 upload_to=dossier_piece_dispatch,
665 storage=storage_prive)
666
667
668 class DossierComparaison(models.Model):
669 """
670 Photo d'une comparaison salariale au moment de l'embauche.
671 """
672 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
673 statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name='Statut', null=True, blank=True, )
674 classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name='Classement', null=True, blank=True, )
675 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True)
676 poste = models.CharField(max_length=255, null=True, blank=True)
677 personne = models.CharField(max_length=255, null=True, blank=True)
678 montant = models.IntegerField(null=True)
679 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
680
681 def taux_devise(self):
682 if self.devise.code == 'EUR':
683 return 1
684 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
685 if len(liste_taux) == 0:
686 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
687 else:
688 return liste_taux[0].taux
689
690 def montant_euros(self):
691 return round(float(self.montant) * float(self.taux_devise()), 2)
692
693
694
695 ### RÉMUNÉRATION
696
697 class Remuneration(models.Model):
698 # Identification
699 dossier = models.ForeignKey('Dossier', db_column='dossier')
700 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
701 related_name='+')
702 montant = models.DecimalField(max_digits=12, decimal_places=2,
703 null=True) # Annuel
704 devise = models.ForeignKey(rh.Devise, to_field='code',
705 db_column='devise', related_name='+')
706 precision = models.CharField(max_length=255, null=True, blank=True)
707
708 # Méta
709 date_creation = models.DateField(auto_now_add=True)
710 user_creation = models.IntegerField(null=True, blank=True) # TODO : user
711
712 def montant_mois(self):
713 return round(self.montant / 12, 2)
714
715 def taux_devise(self):
716 if self.devise.code == 'EUR':
717 return 1
718 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
719 if len(liste_taux) == 0:
720 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
721 else:
722 return liste_taux[0].taux
723
724 def montant_euro(self):
725 return round(float(self.montant) * float(self.taux_devise()), 2)
726
727 def montant_euro_mois(self):
728 return round(self.montant_euro() / 12, 2)