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