fix #1429
[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
f258e4e7 9from managers import DossierManager, PosteManager
bd28238f 10import datamaster_modeles.models as ref
a9c281dd 11from rh_v1 import models as rh
bd28238f
NC
12
13STATUT_RESIDENCE_CHOICES = (
5d680e84
NC
14 ('local', 'Local'),
15 ('expat', 'Expatrié'),
bd28238f
NC
16)
17
18POSTE_APPEL_CHOICES = (
5d680e84
NC
19 ('interne', 'Interne'),
20 ('externe', 'Externe'),
bd28238f
NC
21)
22
23POSTE_STATUT_CHOICES = (
24 ('MAD', 'Mise à disposition'),
25 ('DET', 'Détachement'),
26)
27
d766bf2c 28# Upload de fichiers
36341125
OL
29storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT, base_url=settings.PRIVE_MEDIA_URL)
30
31def poste_piece_dispatch(instance, filename):
fe13cd48 32 path = "poste/%s/%s" % (instance.poste_id, filename)
36341125
OL
33 return path
34
d766bf2c 35def dossier_piece_dispatch(instance, filename):
fe13cd48 36 path = "dossier/%s/%s" % (instance.dossier_id, filename)
d766bf2c
OL
37 return path
38
36341125
OL
39
40class PostePiece(models.Model):
41 poste = models.ForeignKey("Poste")
42 nom = models.CharField(verbose_name="Nom", max_length=255)
43 fichier = models.FileField(verbose_name="Fichier", upload_to=poste_piece_dispatch, storage=storage_prive)
44
e4f56614 45
8fa94e8b 46class Poste(PosteWorkflow, models.Model):
bd28238f 47 # Modèle existant
5d680e84 48 id_rh = models.ForeignKey(rh.Poste, null=True, related_name='+',
98d51b59
NC
49 editable=False,
50 verbose_name="Mise à jour du poste")
ce110fb9 51 nom = models.CharField(verbose_name="Titre du poste", max_length=255)
5d680e84
NC
52 implantation = models.ForeignKey(ref.Implantation)
53 type_poste = models.ForeignKey(rh.TypePoste, null=True, related_name='+')
98d51b59
NC
54 service = models.ForeignKey(rh.Service, related_name='+',
55 verbose_name=u"Direction/Service/Pôle support")
56 responsable = models.ForeignKey(rh.Poste, related_name='+',
5efcd48e 57 verbose_name="Poste du responsable")
9a85768a 58
5d680e84 59 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
5efcd48e 60 default=100,
61 verbose_name="Temps de travail",
62 help_text="% du temps complet")
5d680e84 63 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
5efcd48e 64 decimal_places=2,
5fe0ced5 65 default=35,
5efcd48e 66 verbose_name="Nb. heures par semaine")
bd28238f
NC
67
68 # Recrutement
154677c3
OL
69 local = models.BooleanField(verbose_name="Local", default=True, blank=True)
70 expatrie = models.BooleanField(verbose_name="Expatrié", default=False, blank=True)
71
5d680e84 72 # TODO null?
a3508c67 73 mise_a_disposition = models.BooleanField(verbose_name="Mise à disposition")
98d51b59
NC
74 appel = models.CharField(max_length=10, default='interne',
75 verbose_name="Appel à candidature",
5d680e84 76 choices=POSTE_APPEL_CHOICES)
bd28238f
NC
77
78 # Rémunération
5d680e84
NC
79 classement_min = models.ForeignKey(rh.Classement, related_name='+')
80 classement_max = models.ForeignKey(rh.Classement, related_name='+')
7dcb8d40
OL
81
82 # En fait, les coefficient n'ont pas de valeur dans ces cas, les couts sont calculés
83 # et mis dans les coûts globals
84 #coefficient_min = models.FloatField(null=True) # pour classement "hors grille"
85 #coefficient_max = models.FloatField(null=True) # pour classement "hors grille"
86
4dd75e7b
OL
87 valeur_point_min = models.ForeignKey(rh.ValeurPoint, related_name='+', blank=True, null=True)
88 valeur_point_max = models.ForeignKey(rh.ValeurPoint, related_name='+', blank=True, null=True)
3d627bfd 89 devise_min = models.ForeignKey(rh.Devise, default=5, related_name='+')
90 devise_max = models.ForeignKey(rh.Devise, default=5, related_name='+')
5d680e84
NC
91 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
92 default=0)
93 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
94 default=0)
95 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
96 default=0)
97 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
98 default=0)
99 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
100 default=0)
101 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
102 default=0)
103
104 # Comparatifs de rémunération
105 devise_comparaison = models.ForeignKey(rh.Devise, related_name='+',
a3508c67 106 default=5)
5d680e84
NC
107 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
108 null=True, blank=True)
109 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
110 null=True, blank=True)
111 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
112 null=True, blank=True)
113 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
114 null=True, blank=True)
115 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
116 null=True, blank=True)
117 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
118 null=True, blank=True)
119 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
120 null=True, blank=True)
121 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
122 null=True, blank=True)
123 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
124 null=True, blank=True)
125 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
126 null=True, blank=True)
bd28238f 127
2e092e0c
OL
128 # Justification
129 justification = models.TextField()
130
bd28238f 131 # Méta
5d680e84
NC
132 date_creation = models.DateTimeField(auto_now_add=True)
133 date_modification = models.DateTimeField(auto_now=True)
98d51b59 134 date_debut = models.DateField(verbose_name="Date de début",
9fb2ccd9 135 help_text="format: aaaa-mm-jj")
136 date_fin = models.DateField(null=True, blank=True,
137 verbose_name="Date de fin",
138 help_text="format: aaaa-mm-jj")
bd28238f
NC
139 actif = models.BooleanField(default=True)
140
1c7d67ce
OL
141 # Managers
142 objects = PosteManager()
143
03858ba5
OL
144 def _get_key(self):
145 """
146 Les vues sont montées selon une clef spéciale pour identifier la provenance du poste.
147 Cette méthode fournit un moyen de reconstruire cette clef afin de générer les URLs.
148 """
149 return "dae-%s" % self.id
150 key = property(_get_key)
151
f3333b0e
OL
152 def get_dossiers(self):
153 """
154 Liste tous les anciens dossiers liés à ce poste.
155 (Le nom de la relation sur le rh.Poste est mal choisi poste1 au lieu de dossier1)
156 Note1 : seulement le dosssier principal fait l'objet de la recherche.
157 Note2 : les dossiers sont retournés du plus récent au plus vieux. (Ce test est fait
158 en fonction du id, car les dates de création sont absentes de rh v1).
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 """
167 Inspecte les modèles rh v1 pour trouver dans le dernier dossier un complément de titre de poste.
168 """
169 dossiers = self.get_dossiers()
170 if len(dossiers) > 0:
171 nom = dossiers[0].complement1
172 else:
173 nom = ""
a9c281dd 174 return nom
f3333b0e
OL
175
176 def get_employe(self):
177 """
178 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
179 """
180 dossiers = self.get_dossiers()
181 if len(dossiers) > 0:
182 return dossiers[0].employe
183 else:
184 return None
185
179f6b49
OL
186 def get_default_devise(self):
187 """Récupère la devise par défaut en fonction de l'implantation (EUR autrement)"""
188 try:
189 implantation_devise = rh.TauxChange.objects.filter(implantation=self.implantation)[0].devise
190 except:
191 implantation_devise = 5 # EUR
192 return implantation_devise
193
c0413a6f
OL
194 #####################
195 # Classement de poste
196 #####################
197
198 def get_couts_minimum(self):
199 return (float)(self.salaire_min + self.indemn_min + self.autre_min)
200
201 def get_taux_minimum(self):
202 try:
203 return rh.TauxChange.objects.filter(implantation=self.implantation, devise=self.devise_min)[0].taux
204 except:
205 return 1
206
207 def get_couts_minimum_euros(self):
208 return self.get_couts_minimum() * self.get_taux_minimum()
209
210 def get_couts_maximum(self):
211 return (float)(self.salaire_max + self.indemn_max + self.autre_max)
212
213 def get_taux_maximum(self):
214 try:
215 return rh.TauxChange.objects.filter(implantation=self.implantation, devise=self.devise_max)[0].taux
216 except:
217 return 1
218
219 def get_couts_maximum_euros(self):
220 return self.get_couts_maximum() * self.get_taux_maximum()
221
222 ######################
223 # Comparaison de poste
224 ######################
225
226 def get_taux_comparaison(self):
227 try:
228 return rh.TauxChange.objects.filter(implantation=self.implantation, devise=self.devise_comparaison)[0].taux
229 except:
230 return 1
231
232 def get_comp_universite_min_euros(self):
233 return (float)(self.comp_universite_min) * self.get_taux_comparaison()
234
235 def get_comp_fonctionpub_min_euros(self):
236 return (float)(self.comp_fonctionpub_min) * self.get_taux_comparaison()
237
238 def get_comp_locale_min_euros(self):
239 return (float)(self.comp_locale_min) * self.get_taux_comparaison()
240
241 def get_comp_ong_min_euros(self):
242 return (float)(self.comp_ong_min) * self.get_taux_comparaison()
243
244 def get_comp_autre_min_euros(self):
245 return (float)(self.comp_autre_min) * self.get_taux_comparaison()
246
247 def get_comp_universite_max_euros(self):
248 return (float)(self.comp_universite_max) * self.get_taux_comparaison()
249
250 def get_comp_fonctionpub_max_euros(self):
251 return (float)(self.comp_fonctionpub_max) * self.get_taux_comparaison()
252
253 def get_comp_locale_max_euros(self):
254 return (float)(self.comp_locale_max) * self.get_taux_comparaison()
255
256 def get_comp_ong_max_euros(self):
257 return (float)(self.comp_ong_max) * self.get_taux_comparaison()
258
259 def get_comp_autre_max_euros(self):
260 return (float)(self.comp_autre_max) * self.get_taux_comparaison()
261
a9e52624 262
5d680e84 263 def __unicode__(self):
f3333b0e
OL
264 """
265 Cette fonction est consommatrice SQL car elle cherche les dossiers qui ont été liés à celui-ci.
266 """
267 complement_nom_poste = self.get_complement_nom()
c7a4aa2b
OL
268 if complement_nom_poste is None:
269 complement_nom_poste = ""
f3333b0e 270 employe = self.get_employe()
c7a4aa2b
OL
271 if employe is None:
272 employe = "VACANT"
f3333b0e
OL
273 data = (
274 self.implantation,
275 self.type_poste.nom,
276 self.nom,
277 self.id,
278 complement_nom_poste,
279 employe,
280 )
281 return u'%s - %s (%s) [dae-%s %s %s]' % data
5d680e84 282
bd28238f 283
a9c281dd
OL
284# Tester l'enregistrement car les models.py sont importés au complet
285if not reversion.is_registered(Poste):
286 reversion.register(Poste)
287
bd28238f 288
5d680e84
NC
289POSTE_FINANCEMENT_CHOICES = (
290 ('A', 'A - Frais de personnel'),
291 ('B', 'B - Projet(s)-Titre(s)'),
292 ('C', 'C - Autre')
293)
bd28238f
NC
294
295
5d680e84 296class PosteFinancement(models.Model):
5d680e84
NC
297 poste = models.ForeignKey('Poste', related_name='financements')
298 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
43d04712 299 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
300 help_text="ex.: 33.33 % (décimale avec point)")
301 commentaire = models.TextField(
302 help_text="Spécifiez la source de financement.")
bd28238f 303
43d04712 304 class Meta:
305 ordering = ['type']
bd28238f 306
c0413a6f
OL
307 def __unicode__(self):
308 return u"%s %s %s" % (self.get_type_display(), self.pourcentage, self.commentaire)
309
bd28238f 310GENRE_CHOICES = (
139686f2
NC
311 ('m', 'Homme'),
312 ('f', 'Femme'),
bd28238f
NC
313)
314
315
316class Employe(models.Model):
bd28238f
NC
317
318 # Modèle existant
da3ca955 319 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
320 verbose_name='Employé')
bd28238f 321 nom = models.CharField(max_length=255)
da3ca955 322 prenom = models.CharField(max_length=255, verbose_name='Prénom')
494ff2be
NC
323 genre = models.CharField(max_length=1, choices=GENRE_CHOICES,
324 null=True, blank=True)
bd28238f 325
139686f2
NC
326 def __unicode__(self):
327 return u'%s %s' % (self.prenom, self.nom)
328
bd28238f
NC
329
330COMPTE_COMPTA_CHOICES = (
494ff2be
NC
331 ('coda', 'CODA'),
332 ('scs', 'SCS'),
333 ('aucun', 'Aucun'),
bd28238f
NC
334)
335
d766bf2c
OL
336class DossierPiece(models.Model):
337 dossier = models.ForeignKey("Dossier")
338 nom = models.CharField(verbose_name="Nom", max_length=255)
339 fichier = models.FileField(verbose_name="Fichier", upload_to=dossier_piece_dispatch, storage=storage_prive)
bd28238f 340
e4f56614 341
afc204bf 342class Dossier(DossierWorkflow, models.Model):
bd28238f
NC
343
344 # Modèle existant
139686f2
NC
345 employe = models.ForeignKey('Employe', related_name='+', editable=False)
346 poste = models.ForeignKey('Poste', related_name='+', editable=False)
5d680e84 347 statut = models.ForeignKey(rh.Statut, related_name='+')
dfc2c344 348 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
1f109689 349 null=True, blank=True,
dfc2c344 350 verbose_name="Organisme",
351 help_text="Si détaché (DET) ou mis à disposition (MAD), \
352 préciser l'organisme.",
353 related_name='+')
0288adb5
OL
354 organisme_bstg_autre = models.CharField(max_length=255,
355 verbose_name="Autre organisme",
356 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
357 null=True,
358 blank=True,)
bd28238f 359
139686f2
NC
360 # Données antérieures de l'employé
361 statut_anterieur = models.ForeignKey(
362 rh.Statut, related_name='+', null=True, blank=True,
da3ca955 363 verbose_name='Statut antérieur')
139686f2
NC
364 classement_anterieur = models.ForeignKey(
365 rh.Classement, related_name='+', null=True, blank=True,
366 verbose_name='Classement précédent')
367 salaire_anterieur = models.DecimalField(
368 max_digits=12, decimal_places=2, null=True, default=None,
369 blank=True, verbose_name='Salaire précédent')
370
371 # Données du titulaire précédent
372 employe_anterieur = models.ForeignKey(
373 rh.Employe, related_name='+', null=True, blank=True,
374 verbose_name='Employé précédent')
375 statut_titulaire_anterieur = models.ForeignKey(
376 rh.Statut, related_name='+', null=True, blank=True,
377 verbose_name='Statut titulaire précédent')
378 classement_titulaire_anterieur = models.ForeignKey(
379 rh.Classement, related_name='+', null=True, blank=True,
380 verbose_name='Classement titulaire précédent')
381 salaire_titulaire_anterieur = models.DecimalField(
382 max_digits=12, decimal_places=2, default=None, null=True,
383 blank=True, verbose_name='Salaire titulaire précédent')
494ff2be 384
bd28238f
NC
385 # Recrutement
386 remplacement = models.BooleanField()
4d25e2ba 387 statut_residence = models.CharField(max_length=10, default='local',
388 verbose_name="Statut",
bd28238f
NC
389 choices=STATUT_RESIDENCE_CHOICES)
390
391 # Rémunération
494ff2be
NC
392 classement = models.ForeignKey(rh.Classement, related_name='+',
393 verbose_name='Classement proposé')
394 salaire = models.DecimalField(max_digits=12, decimal_places=2,
bd28238f 395 verbose_name='Salaire de base',
494ff2be 396 null=True, default=None)
e8e75458 397 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
7ad7408e 398 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
4d25e2ba 399 verbose_name="Régime de travail",
400 help_text="% du temps complet")
139686f2 401 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
4d25e2ba 402 decimal_places=2, verbose_name="Nb. heures par semaine")
bd28238f
NC
403
404 # Contrat
5d680e84 405 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
4d25e2ba 406 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
1f109689 407 contrat_date_fin = models.DateField(null=True, blank=True,
408 help_text="format: aaaa-mm-jj")
bd28238f
NC
409
410 # Comptes
dfc2c344 411 compte_compta = models.CharField(max_length=10, default='aucun',
412 verbose_name=u'Compte comptabilité',
413 choices=COMPTE_COMPTA_CHOICES)
bd28238f 414 compte_courriel = models.BooleanField()
0140cbd2 415
416 # Méta
417 date_creation = models.DateTimeField(auto_now_add=True)
e4f56614
OL
418
419 # Managers
420 objects = DossierManager()
421
aec2c91e 422 def __unicode__(self):
e4f56614 423 return u'[%s] %s - %s' % (self.poste.implantation, self.poste.nom, self.employe)
bd28238f 424
b1baa306
OL
425 def get_salaire_euros(self):
426 try:
427 tx = rh.TauxChange.objects.filter(implantation=self.poste.implantation, devise=self.devise)[0].taux
428 except:
429 tx = 1
430 return (float)(tx) * (float)(self.salaire)
431
57bd966c
OL
432 def get_couts_auf(self):
433 """
434 On retire les MAD BSTG
435 """
436 return [r for r in self.remuneration_set.all() if r.type_id not in (2, )]
a9e52624
OL
437
438 def get_total_couts_auf(self):
439 total = 0.0
440 for r in self.get_couts_auf():
441 total += r.montant_euro()
442 return total
57bd966c
OL
443
444 def get_aides_auf(self):
445 """
446 On récupère les MAD BSTG
447 """
448 return [r for r in self.remuneration_set.all() if r.type_id in (2, )]
449
a9e52624
OL
450 def get_total_aides_auf(self):
451 total = 0.0
452 for r in self.get_aides_auf():
453 total += r.montant_euro()
454 return total
455
456 def get_total_remun(self):
457 return self.get_total_couts_auf() + self.get_total_aides_auf()
458
0140cbd2 459# Tester l'enregistrement car les models.py sont importés au complet
460if not reversion.is_registered(Dossier):
461 reversion.register(Dossier)
bd28238f
NC
462
463class Remuneration(models.Model):
5d680e84 464 # Identification
bd28238f 465 dossier = models.ForeignKey('Dossier', db_column='dossier')
cb1d62b5
NC
466 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
467 related_name='+')
5d680e84
NC
468 # TODO: what's that?
469 # type_revalorisation = models.ForeignKey('TypeRevalorisation',
470 # db_column='type_revalorisation')
cb1d62b5
NC
471 montant = models.DecimalField(max_digits=12, decimal_places=2,
472 null=True) # Annuel
494ff2be 473 devise = models.ForeignKey(rh.Devise, to_field='code',
5d680e84 474 db_column='devise', related_name='+')
cb1d62b5
NC
475 precision = models.CharField(max_length=255, null=True, blank=True)
476 # date_effective = models.DateField(null=True, blank=True)
477 # pourcentage = models.IntegerField(null=True, blank=True)
bd28238f 478
5d680e84 479 # Méta
bd28238f 480 date_creation = models.DateField(auto_now_add=True)
494ff2be 481 user_creation = models.IntegerField(null=True, blank=True)
cb1d62b5
NC
482 # desactivation = models.BooleanField(default=False, blank=True)
483 # date_desactivation = models.DateField(null=True, blank=True)
484 # user_desactivation = models.IntegerField(null=True, blank=True)
485 # annulation = models.BooleanField(default=False, blank=True)
486 # date_annulation = models.DateField(null=True, blank=True)
487 # user_annulation = models.IntegerField(null=True, blank=True)
bd28238f 488
ee91bc95
NC
489 def montant_mois(self):
490 return round(self.montant / 12, 2)
491
492 def taux_devise(self):
493 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
494
495 def montant_euro(self):
a9e52624 496 return round(float(self.montant) * float(self.taux_devise()), 2)
ee91bc95
NC
497
498 def montant_euro_mois(self):
499 return round(self.montant_euro() / 12, 2)
bd28238f
NC
500
501
72db8238
OL
502TYPE_JUSTIFICATIONS = (
503 ('N', 'Nouvel employé'),
504 ('R', 'Renouvellement employé'),
505)
bd28238f 506
72db8238
OL
507class JustificationQuestion(models.Model):
508 question = models.CharField(max_length=255)
509 type = models.CharField(max_length=255, choices=TYPE_JUSTIFICATIONS)
510
511 def __unicode__(self,):
512 return self.question
bd28238f 513
72db8238
OL
514class JustificationNouvelEmploye(models.Model):
515 dossier = models.ForeignKey("Dossier")
516 question = models.ForeignKey("JustificationQuestion")
517 reponse = models.TextField()
bd28238f 518
72db8238
OL
519class JustificationAutreEmploye(models.Model):
520 dossier = models.ForeignKey("Dossier")
521 question = models.ForeignKey("JustificationQuestion")
522 reponse = models.TextField()