date format jj-mm-aaaa
[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
a9c281dd 14from rh_v1 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 []
164 postes = [p for p in self.id_rh.poste1.all()]
165 return sorted(postes, key=lambda poste: poste.id, reverse=True)
428e3c0b 166
f3333b0e
OL
167 def get_complement_nom(self):
168 """
1bc84af4 169 Inspecte les modèles rh v1 pour trouver dans le dernier dossier
2d4d2fcf 170 un complément de titre de poste.
f3333b0e
OL
171 """
172 dossiers = self.get_dossiers()
173 if len(dossiers) > 0:
174 nom = dossiers[0].complement1
175 else:
176 nom = ""
a9c281dd 177 return nom
f3333b0e
OL
178
179 def get_employe(self):
180 """
181 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
182 """
183 dossiers = self.get_dossiers()
184 if len(dossiers) > 0:
185 return dossiers[0].employe
186 else:
187 return None
188
179f6b49 189 def get_default_devise(self):
1bc84af4 190 """Récupère la devise par défaut en fonction de l'implantation
2d4d2fcf 191 (EUR autrement)
192 """
179f6b49 193 try:
6e4600ef 194 implantation_devise = rh.TauxChange.objects \
195 .filter(implantation=self.implantation)[0].devise
179f6b49
OL
196 except:
197 implantation_devise = 5 # EUR
198 return implantation_devise
199
c0413a6f
OL
200 #####################
201 # Classement de poste
202 #####################
203
204 def get_couts_minimum(self):
83b94a87
EMS
205 return self.salaire_min + self.indemn_expat_min + self.indemn_fct_min + self.charges_patronales_min + self.autre_min
206
207 def get_salaire_minimum(self):
208 return self.get_couts_minimum() - self.charges_patronales_min
c0413a6f
OL
209
210 def get_taux_minimum(self):
4e439a89
OL
211 if self.devise_min.code == 'EUR':
212 return 1
2455f48d 213 liste_taux = self.devise_min.tauxchange_set.order_by('-annee')
4e439a89
OL
214 if len(liste_taux) == 0:
215 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_min, self.implantation))
b6825282 216 else:
4e439a89 217 return liste_taux[0].taux
c0413a6f
OL
218
219 def get_couts_minimum_euros(self):
fa5b95ed 220 return float(self.get_couts_minimum()) * self.get_taux_minimum()
c0413a6f 221
83b94a87 222 def get_salaire_minimum_euros(self):
fa5b95ed 223 return float(self.get_salaire_minimum()) * self.get_taux_minimum()
83b94a87 224
c0413a6f 225 def get_couts_maximum(self):
83b94a87
EMS
226 return self.salaire_max + self.indemn_expat_max + self.indemn_fct_max + self.charges_patronales_max + self.autre_max
227
228 def get_salaire_maximum(self):
229 return self.get_couts_maximum() - self.charges_patronales_max
c0413a6f
OL
230
231 def get_taux_maximum(self):
4e439a89
OL
232 if self.devise_max.code == 'EUR':
233 return 1
2455f48d 234 liste_taux = self.devise_max.tauxchange_set.order_by('-annee')
4e439a89
OL
235 if len(liste_taux) == 0:
236 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_max, self.implantation))
b6825282 237 else:
4e439a89 238 return liste_taux[0].taux
c0413a6f
OL
239
240 def get_couts_maximum_euros(self):
fa5b95ed 241 return float(self.get_couts_maximum()) * self.get_taux_maximum()
c0413a6f 242
83b94a87 243 def get_salaire_maximum_euros(self):
fa5b95ed 244 return float(self.get_salaire_maximum()) * self.get_taux_maximum()
12c7f8a7
OL
245
246 def show_taux_minimum(self):
247 try:
248 return self.get_taux_minimum()
83b94a87 249 except DeviseException, e:
12c7f8a7
OL
250 return e
251
252 def show_couts_minimum_euros(self):
253 try:
254 return self.get_couts_minimum_euros()
83b94a87
EMS
255 except DeviseException, e:
256 return e
257
258 def show_salaire_minimum_euros(self):
259 try:
260 return self.get_salaire_minimum_euros()
261 except DeviseException, e:
12c7f8a7
OL
262 return e
263
264 def show_taux_maximum(self):
265 try:
266 return self.get_taux_maximum()
83b94a87 267 except DeviseException, e:
12c7f8a7
OL
268 return e
269
270 def show_couts_maximum_euros(self):
271 try:
272 return self.get_couts_maximum_euros()
83b94a87
EMS
273 except DeviseException, e:
274 return e
275
276 def show_salaire_maximum_euros(self):
277 try:
278 return self.get_salaire_maximum_euros()
279 except DeviseException, e:
12c7f8a7
OL
280 return e
281
282
c0413a6f
OL
283 ######################
284 # Comparaison de poste
285 ######################
a3fee9c5
OL
286
287 def est_comparable(self):
288 """
289 Si on a au moins une valeur de saisie dans les comparaisons, alors le poste
290 est comparable.
291 """
292 if self.comp_universite_min is None and \
293 self.comp_fonctionpub_min is None and \
294 self.comp_locale_min is None and \
295 self.comp_ong_min is None and \
296 self.comp_autre_min is None and \
297 self.comp_universite_max is None and \
298 self.comp_fonctionpub_max is None and \
299 self.comp_locale_max is None and \
300 self.comp_ong_max is None and \
301 self.comp_autre_max is None:
302 return False
303 else:
304 return True
1bc84af4 305
a3fee9c5 306
c0413a6f
OL
307 def get_taux_comparaison(self):
308 try:
2455f48d 309 return rh.TauxChange.objects.filter(devise=self.devise_comparaison)[0].taux
c0413a6f
OL
310 except:
311 return 1
312
313 def get_comp_universite_min_euros(self):
314 return (float)(self.comp_universite_min) * self.get_taux_comparaison()
315
316 def get_comp_fonctionpub_min_euros(self):
317 return (float)(self.comp_fonctionpub_min) * self.get_taux_comparaison()
318
319 def get_comp_locale_min_euros(self):
320 return (float)(self.comp_locale_min) * self.get_taux_comparaison()
321
322 def get_comp_ong_min_euros(self):
323 return (float)(self.comp_ong_min) * self.get_taux_comparaison()
324
325 def get_comp_autre_min_euros(self):
326 return (float)(self.comp_autre_min) * self.get_taux_comparaison()
327
328 def get_comp_universite_max_euros(self):
329 return (float)(self.comp_universite_max) * self.get_taux_comparaison()
330
331 def get_comp_fonctionpub_max_euros(self):
332 return (float)(self.comp_fonctionpub_max) * self.get_taux_comparaison()
333
334 def get_comp_locale_max_euros(self):
335 return (float)(self.comp_locale_max) * self.get_taux_comparaison()
336
337 def get_comp_ong_max_euros(self):
338 return (float)(self.comp_ong_max) * self.get_taux_comparaison()
339
340 def get_comp_autre_max_euros(self):
341 return (float)(self.comp_autre_max) * self.get_taux_comparaison()
342
a9e52624 343
5d680e84 344 def __unicode__(self):
f3333b0e 345 """
1bc84af4 346 Cette fonction est consommatrice SQL car elle cherche les dossiers
2d4d2fcf 347 qui ont été liés à celui-ci.
f3333b0e
OL
348 """
349 complement_nom_poste = self.get_complement_nom()
c7a4aa2b
OL
350 if complement_nom_poste is None:
351 complement_nom_poste = ""
f3333b0e
OL
352 data = (
353 self.implantation,
354 self.type_poste.nom,
355 self.nom,
f3333b0e 356 )
a7c68130 357 return u'%s - %s (%s)' % data
5d680e84 358
bd28238f 359
a9c281dd
OL
360# Tester l'enregistrement car les models.py sont importés au complet
361if not reversion.is_registered(Poste):
362 reversion.register(Poste)
363
bd28238f 364
5d680e84
NC
365POSTE_FINANCEMENT_CHOICES = (
366 ('A', 'A - Frais de personnel'),
367 ('B', 'B - Projet(s)-Titre(s)'),
368 ('C', 'C - Autre')
369)
bd28238f 370
5d680e84 371class PosteFinancement(models.Model):
5d680e84
NC
372 poste = models.ForeignKey('Poste', related_name='financements')
373 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
43d04712 374 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
375 help_text="ex.: 33.33 % (décimale avec point)")
376 commentaire = models.TextField(
377 help_text="Spécifiez la source de financement.")
bd28238f 378
43d04712 379 class Meta:
380 ordering = ['type']
bd28238f 381
c0413a6f 382 def __unicode__(self):
320cd84a 383 return u"%s %.0f%% %s" % (self.get_type_display(), self.pourcentage, self.commentaire)
c0413a6f 384
c589d980 385
2d4d2fcf 386class PostePiece(models.Model):
387 """Documents relatifs au Poste
388 Ex.: Description de poste
389 """
390 poste = models.ForeignKey("Poste")
c1195471
OL
391 nom = models.CharField(verbose_name=u"Nom", max_length=255)
392 fichier = models.FileField(verbose_name=u"Fichier",
e158c6de
DB
393 upload_to='dae/poste',
394 storage=UPLOAD_STORAGE)
2d4d2fcf 395
068d1462
OL
396class PosteComparaison(models.Model):
397 poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
e61bfeb0 398 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
3f5f3898
OL
399 statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name=u'Statut', null=True, blank=True, )
400 classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name=u'Classement', null=True, blank=True, )
401 nom = models.CharField(verbose_name=u"Poste", max_length=255, null=True, blank=True)
18170017
EMS
402 montant = models.IntegerField(
403 null=True, verbose_name="Rémunération totale sans les charges patronales"
404 )
068d1462 405 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
1d0f4eef 406
1bc84af4
EMS
407 objects = PosteComparaisonManager()
408
1d0f4eef 409 def taux_devise(self):
ae5c920b
OL
410 if self.devise.code == 'EUR':
411 return 1
2455f48d 412 liste_taux = self.devise.tauxchange_set.order_by('-annee')
1d0f4eef 413 if len(liste_taux) == 0:
ae5c920b 414 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
1d0f4eef
OL
415 else:
416 return liste_taux[0].taux
417
418 def montant_euros(self):
419 return round(float(self.montant) * float(self.taux_devise()), 2)
068d1462
OL
420
421
2d4d2fcf 422### EMPLOYÉ/PERSONNE
423
424# TODO : migration pour m -> M, f -> F
c589d980 425
bd28238f 426GENRE_CHOICES = (
139686f2
NC
427 ('m', 'Homme'),
428 ('f', 'Femme'),
bd28238f
NC
429)
430
bd28238f 431class Employe(models.Model):
bd28238f
NC
432
433 # Modèle existant
428e3c0b 434 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
c1195471 435 verbose_name=u'Employé')
bd28238f 436 nom = models.CharField(max_length=255)
c1195471 437 prenom = models.CharField(max_length=255, verbose_name=u'Prénom')
07b40eda 438 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
bd28238f 439
139686f2 440 def __unicode__(self):
2d4d2fcf 441 return u'%s %s' % (self.prenom, self.nom.upper())
139686f2 442
bd28238f 443
2d4d2fcf 444### DOSSIER
445
446STATUT_RESIDENCE_CHOICES = (
447 ('local', 'Local'),
448 ('expat', 'Expatrié'),
449)
450
bd28238f 451COMPTE_COMPTA_CHOICES = (
494ff2be
NC
452 ('coda', 'CODA'),
453 ('scs', 'SCS'),
454 ('aucun', 'Aucun'),
bd28238f
NC
455)
456
afc204bf 457class Dossier(DossierWorkflow, models.Model):
bd28238f
NC
458
459 # Modèle existant
139686f2 460 employe = models.ForeignKey('Employe', related_name='+', editable=False)
4e4a3545 461 poste = models.ForeignKey('Poste', related_name='dossiers', editable=False)
5d680e84 462 statut = models.ForeignKey(rh.Statut, related_name='+')
428e3c0b 463 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
1f109689 464 null=True, blank=True,
e158c6de 465 verbose_name=u"Organisme",
dfc2c344 466 help_text="Si détaché (DET) ou mis à disposition (MAD), \
467 préciser l'organisme.",
468 related_name='+')
0288adb5 469 organisme_bstg_autre = models.CharField(max_length=255,
c1195471 470 verbose_name=u"Autre organisme",
0288adb5
OL
471 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
472 null=True,
473 blank=True,)
bd28238f 474
139686f2
NC
475 # Données antérieures de l'employé
476 statut_anterieur = models.ForeignKey(
477 rh.Statut, related_name='+', null=True, blank=True,
c1195471 478 verbose_name=u'Statut antérieur')
139686f2
NC
479 classement_anterieur = models.ForeignKey(
480 rh.Classement, related_name='+', null=True, blank=True,
c1195471 481 verbose_name=u'Classement précédent')
139686f2
NC
482 salaire_anterieur = models.DecimalField(
483 max_digits=12, decimal_places=2, null=True, default=None,
481fbd33 484 blank=True, verbose_name=u'Salaire précédent')
e158c6de
DB
485 devise_anterieur = models.ForeignKey(rh.Devise, related_name='+',
486 null=True, blank=True)
487 type_contrat_anterieur = models.ForeignKey(rh.TypeContrat,
488 related_name='+', null=True, blank=True,
489 verbose_name=u'Type contrat antérieur', )
139686f2
NC
490
491 # Données du titulaire précédent
492 employe_anterieur = models.ForeignKey(
493 rh.Employe, related_name='+', null=True, blank=True,
c1195471 494 verbose_name=u'Employé précédent')
139686f2
NC
495 statut_titulaire_anterieur = models.ForeignKey(
496 rh.Statut, related_name='+', null=True, blank=True,
c1195471 497 verbose_name=u'Statut titulaire précédent')
139686f2
NC
498 classement_titulaire_anterieur = models.ForeignKey(
499 rh.Classement, related_name='+', null=True, blank=True,
c1195471 500 verbose_name=u'Classement titulaire précédent')
139686f2
NC
501 salaire_titulaire_anterieur = models.DecimalField(
502 max_digits=12, decimal_places=2, default=None, null=True,
481fbd33 503 blank=True, verbose_name=u'Salaire titulaire précédent')
f8c7c0b8 504 devise_titulaire_anterieur = models.ForeignKey(rh.Devise, related_name='+', null=True, blank=True)
494ff2be 505
bd28238f
NC
506 # Recrutement
507 remplacement = models.BooleanField()
428e3c0b 508 statut_residence = models.CharField(max_length=10, default='local',
2d4d2fcf 509 verbose_name="Statut",
510 choices=STATUT_RESIDENCE_CHOICES)
bd28238f
NC
511
512 # Rémunération
494ff2be 513 classement = models.ForeignKey(rh.Classement, related_name='+',
2d4d2fcf 514 null=True, blank=True,
c1195471 515 verbose_name=u'Classement proposé')
494ff2be 516 salaire = models.DecimalField(max_digits=12, decimal_places=2,
c1195471 517 verbose_name=u'Salaire de base',
2d4d2fcf 518 null=True, default=None)
e8e75458 519 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
428e3c0b 520 regime_travail = models.DecimalField(max_digits=12,
2d4d2fcf 521 decimal_places=2,
522 default=REGIME_TRAVAIL_DEFAULT,
c1195471 523 verbose_name=u"Régime de travail",
2d4d2fcf 524 help_text="% du temps complet")
139686f2 525 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
428e3c0b 526 decimal_places=2,
2d4d2fcf 527 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
c1195471 528 verbose_name=u"Nb. heures par semaine")
bd28238f
NC
529
530 # Contrat
5d680e84 531 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
b666864e 532 contrat_date_debut = models.DateField(help_text=HELP_TEXT_DATE)
1f109689 533 contrat_date_fin = models.DateField(null=True, blank=True,
b666864e 534 help_text=HELP_TEXT_DATE)
bd28238f 535
29dffede
OL
536 # Justifications
537 justif_nouveau_statut_label = u'Justifier le statut que ce type de poste nécessite (national, expatrié, màd ou détachement)'
538 justif_nouveau_statut = models.TextField(verbose_name=justif_nouveau_statut_label, null=True, blank=True)
a83daab3 539 justif_nouveau_tmp_remplacement_label = u"Si l'employé effectue un remplacement temporaire, préciser"
29dffede 540 justif_nouveau_tmp_remplacement = models.TextField(verbose_name=justif_nouveau_tmp_remplacement_label, null=True, blank=True)
a83daab3 541 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 542 justif_nouveau_salaire = models.TextField(verbose_name=justif_nouveau_salaire_label, null=True, blank=True)
a83daab3 543 justif_nouveau_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
29dffede
OL
544 justif_nouveau_commentaire = models.TextField(verbose_name=justif_nouveau_commentaire_label, null=True, blank=True)
545 justif_rempl_type_contrat_label = u"Changement de type de contrat, ex : d'un CDD en CDI"
546 justif_rempl_type_contrat = models.TextField(verbose_name=justif_rempl_type_contrat_label, null=True, blank=True)
a83daab3 547 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 548 justif_rempl_statut_employe = models.TextField(verbose_name=justif_rempl_statut_employe_label, null=True, blank=True)
a83daab3 549 justif_rempl_evaluation_label = u"L'évaluation de l'employé est-elle favorable? Préciser"
29dffede
OL
550 justif_rempl_evaluation = models.TextField(verbose_name=justif_rempl_evaluation_label, null=True, blank=True)
551 justif_rempl_salaire_label = u"Si le salaire de l'employé est modifié et/ou ne correspond pas à son classement, justifier"
552 justif_rempl_salaire = models.TextField(verbose_name=justif_rempl_salaire_label, null=True, blank=True)
a83daab3 553 justif_rempl_commentaire_label = u"COMMENTAIRES ADDITIONNELS"
29dffede
OL
554 justif_rempl_commentaire = models.TextField(verbose_name=justif_rempl_commentaire_label, null=True, blank=True)
555
bd28238f 556 # Comptes
dfc2c344 557 compte_compta = models.CharField(max_length=10, default='aucun',
558 verbose_name=u'Compte comptabilité',
559 choices=COMPTE_COMPTA_CHOICES)
bd28238f 560 compte_courriel = models.BooleanField()
428e3c0b 561
c3f0b49f
EMS
562 # DAE numérisée
563 dae_numerisee = models.FileField(upload_to='dae/dae_numerisee', storage=UPLOAD_STORAGE,
5be87e56 564 blank=True, null=True, verbose_name="DAE numérisée")
c3f0b49f 565
0140cbd2 566 # Méta
567 date_creation = models.DateTimeField(auto_now_add=True)
428e3c0b 568
e4f56614
OL
569 # Managers
570 objects = DossierManager()
428e3c0b 571
aec2c91e 572 def __unicode__(self):
e4f56614 573 return u'[%s] %s - %s' % (self.poste.implantation, self.poste.nom, self.employe)
bd28238f 574
a7186cbb
OL
575 def taux_devise(self):
576 if self.devise.code == 'EUR':
577 return 1
2455f48d 578 liste_taux = self.devise.tauxchange_set.order_by('-annee')
a7186cbb
OL
579 if len(liste_taux) == 0:
580 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.poste.implantation))
581 else:
582 return liste_taux[0].taux
583
eed93931
OL
584 def get_salaire_anterieur_euros(self):
585 if self.devise_anterieur.code == 'EUR':
586 tx = 1
587 else:
2455f48d 588 liste_taux = self.devise_anterieur.tauxchange_set.order_by('-annee')
eed93931
OL
589 if len(liste_taux) == 0:
590 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_anterieur, self.poste.implantation))
591 tx = liste_taux[0].taux
592 return (float)(tx) * (float)(self.salaire_anterieur)
593
594 def get_salaire_titulaire_anterieur_euros(self):
2626f449 595 if self.devise_titulaire_anterieur.code == 'EUR':
eed93931
OL
596 tx = 1
597 else:
2455f48d 598 liste_taux = self.devise_titulaire_anterieur.tauxchange_set.order_by('-annee')
eed93931
OL
599 if len(liste_taux) == 0:
600 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_titulaire_anterieur, self.poste.implantation))
601 tx = liste_taux[0].taux
602 return (float)(tx) * (float)(self.salaire_titulaire_anterieur)
603
b1baa306 604 def get_salaire_euros(self):
a7186cbb 605 tx = self.taux_devise()
b1baa306
OL
606 return (float)(tx) * (float)(self.salaire)
607
bf6fbbcf 608 def get_remunerations_brutes(self):
57bd966c 609 """
bf6fbbcf
OL
610 1 Salaire de base
611 3 Indemnité de base
612 4 Indemnité d'expatriation
613 5 Indemnité pour frais
614 6 Indemnité de logement
615 7 Indemnité de fonction
616 8 Indemnité de responsabilité
617 9 Indemnité de transport
618 10 Indemnité compensatrice
619 11 Indemnité de subsistance
620 12 Indemnité différentielle
621 13 Prime d'installation
622 14 Billet d'avion
623 15 Déménagement
624 16 Indemnité de départ
625 18 Prime de 13ième mois
626 19 Prime d'intérim
57bd966c 627 """
bf6fbbcf
OL
628 ids = [1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19]
629 return [r for r in self.remuneration_set.all() if r.type_id in ids]
630
631 def get_charges_salariales(self):
632 """
633 20 Charges salariales ?
634 """
635 ids = [20, ]
636 return [r for r in self.remuneration_set.all() if r.type_id in ids]
637
638 def get_total_charges_salariales(self):
639 total = 0.0
640 for r in self.get_charges_salariales():
641 total += r.montant_euro()
642 return total
643
644 def get_charges_patronales(self):
645 """
646 17 Charges patronales
647 """
648 ids = [17, ]
649 return [r for r in self.remuneration_set.all() if r.type_id in ids]
650
651 def get_total_charges_patronales(self):
a9e52624 652 total = 0.0
bf6fbbcf 653 for r in self.get_charges_patronales():
a9e52624
OL
654 total += r.montant_euro()
655 return total
57bd966c 656
bf6fbbcf
OL
657 def get_salaire_brut(self):
658 """
659 somme des rémuérations brutes
660 """
661 total = 0.0
662 for r in self.get_remunerations_brutes():
663 total += r.montant_euro()
664 return total
665
666 def get_salaire_net(self):
667 """
668 salaire brut - charges salariales
669 """
670 total_charges = 0.0
671 for r in self.get_charges_salariales():
672 total_charges += r.montant_euro()
673 return self.get_salaire_brut() - total_charges
674
675 def get_couts_auf(self):
676 """
677 salaire net + charges patronales
678 """
679 total_charges = 0.0
680 for r in self.get_charges_patronales():
681 total_charges += r.montant_euro()
682 return self.get_salaire_net() + total_charges
683
684 def get_remunerations_tierces(self):
57bd966c 685 """
bf6fbbcf 686 2 Salaire MAD
57bd966c
OL
687 """
688 return [r for r in self.remuneration_set.all() if r.type_id in (2, )]
689
bf6fbbcf 690 def get_total_remunerations_tierces(self):
a9e52624 691 total = 0.0
1bc84af4 692 for r in self.get_remunerations_tierces():
a9e52624
OL
693 total += r.montant_euro()
694 return total
695
c511cd1f
EMS
696 def valide(self):
697 return self.etat in (DOSSIER_ETAT_REGION_FINALISATION,
698 DOSSIER_ETAT_DRH_FINALISATION,
699 DOSSIER_ETAT_FINALISE)
700
bd28238f 701
0140cbd2 702# Tester l'enregistrement car les models.py sont importés au complet
703if not reversion.is_registered(Dossier):
704 reversion.register(Dossier)
bd28238f 705
2d4d2fcf 706class DossierPiece(models.Model):
707 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
708 Ex.: Lettre de motivation.
709 """
710 dossier = models.ForeignKey("Dossier")
711 nom = models.CharField(verbose_name="Nom", max_length=255)
e158c6de
DB
712 fichier = models.FileField(verbose_name="Fichier",
713 upload_to='dae/dossier',
714 storage=UPLOAD_STORAGE)
2d4d2fcf 715
716
03b395db
OL
717class DossierComparaison(models.Model):
718 """
719 Photo d'une comparaison salariale au moment de l'embauche.
720 """
721 dossier = models.ForeignKey('Dossier', related_name='comparaisons')
e158c6de
DB
722 statut = models.ForeignKey(rh.Statut, related_name='+',
723 verbose_name='Statut', null=True, blank=True, )
724 classement = models.ForeignKey(rh.Classement, related_name='+',
725 verbose_name='Classement', null=True, blank=True, )
726 implantation = models.ForeignKey(ref.Implantation, related_name='+',
727 null=True, blank=True)
03b395db
OL
728 poste = models.CharField(max_length=255, null=True, blank=True)
729 personne = models.CharField(max_length=255, null=True, blank=True)
730 montant = models.IntegerField(null=True)
731 devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
1d0f4eef 732
296fff6b
EMS
733 objects = DossierComparaisonManager()
734
1d0f4eef 735 def taux_devise(self):
ae5c920b
OL
736 if self.devise.code == 'EUR':
737 return 1
2455f48d 738 liste_taux = self.devise.tauxchange_set.order_by('-annee')
1d0f4eef 739 if len(liste_taux) == 0:
0cbf1827 740 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
1d0f4eef
OL
741 else:
742 return liste_taux[0].taux
743
744 def montant_euros(self):
745 return round(float(self.montant) * float(self.taux_devise()), 2)
746
03b395db 747
c589d980 748
2d4d2fcf 749### RÉMUNÉRATION
750
bd28238f 751class Remuneration(models.Model):
5d680e84 752 # Identification
bd28238f 753 dossier = models.ForeignKey('Dossier', db_column='dossier')
cb1d62b5
NC
754 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
755 related_name='+')
cb1d62b5
NC
756 montant = models.DecimalField(max_digits=12, decimal_places=2,
757 null=True) # Annuel
494ff2be 758 devise = models.ForeignKey(rh.Devise, to_field='code',
5d680e84 759 db_column='devise', related_name='+')
cb1d62b5 760 precision = models.CharField(max_length=255, null=True, blank=True)
bd28238f 761
5d680e84 762 # Méta
bd28238f 763 date_creation = models.DateField(auto_now_add=True)
2d4d2fcf 764 user_creation = models.IntegerField(null=True, blank=True) # TODO : user
bd28238f 765
ee91bc95
NC
766 def montant_mois(self):
767 return round(self.montant / 12, 2)
768
769 def taux_devise(self):
ae5c920b
OL
770 if self.devise.code == 'EUR':
771 return 1
2455f48d 772 liste_taux = self.devise.tauxchange_set.order_by('-annee')
dc4b78a7 773 if len(liste_taux) == 0:
ae5c920b 774 raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
dc4b78a7
OL
775 else:
776 return liste_taux[0].taux
ee91bc95
NC
777
778 def montant_euro(self):
a9e52624 779 return round(float(self.montant) * float(self.taux_devise()), 2)
ee91bc95
NC
780
781 def montant_euro_mois(self):
782 return round(self.montant_euro() / 12, 2)
c1834169
EMS
783
784
785### CONTRATS
786
787class Contrat(models.Model):
c511cd1f 788 dossier = models.ForeignKey(Dossier, related_name='contrats')
c1834169
EMS
789 type = models.ForeignKey(rh.TypeContrat, related_name='+')
790 fichier = models.FileField(upload_to='dae/contrats', storage=UPLOAD_STORAGE)