recherche responsable super cool
[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
8fa94e8b 7from workflow import PosteWorkflow
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,
83 default=40,
84 verbose_name="Nb. heures par semaine")
bd28238f
NC
85
86 # Recrutement
4d25e2ba 87 statut_residence = models.CharField(max_length=10, default='local',
5efcd48e 88 verbose_name="Statut",
bd28238f 89 choices=STATUT_RESIDENCE_CHOICES)
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='+')
96d32304 99 coefficient_min = models.FloatField(null=True) # pour classement "hors grille"
100 coefficient_max = models.FloatField(null=True) # pour classement "hors grille"
4dd75e7b
OL
101 valeur_point_min = models.ForeignKey(rh.ValeurPoint, related_name='+', blank=True, null=True)
102 valeur_point_max = models.ForeignKey(rh.ValeurPoint, related_name='+', blank=True, null=True)
3d627bfd 103 devise_min = models.ForeignKey(rh.Devise, default=5, related_name='+')
104 devise_max = models.ForeignKey(rh.Devise, default=5, related_name='+')
5d680e84
NC
105 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
106 default=0)
107 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
108 default=0)
109 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
110 default=0)
111 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
112 default=0)
113 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
114 default=0)
115 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
116 default=0)
117
118 # Comparatifs de rémunération
119 devise_comparaison = models.ForeignKey(rh.Devise, related_name='+',
a3508c67 120 default=5)
5d680e84
NC
121 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
122 null=True, blank=True)
123 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
124 null=True, blank=True)
125 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
126 null=True, blank=True)
127 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
128 null=True, blank=True)
129 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
130 null=True, blank=True)
131 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
132 null=True, blank=True)
133 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
134 null=True, blank=True)
135 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
136 null=True, blank=True)
137 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
138 null=True, blank=True)
139 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
140 null=True, blank=True)
bd28238f 141
2e092e0c
OL
142 # Justification
143 justification = models.TextField()
144
a05cc82d
OL
145 # Validations
146 validation_bureau_regional = models.BooleanField(verbose_name="Validation bureau régional")
147 validation_bureau_regional_date = models.DateField(blank=True, null=True)
148 validation_drh = models.BooleanField(verbose_name="Validation DRH")
149 validation_drh_date = models.DateField(blank=True, null=True)
150 validation_secretaire_general = models.BooleanField(verbose_name="Validation secrétaire général")
151 validation_secretaire_general_date = models.DateField(blank=True, null=True)
152 validation_recteur = models.BooleanField(verbose_name="Validation recteur")
153 validation_recteur_date = models.DateField(blank=True, null=True)
154
bd28238f 155 # Méta
5d680e84
NC
156 date_creation = models.DateTimeField(auto_now_add=True)
157 date_modification = models.DateTimeField(auto_now=True)
98d51b59 158 date_debut = models.DateField(verbose_name="Date de début",
9fb2ccd9 159 help_text="format: aaaa-mm-jj")
160 date_fin = models.DateField(null=True, blank=True,
161 verbose_name="Date de fin",
162 help_text="format: aaaa-mm-jj")
bd28238f
NC
163 actif = models.BooleanField(default=True)
164
1c7d67ce
OL
165 # Managers
166 objects = PosteManager()
167
03858ba5
OL
168 def _get_key(self):
169 """
170 Les vues sont montées selon une clef spéciale pour identifier la provenance du poste.
171 Cette méthode fournit un moyen de reconstruire cette clef afin de générer les URLs.
172 """
173 return "dae-%s" % self.id
174 key = property(_get_key)
175
f3333b0e
OL
176 def get_dossiers(self):
177 """
178 Liste tous les anciens dossiers liés à ce poste.
179 (Le nom de la relation sur le rh.Poste est mal choisi poste1 au lieu de dossier1)
180 Note1 : seulement le dosssier principal fait l'objet de la recherche.
181 Note2 : les dossiers sont retournés du plus récent au plus vieux. (Ce test est fait
182 en fonction du id, car les dates de création sont absentes de rh v1).
183 """
184 if self.id_rh is None:
185 return []
186 postes = [p for p in self.id_rh.poste1.all()]
187 return sorted(postes, key=lambda poste: poste.id, reverse=True)
188
189 def get_complement_nom(self):
190 """
191 Inspecte les modèles rh v1 pour trouver dans le dernier dossier un complément de titre de poste.
192 """
193 dossiers = self.get_dossiers()
194 if len(dossiers) > 0:
195 nom = dossiers[0].complement1
196 else:
197 nom = ""
a9c281dd 198 return nom
f3333b0e
OL
199
200 def get_employe(self):
201 """
202 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
203 """
204 dossiers = self.get_dossiers()
205 if len(dossiers) > 0:
206 return dossiers[0].employe
207 else:
208 return None
209
179f6b49
OL
210 def get_default_devise(self):
211 """Récupère la devise par défaut en fonction de l'implantation (EUR autrement)"""
212 try:
213 implantation_devise = rh.TauxChange.objects.filter(implantation=self.implantation)[0].devise
214 except:
215 implantation_devise = 5 # EUR
216 return implantation_devise
217
5d680e84 218 def __unicode__(self):
f3333b0e
OL
219 """
220 Cette fonction est consommatrice SQL car elle cherche les dossiers qui ont été liés à celui-ci.
221 """
222 complement_nom_poste = self.get_complement_nom()
c7a4aa2b
OL
223 if complement_nom_poste is None:
224 complement_nom_poste = ""
f3333b0e 225 employe = self.get_employe()
c7a4aa2b
OL
226 if employe is None:
227 employe = "VACANT"
f3333b0e
OL
228 data = (
229 self.implantation,
230 self.type_poste.nom,
231 self.nom,
232 self.id,
233 complement_nom_poste,
234 employe,
235 )
236 return u'%s - %s (%s) [dae-%s %s %s]' % data
5d680e84 237
bd28238f 238
a9c281dd
OL
239# Tester l'enregistrement car les models.py sont importés au complet
240if not reversion.is_registered(Poste):
241 reversion.register(Poste)
242
bd28238f 243
5d680e84
NC
244POSTE_FINANCEMENT_CHOICES = (
245 ('A', 'A - Frais de personnel'),
246 ('B', 'B - Projet(s)-Titre(s)'),
247 ('C', 'C - Autre')
248)
bd28238f
NC
249
250
5d680e84 251class PosteFinancement(models.Model):
5d680e84
NC
252 poste = models.ForeignKey('Poste', related_name='financements')
253 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
43d04712 254 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
255 help_text="ex.: 33.33 % (décimale avec point)")
256 commentaire = models.TextField(
257 help_text="Spécifiez la source de financement.")
bd28238f 258
43d04712 259 class Meta:
260 ordering = ['type']
bd28238f
NC
261
262GENRE_CHOICES = (
139686f2
NC
263 ('m', 'Homme'),
264 ('f', 'Femme'),
bd28238f
NC
265)
266
267
268class Employe(models.Model):
bd28238f
NC
269
270 # Modèle existant
da3ca955 271 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
272 verbose_name='Employé')
bd28238f 273 nom = models.CharField(max_length=255)
da3ca955 274 prenom = models.CharField(max_length=255, verbose_name='Prénom')
494ff2be
NC
275 genre = models.CharField(max_length=1, choices=GENRE_CHOICES,
276 null=True, blank=True)
bd28238f 277
139686f2
NC
278 def __unicode__(self):
279 return u'%s %s' % (self.prenom, self.nom)
280
bd28238f
NC
281
282COMPTE_COMPTA_CHOICES = (
494ff2be
NC
283 ('coda', 'CODA'),
284 ('scs', 'SCS'),
285 ('aucun', 'Aucun'),
bd28238f
NC
286)
287
d766bf2c
OL
288class DossierPiece(models.Model):
289 dossier = models.ForeignKey("Dossier")
290 nom = models.CharField(verbose_name="Nom", max_length=255)
291 fichier = models.FileField(verbose_name="Fichier", upload_to=dossier_piece_dispatch, storage=storage_prive)
bd28238f
NC
292
293class Dossier(models.Model):
bd28238f
NC
294
295 # Modèle existant
139686f2
NC
296 employe = models.ForeignKey('Employe', related_name='+', editable=False)
297 poste = models.ForeignKey('Poste', related_name='+', editable=False)
5d680e84 298 statut = models.ForeignKey(rh.Statut, related_name='+')
dfc2c344 299 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
1f109689 300 null=True, blank=True,
dfc2c344 301 verbose_name="Organisme",
302 help_text="Si détaché (DET) ou mis à disposition (MAD), \
303 préciser l'organisme.",
304 related_name='+')
0288adb5
OL
305 organisme_bstg_autre = models.CharField(max_length=255,
306 verbose_name="Autre organisme",
307 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
308 null=True,
309 blank=True,)
bd28238f 310
139686f2
NC
311 # Données antérieures de l'employé
312 statut_anterieur = models.ForeignKey(
313 rh.Statut, related_name='+', null=True, blank=True,
da3ca955 314 verbose_name='Statut antérieur')
139686f2
NC
315 classement_anterieur = models.ForeignKey(
316 rh.Classement, related_name='+', null=True, blank=True,
317 verbose_name='Classement précédent')
318 salaire_anterieur = models.DecimalField(
319 max_digits=12, decimal_places=2, null=True, default=None,
320 blank=True, verbose_name='Salaire précédent')
321
322 # Données du titulaire précédent
323 employe_anterieur = models.ForeignKey(
324 rh.Employe, related_name='+', null=True, blank=True,
325 verbose_name='Employé précédent')
326 statut_titulaire_anterieur = models.ForeignKey(
327 rh.Statut, related_name='+', null=True, blank=True,
328 verbose_name='Statut titulaire précédent')
329 classement_titulaire_anterieur = models.ForeignKey(
330 rh.Classement, related_name='+', null=True, blank=True,
331 verbose_name='Classement titulaire précédent')
332 salaire_titulaire_anterieur = models.DecimalField(
333 max_digits=12, decimal_places=2, default=None, null=True,
334 blank=True, verbose_name='Salaire titulaire précédent')
494ff2be 335
bd28238f
NC
336 # Recrutement
337 remplacement = models.BooleanField()
4d25e2ba 338 statut_residence = models.CharField(max_length=10, default='local',
339 verbose_name="Statut",
bd28238f
NC
340 choices=STATUT_RESIDENCE_CHOICES)
341
342 # Rémunération
494ff2be
NC
343 classement = models.ForeignKey(rh.Classement, related_name='+',
344 verbose_name='Classement proposé')
345 salaire = models.DecimalField(max_digits=12, decimal_places=2,
bd28238f 346 verbose_name='Salaire de base',
494ff2be 347 null=True, default=None)
e8e75458 348 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
7ad7408e 349 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
4d25e2ba 350 verbose_name="Régime de travail",
351 help_text="% du temps complet")
139686f2 352 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
4d25e2ba 353 decimal_places=2, verbose_name="Nb. heures par semaine")
bd28238f
NC
354
355 # Contrat
5d680e84 356 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
4d25e2ba 357 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
1f109689 358 contrat_date_fin = models.DateField(null=True, blank=True,
359 help_text="format: aaaa-mm-jj")
bd28238f
NC
360
361 # Comptes
dfc2c344 362 compte_compta = models.CharField(max_length=10, default='aucun',
363 verbose_name=u'Compte comptabilité',
364 choices=COMPTE_COMPTA_CHOICES)
bd28238f 365 compte_courriel = models.BooleanField()
0140cbd2 366
367 # Méta
368 date_creation = models.DateTimeField(auto_now_add=True)
aec2c91e 369
370 def __unicode__(self):
371 return u'%s - %s' % (self.poste.nom, self.employe)
bd28238f 372
0140cbd2 373# Tester l'enregistrement car les models.py sont importés au complet
374if not reversion.is_registered(Dossier):
375 reversion.register(Dossier)
bd28238f
NC
376
377class Remuneration(models.Model):
5d680e84 378 # Identification
bd28238f 379 dossier = models.ForeignKey('Dossier', db_column='dossier')
cb1d62b5
NC
380 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
381 related_name='+')
5d680e84
NC
382 # TODO: what's that?
383 # type_revalorisation = models.ForeignKey('TypeRevalorisation',
384 # db_column='type_revalorisation')
cb1d62b5
NC
385 montant = models.DecimalField(max_digits=12, decimal_places=2,
386 null=True) # Annuel
494ff2be 387 devise = models.ForeignKey(rh.Devise, to_field='code',
5d680e84 388 db_column='devise', related_name='+')
cb1d62b5
NC
389 precision = models.CharField(max_length=255, null=True, blank=True)
390 # date_effective = models.DateField(null=True, blank=True)
391 # pourcentage = models.IntegerField(null=True, blank=True)
bd28238f 392
5d680e84 393 # Méta
bd28238f 394 date_creation = models.DateField(auto_now_add=True)
494ff2be 395 user_creation = models.IntegerField(null=True, blank=True)
cb1d62b5
NC
396 # desactivation = models.BooleanField(default=False, blank=True)
397 # date_desactivation = models.DateField(null=True, blank=True)
398 # user_desactivation = models.IntegerField(null=True, blank=True)
399 # annulation = models.BooleanField(default=False, blank=True)
400 # date_annulation = models.DateField(null=True, blank=True)
401 # user_annulation = models.IntegerField(null=True, blank=True)
bd28238f 402
ee91bc95
NC
403 def montant_mois(self):
404 return round(self.montant / 12, 2)
405
406 def taux_devise(self):
407 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
408
409 def montant_euro(self):
410 return round(float(self.montant) / float(self.taux_devise()), 2)
411
412 def montant_euro_mois(self):
413 return round(self.montant_euro() / 12, 2)
bd28238f
NC
414
415
72db8238
OL
416TYPE_JUSTIFICATIONS = (
417 ('N', 'Nouvel employé'),
418 ('R', 'Renouvellement employé'),
419)
bd28238f 420
72db8238
OL
421class JustificationQuestion(models.Model):
422 question = models.CharField(max_length=255)
423 type = models.CharField(max_length=255, choices=TYPE_JUSTIFICATIONS)
424
425 def __unicode__(self,):
426 return self.question
bd28238f 427
72db8238
OL
428class JustificationNouvelEmploye(models.Model):
429 dossier = models.ForeignKey("Dossier")
430 question = models.ForeignKey("JustificationQuestion")
431 reponse = models.TextField()
bd28238f 432
72db8238
OL
433class JustificationAutreEmploye(models.Model):
434 dossier = models.ForeignKey("Dossier")
435 question = models.ForeignKey("JustificationQuestion")
436 reponse = models.TextField()
bd28238f
NC
437
438
439class Validation(models.Model):
440 # user
441 date = models.DateField()
442
443 # avis = ? (CHOICES?)
444
445
446class ValidationPoste(models.Model):
447 poste = models.ForeignKey('Poste')
448
449
450class ValidationEmploye(models.Model):
451 employe = models.ForeignKey('Employe')