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