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