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