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