[#2245] Liste des DAE finalisées
[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 if self.devise_min.code == 'EUR':
211 return 1
212 liste_taux = self.devise_min.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
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').filter(implantation=self.implantation)
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(implantation=self.implantation, 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 %s %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="Nom", max_length=255)
391 fichier = models.FileField(verbose_name="Fichier", upload_to='dae/poste', storage=UPLOAD_STORAGE)
392
393 class PosteComparaison(models.Model):
394 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
395 statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name='Statut', null=True, blank=True, )
396 classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name='Classement', null=True, blank=True, )
397 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True)
398 nom = models.CharField(verbose_name="Poste", max_length=255, null=True, blank=True)
399 montant = models.IntegerField(null=True)
400 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
401
402 def taux_devise(self):
403 if self.devise.code == 'EUR':
404 return 1
405 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
406 if len(liste_taux) == 0:
407 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
408 else:
409 return liste_taux[0].taux
410
411 def montant_euros(self):
412 return round(float(self.montant) * float(self.taux_devise()), 2)
413
414
415 ### EMPLOYÉ/PERSONNE
416
417 # TODO : migration pour m -> M, f -> F
418
419 GENRE_CHOICES = (
420 ('m', 'Homme'),
421 ('f', 'Femme'),
422 )
423
424 class Employe(models.Model):
425
426 # Modèle existant
427 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
428 verbose_name='Employé')
429 nom = models.CharField(max_length=255)
430 prenom = models.CharField(max_length=255, verbose_name='Prénom')
431 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
432
433 def __unicode__(self):
434 return u'%s %s' % (self.prenom, self.nom.upper())
435
436
437 ### DOSSIER
438
439 STATUT_RESIDENCE_CHOICES = (
440 ('local', 'Local'),
441 ('expat', 'Expatrié'),
442 )
443
444 COMPTE_COMPTA_CHOICES = (
445 ('coda', 'CODA'),
446 ('scs', 'SCS'),
447 ('aucun', 'Aucun'),
448 )
449
450 class Dossier(DossierWorkflow, models.Model):
451
452 # Modèle existant
453 employe = models.ForeignKey('Employe', related_name='+', editable=False)
454 poste = models.ForeignKey('Poste', related_name='dossiers', editable=False)
455 statut = models.ForeignKey(rh.Statut, related_name='+')
456 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
457 null=True, blank=True,
458 verbose_name="Organisme",
459 help_text="Si détaché (DET) ou mis à disposition (MAD), \
460 préciser l'organisme.",
461 related_name='+')
462 organisme_bstg_autre = models.CharField(max_length=255,
463 verbose_name="Autre organisme",
464 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
465 null=True,
466 blank=True,)
467
468 # Données antérieures de l'employé
469 statut_anterieur = models.ForeignKey(
470 rh.Statut, related_name='+', null=True, blank=True,
471 verbose_name='Statut antérieur')
472 classement_anterieur = models.ForeignKey(
473 rh.Classement, related_name='+', null=True, blank=True,
474 verbose_name='Classement précédent')
475 salaire_anterieur = models.DecimalField(
476 max_digits=12, decimal_places=2, null=True, default=None,
477 blank=True, verbose_name='Salaire précédent')
478 devise_anterieur = models.ForeignKey(rh.Devise, related_name='+', null=True, blank=True)
479 type_contrat_anterieur = models.ForeignKey(rh.TypeContrat, related_name='+', null=True, blank=True, verbose_name=u'Type contrat antérieur', )
480
481 # Données du titulaire précédent
482 employe_anterieur = models.ForeignKey(
483 rh.Employe, related_name='+', null=True, blank=True,
484 verbose_name='Employé précédent')
485 statut_titulaire_anterieur = models.ForeignKey(
486 rh.Statut, related_name='+', null=True, blank=True,
487 verbose_name='Statut titulaire précédent')
488 classement_titulaire_anterieur = models.ForeignKey(
489 rh.Classement, related_name='+', null=True, blank=True,
490 verbose_name='Classement titulaire précédent')
491 salaire_titulaire_anterieur = models.DecimalField(
492 max_digits=12, decimal_places=2, default=None, null=True,
493 blank=True, verbose_name='Salaire titulaire précédent')
494 devise_titulaire_anterieur = models.ForeignKey(rh.Devise, related_name='+', null=True, blank=True)
495
496 # Recrutement
497 remplacement = models.BooleanField()
498 statut_residence = models.CharField(max_length=10, default='local',
499 verbose_name="Statut",
500 choices=STATUT_RESIDENCE_CHOICES)
501
502 # Rémunération
503 classement = models.ForeignKey(rh.Classement, related_name='+',
504 null=True, blank=True,
505 verbose_name='Classement proposé')
506 salaire = models.DecimalField(max_digits=12, decimal_places=2,
507 verbose_name='Salaire de base',
508 null=True, default=None)
509 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
510 regime_travail = models.DecimalField(max_digits=12,
511 decimal_places=2,
512 default=REGIME_TRAVAIL_DEFAULT,
513 verbose_name="Régime de travail",
514 help_text="% du temps complet")
515 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
516 decimal_places=2,
517 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
518 verbose_name="Nb. heures par semaine")
519
520 # Contrat
521 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
522 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
523 contrat_date_fin = models.DateField(null=True, blank=True,
524 help_text="format: aaaa-mm-jj")
525
526 # Justifications
527 justif_nouveau_statut_label = u'Justifier le statut que ce type de poste nécessite (national, expatrié, màd ou détachement)'
528 justif_nouveau_statut = models.TextField(verbose_name=justif_nouveau_statut_label, null=True, blank=True)
529 justif_nouveau_tmp_remplacement_label = u"Si l'employé effectue un remplacement temporaire, préciser"
530 justif_nouveau_tmp_remplacement = models.TextField(verbose_name=justif_nouveau_tmp_remplacement_label, null=True, blank=True)
531 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 "
532 justif_nouveau_salaire = models.TextField(verbose_name=justif_nouveau_salaire_label, null=True, blank=True)
533 justif_nouveau_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
534 justif_nouveau_commentaire = models.TextField(verbose_name=justif_nouveau_commentaire_label, null=True, blank=True)
535 justif_rempl_type_contrat_label = u"Changement de type de contrat, ex : d'un CDD en CDI"
536 justif_rempl_type_contrat = models.TextField(verbose_name=justif_rempl_type_contrat_label, null=True, blank=True)
537 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"
538 justif_rempl_statut_employe = models.TextField(verbose_name=justif_rempl_statut_employe_label, null=True, blank=True)
539 justif_rempl_evaluation_label = u"L'évaluation de l'employé est-elle favorable? Préciser"
540 justif_rempl_evaluation = models.TextField(verbose_name=justif_rempl_evaluation_label, null=True, blank=True)
541 justif_rempl_salaire_label = u"Si le salaire de l'employé est modifié et/ou ne correspond pas à son classement, justifier"
542 justif_rempl_salaire = models.TextField(verbose_name=justif_rempl_salaire_label, null=True, blank=True)
543 justif_rempl_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
544 justif_rempl_commentaire = models.TextField(verbose_name=justif_rempl_commentaire_label, null=True, blank=True)
545
546 # Comptes
547 compte_compta = models.CharField(max_length=10, default='aucun',
548 verbose_name=u'Compte comptabilité',
549 choices=COMPTE_COMPTA_CHOICES)
550 compte_courriel = models.BooleanField()
551
552 # Méta
553 date_creation = models.DateTimeField(auto_now_add=True)
554
555 # Managers
556 objects = DossierManager()
557
558 def __unicode__(self):
559 return u'[%s] %s - %s' % (self.poste.implantation, self.poste.nom, self.employe)
560
561 def taux_devise(self):
562 if self.devise.code == 'EUR':
563 return 1
564 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.poste.implantation)
565 if len(liste_taux) == 0:
566 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.poste.implantation))
567 else:
568 return liste_taux[0].taux
569
570 def get_salaire_anterieur_euros(self):
571 if self.devise_anterieur.code == 'EUR':
572 tx = 1
573 else:
574 liste_taux = self.devise_anterieur.tauxchange_set.order_by('-annee').filter(implantation=self.poste.implantation)
575 if len(liste_taux) == 0:
576 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_anterieur, self.poste.implantation))
577 tx = liste_taux[0].taux
578 return (float)(tx) * (float)(self.salaire_anterieur)
579
580 def get_salaire_titulaire_anterieur_euros(self):
581 if self.devise_titulaire_anterieur.code == 'EUR':
582 tx = 1
583 else:
584 liste_taux = self.devise_titulaire_anterieur.tauxchange_set.order_by('-annee').filter(implantation=self.poste.implantation)
585 if len(liste_taux) == 0:
586 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_titulaire_anterieur, self.poste.implantation))
587 tx = liste_taux[0].taux
588 return (float)(tx) * (float)(self.salaire_titulaire_anterieur)
589
590 def get_salaire_euros(self):
591 tx = self.taux_devise()
592 return (float)(tx) * (float)(self.salaire)
593
594 def get_remunerations_brutes(self):
595 """
596 1 Salaire de base
597 3 Indemnité de base
598 4 Indemnité d'expatriation
599 5 Indemnité pour frais
600 6 Indemnité de logement
601 7 Indemnité de fonction
602 8 Indemnité de responsabilité
603 9 Indemnité de transport
604 10 Indemnité compensatrice
605 11 Indemnité de subsistance
606 12 Indemnité différentielle
607 13 Prime d'installation
608 14 Billet d'avion
609 15 Déménagement
610 16 Indemnité de départ
611 18 Prime de 13ième mois
612 19 Prime d'intérim
613 """
614 ids = [1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19]
615 return [r for r in self.remuneration_set.all() if r.type_id in ids]
616
617 def get_charges_salariales(self):
618 """
619 20 Charges salariales ?
620 """
621 ids = [20, ]
622 return [r for r in self.remuneration_set.all() if r.type_id in ids]
623
624 def get_total_charges_salariales(self):
625 total = 0.0
626 for r in self.get_charges_salariales():
627 total += r.montant_euro()
628 return total
629
630 def get_charges_patronales(self):
631 """
632 17 Charges patronales
633 """
634 ids = [17, ]
635 return [r for r in self.remuneration_set.all() if r.type_id in ids]
636
637 def get_total_charges_patronales(self):
638 total = 0.0
639 for r in self.get_charges_patronales():
640 total += r.montant_euro()
641 return total
642
643 def get_salaire_brut(self):
644 """
645 somme des rémuérations brutes
646 """
647 total = 0.0
648 for r in self.get_remunerations_brutes():
649 total += r.montant_euro()
650 return total
651
652 def get_salaire_net(self):
653 """
654 salaire brut - charges salariales
655 """
656 total_charges = 0.0
657 for r in self.get_charges_salariales():
658 total_charges += r.montant_euro()
659 return self.get_salaire_brut() - total_charges
660
661 def get_couts_auf(self):
662 """
663 salaire net + charges patronales
664 """
665 total_charges = 0.0
666 for r in self.get_charges_patronales():
667 total_charges += r.montant_euro()
668 return self.get_salaire_net() + total_charges
669
670 def get_remunerations_tierces(self):
671 """
672 2 Salaire MAD
673 """
674 return [r for r in self.remuneration_set.all() if r.type_id in (2, )]
675
676 def get_total_remunerations_tierces(self):
677 total = 0.0
678 for r in self.get_remunerations_tierces():
679 total += r.montant_euro()
680 return total
681
682 def valide(self):
683 return self.etat in (DOSSIER_ETAT_REGION_FINALISATION,
684 DOSSIER_ETAT_DRH_FINALISATION,
685 DOSSIER_ETAT_FINALISE)
686
687
688 # Tester l'enregistrement car les models.py sont importés au complet
689 if not reversion.is_registered(Dossier):
690 reversion.register(Dossier)
691
692 class DossierPiece(models.Model):
693 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
694 Ex.: Lettre de motivation.
695 """
696 dossier = models.ForeignKey("Dossier")
697 nom = models.CharField(verbose_name="Nom", max_length=255)
698 fichier = models.FileField(verbose_name="Fichier", upload_to='dae/dossier', storage=UPLOAD_STORAGE)
699
700
701 class DossierComparaison(models.Model):
702 """
703 Photo d'une comparaison salariale au moment de l'embauche.
704 """
705 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
706 statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name='Statut', null=True, blank=True, )
707 classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name='Classement', null=True, blank=True, )
708 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True)
709 poste = models.CharField(max_length=255, null=True, blank=True)
710 personne = models.CharField(max_length=255, null=True, blank=True)
711 montant = models.IntegerField(null=True)
712 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
713
714 def taux_devise(self):
715 if self.devise.code == 'EUR':
716 return 1
717 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
718 if len(liste_taux) == 0:
719 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
720 else:
721 return liste_taux[0].taux
722
723 def montant_euros(self):
724 return round(float(self.montant) * float(self.taux_devise()), 2)
725
726
727
728 ### RÉMUNÉRATION
729
730 class Remuneration(models.Model):
731 # Identification
732 dossier = models.ForeignKey('Dossier', db_column='dossier')
733 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
734 related_name='+')
735 montant = models.DecimalField(max_digits=12, decimal_places=2,
736 null=True) # Annuel
737 devise = models.ForeignKey(rh.Devise, to_field='code',
738 db_column='devise', related_name='+')
739 precision = models.CharField(max_length=255, null=True, blank=True)
740
741 # Méta
742 date_creation = models.DateField(auto_now_add=True)
743 user_creation = models.IntegerField(null=True, blank=True) # TODO : user
744
745 def montant_mois(self):
746 return round(self.montant / 12, 2)
747
748 def taux_devise(self):
749 if self.devise.code == 'EUR':
750 return 1
751 liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
752 if len(liste_taux) == 0:
753 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
754 else:
755 return liste_taux[0].taux
756
757 def montant_euro(self):
758 return round(float(self.montant) * float(self.taux_devise()), 2)
759
760 def montant_euro_mois(self):
761 return round(self.montant_euro() / 12, 2)
762
763
764 ### CONTRATS
765
766 class Contrat(models.Model):
767 dossier = models.ForeignKey(Dossier, related_name='contrats')
768 type = models.ForeignKey(rh.TypeContrat, related_name='+')
769 fichier = models.FileField(upload_to='dae/contrats', storage=UPLOAD_STORAGE)
770