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