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