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