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