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