remove rh_v1
[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
c511cd1f
EMS
9from workflow import DOSSIER_ETAT_DRH_FINALISATION, DOSSIER_ETAT_REGION_FINALISATION, \
10 DOSSIER_ETAT_FINALISE
296fff6b
EMS
11from managers import DossierManager, PosteManager, PosteComparaisonManager, \
12 DossierComparaisonManager
bd28238f 13import datamaster_modeles.models as ref
09aa8374 14from rh import models as rh
bd28238f 15
bd28238f 16
2d4d2fcf 17# Constantes
b666864e 18HELP_TEXT_DATE = "format: jj-mm-aaaa"
2d4d2fcf 19REGIME_TRAVAIL_DEFAULT=100.00
20REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT=35.00
bd28238f 21
d766bf2c 22# Upload de fichiers
34dad720 23UPLOAD_STORAGE = FileSystemStorage(settings.PRIVE_MEDIA_ROOT)
d766bf2c 24
36341125 25
2d4d2fcf 26### POSTE
27
28POSTE_APPEL_CHOICES = (
29 ('interne', 'Interne'),
30 ('externe', 'Externe'),
31)
c3be904d
OL
32POSTE_ACTION = (
33 ('N', u"Nouveau poste"),
34 ('M', u"Poste existant"),
35 ('E', u"Évolution de poste"),
36)
37
36341125 38
ae5c920b
OL
39class DeviseException(Exception):
40 silent_variable_failure = True
41
1c7d67ce 42
8fa94e8b 43class Poste(PosteWorkflow, models.Model):
c3be904d
OL
44
45 type_intervention = models.CharField(max_length=1, choices=POSTE_ACTION, default='N')
46
bd28238f 47 # Modèle existant
5d680e84 48 id_rh = models.ForeignKey(rh.Poste, null=True, related_name='+',
2d4d2fcf 49 editable=False,
c1195471
OL
50 verbose_name=u"Mise à jour du poste")
51 nom = models.CharField(verbose_name=u"Titre du poste", max_length=255)
5d680e84
NC
52 implantation = models.ForeignKey(ref.Implantation)
53 type_poste = models.ForeignKey(rh.TypePoste, null=True, related_name='+')
98d51b59 54 service = models.ForeignKey(rh.Service, related_name='+',
2d4d2fcf 55 verbose_name=u"Direction/Service/Pôle support")
98d51b59 56 responsable = models.ForeignKey(rh.Poste, related_name='+',
c1195471 57 verbose_name=u"Poste du responsable")
9a85768a 58
2d4d2fcf 59 # Contrat
5d680e84 60 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
1bc84af4 61 default=REGIME_TRAVAIL_DEFAULT,
e158c6de 62 verbose_name=u"Temps de travail",
2d4d2fcf 63 help_text="% du temps complet")
5d680e84 64 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 65 decimal_places=2,
66 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
c1195471 67 verbose_name=u"Nb. heures par semaine")
bd28238f
NC
68
69 # Recrutement
c1195471 70 local = models.BooleanField(verbose_name=u"Local", default=True, blank=True)
e158c6de 71 expatrie = models.BooleanField(verbose_name=u"Expatrié", default=False,
2d4d2fcf 72 blank=True)
c1195471 73 mise_a_disposition = models.BooleanField(verbose_name=u"Mise à disposition")
98d51b59 74 appel = models.CharField(max_length=10, default='interne',
c1195471 75 verbose_name=u"Appel à candidature",
2d4d2fcf 76 choices=POSTE_APPEL_CHOICES)
bd28238f
NC
77
78 # Rémunération
2d4d2fcf 79 classement_min = models.ForeignKey(rh.Classement, related_name='+',
80 blank=True, null=True)
81 classement_max = models.ForeignKey(rh.Classement, related_name='+',
82 blank=True, null=True)
428e3c0b 83 valeur_point_min = models.ForeignKey(rh.ValeurPoint, related_name='+',
2d4d2fcf 84 blank=True, null=True)
428e3c0b 85 valeur_point_max = models.ForeignKey(rh.ValeurPoint, related_name='+',
2d4d2fcf 86 blank=True, null=True)
3d627bfd 87 devise_min = models.ForeignKey(rh.Devise, default=5, related_name='+')
88 devise_max = models.ForeignKey(rh.Devise, default=5, related_name='+')
5d680e84
NC
89 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
90 default=0)
5f61bccb
OL
91 salaire_max = models.DecimalField(max_digits=12, decimal_places=2, default=0)
92 indemn_expat_min = models.DecimalField(max_digits=12, decimal_places=2, default=0)
93 indemn_expat_max = models.DecimalField(max_digits=12, decimal_places=2, default=0)
94 indemn_fct_min = models.DecimalField(max_digits=12, decimal_places=2, default=0)
95 indemn_fct_max = models.DecimalField(max_digits=12, decimal_places=2, default=0)
96 charges_patronales_min = models.DecimalField(max_digits=12, decimal_places=2, default=0)
97 charges_patronales_max = models.DecimalField(max_digits=12, decimal_places=2, default=0)
98 autre_min = models.DecimalField(max_digits=12, decimal_places=2, default=0)
99 autre_max = models.DecimalField(max_digits=12, decimal_places=2, default=0)
5d680e84
NC
100
101 # Comparatifs de rémunération
102 devise_comparaison = models.ForeignKey(rh.Devise, related_name='+',
a3508c67 103 default=5)
5d680e84
NC
104 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
105 null=True, blank=True)
106 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
107 null=True, blank=True)
108 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
109 null=True, blank=True)
110 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
111 null=True, blank=True)
112 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
113 null=True, blank=True)
114 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
115 null=True, blank=True)
116 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
117 null=True, blank=True)
118 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
119 null=True, blank=True)
120 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
121 null=True, blank=True)
122 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
123 null=True, blank=True)
bd28238f 124
2e092e0c
OL
125 # Justification
126 justification = models.TextField()
127
bd28238f 128 # Méta
5d680e84
NC
129 date_creation = models.DateTimeField(auto_now_add=True)
130 date_modification = models.DateTimeField(auto_now=True)
3f5f3898 131 date_debut = models.DateField(verbose_name=u"Date de début",
7332d8f9
OL
132 help_text=HELP_TEXT_DATE,
133 null=True, blank=True)
9fb2ccd9 134 date_fin = models.DateField(null=True, blank=True,
c1195471 135 verbose_name=u"Date de fin",
2d4d2fcf 136 help_text=HELP_TEXT_DATE)
bd28238f
NC
137 actif = models.BooleanField(default=True)
138
1c7d67ce
OL
139 # Managers
140 objects = PosteManager()
141
03858ba5
OL
142 def _get_key(self):
143 """
1bc84af4 144 Les vues sont montées selon une clef spéciale
2d4d2fcf 145 pour identifier la provenance du poste.
1bc84af4 146 Cette méthode fournit un moyen de reconstruire cette clef
2d4d2fcf 147 afin de générer les URLs.
03858ba5
OL
148 """
149 return "dae-%s" % self.id
150 key = property(_get_key)
151
f3333b0e
OL
152 def get_dossiers(self):
153 """
154 Liste tous les anciens dossiers liés à ce poste.
1bc84af4 155 (Le nom de la relation sur le rh.Poste est mal choisi
2d4d2fcf 156 poste1 au lieu de dossier1)
f3333b0e 157 Note1 : seulement le dosssier principal fait l'objet de la recherche.
1bc84af4
EMS
158 Note2 : les dossiers sont retournés du plus récent au plus vieux.
159 (Ce test est fait en fonction du id,
2d4d2fcf 160 car les dates de création sont absentes de rh v1).
f3333b0e
OL
161 """
162 if self.id_rh is None:
163 return []
09aa8374 164 return self.id_rh.dossiers.all()
428e3c0b 165
f3333b0e
OL
166 def get_complement_nom(self):
167 """
1bc84af4 168 Inspecte les modèles rh v1 pour trouver dans le dernier dossier
2d4d2fcf 169 un complément de titre de poste.
f3333b0e
OL
170 """
171 dossiers = self.get_dossiers()
172 if len(dossiers) > 0:
09aa8374 173 nom = dossiers[0].poste.nom
f3333b0e
OL
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):
1bc84af4 189 """Récupère la devise par défaut en fonction de l'implantation
2d4d2fcf 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):
83b94a87
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
2455f48d 212 liste_taux = self.devise_min.tauxchange_set.order_by('-annee')
4e439a89
OL
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):
fa5b95ed 219 return float(self.get_couts_minimum()) * self.get_taux_minimum()
c0413a6f 220
83b94a87 221 def get_salaire_minimum_euros(self):
fa5b95ed 222 return float(self.get_salaire_minimum()) * self.get_taux_minimum()
83b94a87 223
c0413a6f 224 def get_couts_maximum(self):
83b94a87
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
2455f48d 233 liste_taux = self.devise_max.tauxchange_set.order_by('-annee')
4e439a89
OL
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):
fa5b95ed 240 return float(self.get_couts_maximum()) * self.get_taux_maximum()
c0413a6f 241
83b94a87 242 def get_salaire_maximum_euros(self):
fa5b95ed 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()
83b94a87 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()
83b94a87
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()
83b94a87 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()
83b94a87
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
1bc84af4 304
a3fee9c5 305
c0413a6f
OL
306 def get_taux_comparaison(self):
307 try:
2455f48d 308 return rh.TauxChange.objects.filter(devise=self.devise_comparaison)[0].taux
c0413a6f
OL
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 """
1bc84af4 345 Cette fonction est consommatrice SQL car elle cherche les dossiers
2d4d2fcf 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 381 def __unicode__(self):
320cd84a 382 return u"%s %.0f%% %s" % (self.get_type_display(), self.pourcentage, self.commentaire)
c0413a6f 383
c589d980 384
2d4d2fcf 385class PostePiece(models.Model):
386 """Documents relatifs au Poste
387 Ex.: Description de poste
388 """
389 poste = models.ForeignKey("Poste")
c1195471
OL
390 nom = models.CharField(verbose_name=u"Nom", max_length=255)
391 fichier = models.FileField(verbose_name=u"Fichier",
e158c6de
DB
392 upload_to='dae/poste',
393 storage=UPLOAD_STORAGE)
2d4d2fcf 394
068d1462
OL
395class PosteComparaison(models.Model):
396 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
e61bfeb0 397 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
3f5f3898
OL
398 statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name=u'Statut', null=True, blank=True, )
399 classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name=u'Classement', null=True, blank=True, )
400 nom = models.CharField(verbose_name=u"Poste", max_length=255, null=True, blank=True)
18170017
EMS
401 montant = models.IntegerField(
402 null=True, verbose_name="Rémunération totale sans les charges patronales"
403 )
068d1462 404 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
1d0f4eef 405
1bc84af4
EMS
406 objects = PosteComparaisonManager()
407
1d0f4eef 408 def taux_devise(self):
ae5c920b
OL
409 if self.devise.code == 'EUR':
410 return 1
2455f48d 411 liste_taux = self.devise.tauxchange_set.order_by('-annee')
1d0f4eef 412 if len(liste_taux) == 0:
ae5c920b 413 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
1d0f4eef
OL
414 else:
415 return liste_taux[0].taux
416
417 def montant_euros(self):
418 return round(float(self.montant) * float(self.taux_devise()), 2)
068d1462
OL
419
420
2d4d2fcf 421### EMPLOYÉ/PERSONNE
422
423# TODO : migration pour m -> M, f -> F
c589d980 424
bd28238f 425GENRE_CHOICES = (
139686f2
NC
426 ('m', 'Homme'),
427 ('f', 'Femme'),
bd28238f
NC
428)
429
bd28238f 430class Employe(models.Model):
bd28238f
NC
431
432 # Modèle existant
428e3c0b 433 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
c1195471 434 verbose_name=u'Employé')
bd28238f 435 nom = models.CharField(max_length=255)
c1195471 436 prenom = models.CharField(max_length=255, verbose_name=u'Prénom')
07b40eda 437 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
bd28238f 438
139686f2 439 def __unicode__(self):
2d4d2fcf 440 return u'%s %s' % (self.prenom, self.nom.upper())
139686f2 441
bd28238f 442
2d4d2fcf 443### DOSSIER
444
445STATUT_RESIDENCE_CHOICES = (
446 ('local', 'Local'),
447 ('expat', 'Expatrié'),
448)
449
bd28238f 450COMPTE_COMPTA_CHOICES = (
494ff2be
NC
451 ('coda', 'CODA'),
452 ('scs', 'SCS'),
453 ('aucun', 'Aucun'),
bd28238f
NC
454)
455
afc204bf 456class Dossier(DossierWorkflow, models.Model):
bd28238f
NC
457
458 # Modèle existant
139686f2 459 employe = models.ForeignKey('Employe', related_name='+', editable=False)
4e4a3545 460 poste = models.ForeignKey('Poste', related_name='dossiers', editable=False)
5d680e84 461 statut = models.ForeignKey(rh.Statut, related_name='+')
428e3c0b 462 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
1f109689 463 null=True, blank=True,
e158c6de 464 verbose_name=u"Organisme",
dfc2c344 465 help_text="Si détaché (DET) ou mis à disposition (MAD), \
466 préciser l'organisme.",
467 related_name='+')
0288adb5 468 organisme_bstg_autre = models.CharField(max_length=255,
c1195471 469 verbose_name=u"Autre organisme",
0288adb5
OL
470 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
471 null=True,
472 blank=True,)
bd28238f 473
139686f2
NC
474 # Données antérieures de l'employé
475 statut_anterieur = models.ForeignKey(
476 rh.Statut, related_name='+', null=True, blank=True,
c1195471 477 verbose_name=u'Statut antérieur')
139686f2
NC
478 classement_anterieur = models.ForeignKey(
479 rh.Classement, related_name='+', null=True, blank=True,
c1195471 480 verbose_name=u'Classement précédent')
139686f2
NC
481 salaire_anterieur = models.DecimalField(
482 max_digits=12, decimal_places=2, null=True, default=None,
481fbd33 483 blank=True, verbose_name=u'Salaire précédent')
e158c6de
DB
484 devise_anterieur = models.ForeignKey(rh.Devise, related_name='+',
485 null=True, blank=True)
486 type_contrat_anterieur = models.ForeignKey(rh.TypeContrat,
487 related_name='+', null=True, blank=True,
488 verbose_name=u'Type contrat antérieur', )
139686f2
NC
489
490 # Données du titulaire précédent
491 employe_anterieur = models.ForeignKey(
492 rh.Employe, related_name='+', null=True, blank=True,
c1195471 493 verbose_name=u'Employé précédent')
139686f2
NC
494 statut_titulaire_anterieur = models.ForeignKey(
495 rh.Statut, related_name='+', null=True, blank=True,
c1195471 496 verbose_name=u'Statut titulaire précédent')
139686f2
NC
497 classement_titulaire_anterieur = models.ForeignKey(
498 rh.Classement, related_name='+', null=True, blank=True,
c1195471 499 verbose_name=u'Classement titulaire précédent')
139686f2
NC
500 salaire_titulaire_anterieur = models.DecimalField(
501 max_digits=12, decimal_places=2, default=None, null=True,
481fbd33 502 blank=True, verbose_name=u'Salaire titulaire précédent')
f8c7c0b8 503 devise_titulaire_anterieur = models.ForeignKey(rh.Devise, related_name='+', null=True, blank=True)
494ff2be 504
bd28238f
NC
505 # Recrutement
506 remplacement = models.BooleanField()
428e3c0b 507 statut_residence = models.CharField(max_length=10, default='local',
2d4d2fcf 508 verbose_name="Statut",
509 choices=STATUT_RESIDENCE_CHOICES)
bd28238f
NC
510
511 # Rémunération
494ff2be 512 classement = models.ForeignKey(rh.Classement, related_name='+',
2d4d2fcf 513 null=True, blank=True,
c1195471 514 verbose_name=u'Classement proposé')
494ff2be 515 salaire = models.DecimalField(max_digits=12, decimal_places=2,
c1195471 516 verbose_name=u'Salaire de base',
2d4d2fcf 517 null=True, default=None)
e8e75458 518 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
428e3c0b 519 regime_travail = models.DecimalField(max_digits=12,
2d4d2fcf 520 decimal_places=2,
521 default=REGIME_TRAVAIL_DEFAULT,
c1195471 522 verbose_name=u"Régime de travail",
2d4d2fcf 523 help_text="% du temps complet")
139686f2 524 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
428e3c0b 525 decimal_places=2,
2d4d2fcf 526 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
c1195471 527 verbose_name=u"Nb. heures par semaine")
bd28238f
NC
528
529 # Contrat
5d680e84 530 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
b666864e 531 contrat_date_debut = models.DateField(help_text=HELP_TEXT_DATE)
1f109689 532 contrat_date_fin = models.DateField(null=True, blank=True,
b666864e 533 help_text=HELP_TEXT_DATE)
bd28238f 534
29dffede
OL
535 # Justifications
536 justif_nouveau_statut_label = u'Justifier le statut que ce type de poste nécessite (national, expatrié, màd ou détachement)'
537 justif_nouveau_statut = models.TextField(verbose_name=justif_nouveau_statut_label, null=True, blank=True)
a83daab3 538 justif_nouveau_tmp_remplacement_label = u"Si l'employé effectue un remplacement temporaire, préciser"
29dffede 539 justif_nouveau_tmp_remplacement = models.TextField(verbose_name=justif_nouveau_tmp_remplacement_label, null=True, blank=True)
a83daab3 540 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 541 justif_nouveau_salaire = models.TextField(verbose_name=justif_nouveau_salaire_label, null=True, blank=True)
a83daab3 542 justif_nouveau_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
29dffede
OL
543 justif_nouveau_commentaire = models.TextField(verbose_name=justif_nouveau_commentaire_label, null=True, blank=True)
544 justif_rempl_type_contrat_label = u"Changement de type de contrat, ex : d'un CDD en CDI"
545 justif_rempl_type_contrat = models.TextField(verbose_name=justif_rempl_type_contrat_label, null=True, blank=True)
a83daab3 546 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 547 justif_rempl_statut_employe = models.TextField(verbose_name=justif_rempl_statut_employe_label, null=True, blank=True)
a83daab3 548 justif_rempl_evaluation_label = u"L'évaluation de l'employé est-elle favorable? Préciser"
29dffede
OL
549 justif_rempl_evaluation = models.TextField(verbose_name=justif_rempl_evaluation_label, null=True, blank=True)
550 justif_rempl_salaire_label = u"Si le salaire de l'employé est modifié et/ou ne correspond pas à son classement, justifier"
551 justif_rempl_salaire = models.TextField(verbose_name=justif_rempl_salaire_label, null=True, blank=True)
a83daab3 552 justif_rempl_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
29dffede
OL
553 justif_rempl_commentaire = models.TextField(verbose_name=justif_rempl_commentaire_label, null=True, blank=True)
554
bd28238f 555 # Comptes
dfc2c344 556 compte_compta = models.CharField(max_length=10, default='aucun',
557 verbose_name=u'Compte comptabilité',
558 choices=COMPTE_COMPTA_CHOICES)
bd28238f 559 compte_courriel = models.BooleanField()
428e3c0b 560
c3f0b49f
EMS
561 # DAE numérisée
562 dae_numerisee = models.FileField(upload_to='dae/dae_numerisee', storage=UPLOAD_STORAGE,
5be87e56 563 blank=True, null=True, verbose_name="DAE numérisée")
c3f0b49f 564
0140cbd2 565 # Méta
566 date_creation = models.DateTimeField(auto_now_add=True)
428e3c0b 567
e4f56614
OL
568 # Managers
569 objects = DossierManager()
428e3c0b 570
aec2c91e 571 def __unicode__(self):
e4f56614 572 return u'[%s] %s - %s' % (self.poste.implantation, self.poste.nom, self.employe)
bd28238f 573
a7186cbb
OL
574 def taux_devise(self):
575 if self.devise.code == 'EUR':
576 return 1
2455f48d 577 liste_taux = self.devise.tauxchange_set.order_by('-annee')
a7186cbb
OL
578 if len(liste_taux) == 0:
579 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.poste.implantation))
580 else:
581 return liste_taux[0].taux
582
eed93931
OL
583 def get_salaire_anterieur_euros(self):
584 if self.devise_anterieur.code == 'EUR':
585 tx = 1
586 else:
2455f48d 587 liste_taux = self.devise_anterieur.tauxchange_set.order_by('-annee')
eed93931
OL
588 if len(liste_taux) == 0:
589 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_anterieur, self.poste.implantation))
590 tx = liste_taux[0].taux
591 return (float)(tx) * (float)(self.salaire_anterieur)
592
593 def get_salaire_titulaire_anterieur_euros(self):
2626f449 594 if self.devise_titulaire_anterieur.code == 'EUR':
eed93931
OL
595 tx = 1
596 else:
2455f48d 597 liste_taux = self.devise_titulaire_anterieur.tauxchange_set.order_by('-annee')
eed93931
OL
598 if len(liste_taux) == 0:
599 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_titulaire_anterieur, self.poste.implantation))
600 tx = liste_taux[0].taux
601 return (float)(tx) * (float)(self.salaire_titulaire_anterieur)
602
b1baa306 603 def get_salaire_euros(self):
a7186cbb 604 tx = self.taux_devise()
b1baa306
OL
605 return (float)(tx) * (float)(self.salaire)
606
bf6fbbcf 607 def get_remunerations_brutes(self):
57bd966c 608 """
bf6fbbcf
OL
609 1 Salaire de base
610 3 Indemnité de base
611 4 Indemnité d'expatriation
612 5 Indemnité pour frais
613 6 Indemnité de logement
614 7 Indemnité de fonction
615 8 Indemnité de responsabilité
616 9 Indemnité de transport
617 10 Indemnité compensatrice
618 11 Indemnité de subsistance
619 12 Indemnité différentielle
620 13 Prime d'installation
621 14 Billet d'avion
622 15 Déménagement
623 16 Indemnité de départ
624 18 Prime de 13ième mois
625 19 Prime d'intérim
57bd966c 626 """
bf6fbbcf
OL
627 ids = [1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19]
628 return [r for r in self.remuneration_set.all() if r.type_id in ids]
629
630 def get_charges_salariales(self):
631 """
632 20 Charges salariales ?
633 """
634 ids = [20, ]
635 return [r for r in self.remuneration_set.all() if r.type_id in ids]
636
637 def get_total_charges_salariales(self):
638 total = 0.0
639 for r in self.get_charges_salariales():
640 total += r.montant_euro()
641 return total
642
643 def get_charges_patronales(self):
644 """
645 17 Charges patronales
646 """
647 ids = [17, ]
648 return [r for r in self.remuneration_set.all() if r.type_id in ids]
649
650 def get_total_charges_patronales(self):
a9e52624 651 total = 0.0
bf6fbbcf 652 for r in self.get_charges_patronales():
a9e52624
OL
653 total += r.montant_euro()
654 return total
57bd966c 655
bf6fbbcf
OL
656 def get_salaire_brut(self):
657 """
658 somme des rémuérations brutes
659 """
660 total = 0.0
661 for r in self.get_remunerations_brutes():
662 total += r.montant_euro()
663 return total
664
665 def get_salaire_net(self):
666 """
667 salaire brut - charges salariales
668 """
669 total_charges = 0.0
670 for r in self.get_charges_salariales():
671 total_charges += r.montant_euro()
672 return self.get_salaire_brut() - total_charges
673
674 def get_couts_auf(self):
675 """
676 salaire net + charges patronales
677 """
678 total_charges = 0.0
679 for r in self.get_charges_patronales():
680 total_charges += r.montant_euro()
681 return self.get_salaire_net() + total_charges
682
683 def get_remunerations_tierces(self):
57bd966c 684 """
bf6fbbcf 685 2 Salaire MAD
57bd966c
OL
686 """
687 return [r for r in self.remuneration_set.all() if r.type_id in (2, )]
688
bf6fbbcf 689 def get_total_remunerations_tierces(self):
a9e52624 690 total = 0.0
1bc84af4 691 for r in self.get_remunerations_tierces():
a9e52624
OL
692 total += r.montant_euro()
693 return total
694
c511cd1f
EMS
695 def valide(self):
696 return self.etat in (DOSSIER_ETAT_REGION_FINALISATION,
697 DOSSIER_ETAT_DRH_FINALISATION,
698 DOSSIER_ETAT_FINALISE)
699
bd28238f 700
0140cbd2 701# Tester l'enregistrement car les models.py sont importés au complet
702if not reversion.is_registered(Dossier):
703 reversion.register(Dossier)
bd28238f 704
2d4d2fcf 705class DossierPiece(models.Model):
706 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
707 Ex.: Lettre de motivation.
708 """
709 dossier = models.ForeignKey("Dossier")
710 nom = models.CharField(verbose_name="Nom", max_length=255)
e158c6de
DB
711 fichier = models.FileField(verbose_name="Fichier",
712 upload_to='dae/dossier',
713 storage=UPLOAD_STORAGE)
2d4d2fcf 714
715
03b395db
OL
716class DossierComparaison(models.Model):
717 """
718 Photo d'une comparaison salariale au moment de l'embauche.
719 """
720 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
e158c6de
DB
721 statut = models.ForeignKey(rh.Statut, related_name='+',
722 verbose_name='Statut', null=True, blank=True, )
723 classement = models.ForeignKey(rh.Classement, related_name='+',
724 verbose_name='Classement', null=True, blank=True, )
725 implantation = models.ForeignKey(ref.Implantation, related_name='+',
726 null=True, blank=True)
03b395db
OL
727 poste = models.CharField(max_length=255, null=True, blank=True)
728 personne = models.CharField(max_length=255, null=True, blank=True)
729 montant = models.IntegerField(null=True)
730 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
1d0f4eef 731
296fff6b
EMS
732 objects = DossierComparaisonManager()
733
1d0f4eef 734 def taux_devise(self):
ae5c920b
OL
735 if self.devise.code == 'EUR':
736 return 1
2455f48d 737 liste_taux = self.devise.tauxchange_set.order_by('-annee')
1d0f4eef 738 if len(liste_taux) == 0:
0cbf1827 739 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
1d0f4eef
OL
740 else:
741 return liste_taux[0].taux
742
743 def montant_euros(self):
744 return round(float(self.montant) * float(self.taux_devise()), 2)
745
03b395db 746
c589d980 747
2d4d2fcf 748### RÉMUNÉRATION
749
bd28238f 750class Remuneration(models.Model):
5d680e84 751 # Identification
bd28238f 752 dossier = models.ForeignKey('Dossier', db_column='dossier')
cb1d62b5
NC
753 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
754 related_name='+')
cb1d62b5
NC
755 montant = models.DecimalField(max_digits=12, decimal_places=2,
756 null=True) # Annuel
494ff2be 757 devise = models.ForeignKey(rh.Devise, to_field='code',
5d680e84 758 db_column='devise', related_name='+')
cb1d62b5 759 precision = models.CharField(max_length=255, null=True, blank=True)
bd28238f 760
5d680e84 761 # Méta
bd28238f 762 date_creation = models.DateField(auto_now_add=True)
2d4d2fcf 763 user_creation = models.IntegerField(null=True, blank=True) # TODO : user
bd28238f 764
ee91bc95
NC
765 def montant_mois(self):
766 return round(self.montant / 12, 2)
767
768 def taux_devise(self):
ae5c920b
OL
769 if self.devise.code == 'EUR':
770 return 1
2455f48d 771 liste_taux = self.devise.tauxchange_set.order_by('-annee')
dc4b78a7 772 if len(liste_taux) == 0:
ae5c920b 773 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
dc4b78a7
OL
774 else:
775 return liste_taux[0].taux
ee91bc95
NC
776
777 def montant_euro(self):
a9e52624 778 return round(float(self.montant) * float(self.taux_devise()), 2)
ee91bc95
NC
779
780 def montant_euro_mois(self):
781 return round(self.montant_euro() / 12, 2)
c1834169
EMS
782
783
784### CONTRATS
785
786class Contrat(models.Model):
c511cd1f 787 dossier = models.ForeignKey(Dossier, related_name='contrats')
c1834169
EMS
788 type = models.ForeignKey(rh.TypeContrat, related_name='+')
789 fichier = models.FileField(upload_to='dae/contrats', storage=UPLOAD_STORAGE)