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