rh.models refact
[auf_rh_dae.git] / project / dae / models.py
1 # -=- encoding: utf-8 -=-
2
3 import os
4 from django.core.files.storage import FileSystemStorage
5 from django.db import models
6 import reversion
7 from workflow import PosteWorkflow
8 import datamaster_modeles.models as ref
9 from rh_v1 import models as rh
10 import settings
11
12 STATUT_RESIDENCE_CHOICES = (
13 ('local', 'Local'),
14 ('expat', 'Expatrié'),
15 )
16
17 POSTE_APPEL_CHOICES = (
18 ('interne', 'Interne'),
19 ('externe', 'Externe'),
20 )
21
22 POSTE_STATUT_CHOICES = (
23 ('MAD', 'Mise à disposition'),
24 ('DET', 'Détachement'),
25 )
26
27 # Upload de fichiers
28 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT, base_url=settings.PRIVE_MEDIA_URL)
29
30 def poste_piece_dispatch(instance, filename):
31 path = "poste/%s/%s" % (instance.poste_id, filename)
32 return path
33
34 def dossier_piece_dispatch(instance, filename):
35 path = "dossier/%s/%s" % (instance.dossier_id, filename)
36 return path
37
38
39 class 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
44 class 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 )
60 return super(PosteManager, self).get_query_set() \
61 .select_related(*fkeys).all()
62
63
64 class Poste(PosteWorkflow, models.Model):
65 # Modèle existant
66 id_rh = models.ForeignKey(rh.Poste, null=True, related_name='+',
67 editable=False,
68 verbose_name="Mise à jour du poste")
69 nom = models.CharField(verbose_name="Titre du poste", max_length=255)
70 implantation = models.ForeignKey(ref.Implantation)
71 type_poste = models.ForeignKey(rh.TypePoste, null=True, related_name='+')
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='+',
75 verbose_name="Poste du responsable")
76
77 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
78 default=100,
79 verbose_name="Temps de travail",
80 help_text="% du temps complet")
81 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
82 decimal_places=2,
83 default=35,
84 verbose_name="Nb. heures par semaine")
85
86 # Recrutement
87 local = models.BooleanField(verbose_name="Local", default=True, blank=True)
88 expatrie = models.BooleanField(verbose_name="Expatrié", default=False, blank=True)
89
90 # TODO null?
91 mise_a_disposition = models.BooleanField(verbose_name="Mise à disposition")
92 appel = models.CharField(max_length=10, default='interne',
93 verbose_name="Appel à candidature",
94 choices=POSTE_APPEL_CHOICES)
95
96 # Rémunération
97 classement_min = models.ForeignKey(rh.Classement, related_name='+')
98 classement_max = models.ForeignKey(rh.Classement, related_name='+')
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
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)
107 devise_min = models.ForeignKey(rh.Devise, default=5, related_name='+')
108 devise_max = models.ForeignKey(rh.Devise, default=5, related_name='+')
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='+',
124 default=5)
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)
145
146 # Justification
147 justification = models.TextField()
148
149 # Validations
150 validation_bureau_regional = models.BooleanField(verbose_name="Validation bureau régional")
151 validation_bureau_regional_date = models.DateField(blank=True, null=True)
152 validation_drh = models.BooleanField(verbose_name="Validation DRH")
153 validation_drh_date = models.DateField(blank=True, null=True)
154 validation_secretaire_general = models.BooleanField(verbose_name="Validation secrétaire général")
155 validation_secretaire_general_date = models.DateField(blank=True, null=True)
156 validation_recteur = models.BooleanField(verbose_name="Validation recteur")
157 validation_recteur_date = models.DateField(blank=True, null=True)
158
159 # Méta
160 date_creation = models.DateTimeField(auto_now_add=True)
161 date_modification = models.DateTimeField(auto_now=True)
162 date_debut = models.DateField(verbose_name="Date de début",
163 help_text="format: aaaa-mm-jj")
164 date_fin = models.DateField(null=True, blank=True,
165 verbose_name="Date de fin",
166 help_text="format: aaaa-mm-jj")
167 actif = models.BooleanField(default=True)
168
169 # Managers
170 objects = PosteManager()
171
172 def _get_key(self):
173 """
174 Les vues sont montées selon une clef spéciale pour identifier la provenance du poste.
175 Cette méthode fournit un moyen de reconstruire cette clef afin de générer les URLs.
176 """
177 return "dae-%s" % self.id
178 key = property(_get_key)
179
180 def get_dossiers(self):
181 """
182 Liste tous les anciens dossiers liés à ce poste.
183 (Le nom de la relation sur le rh.Poste est mal choisi poste1 au lieu de dossier1)
184 Note1 : seulement le dosssier principal fait l'objet de la recherche.
185 Note2 : les dossiers sont retournés du plus récent au plus vieux. (Ce test est fait
186 en fonction du id, car les dates de création sont absentes de rh v1).
187 """
188 if self.id_rh is None:
189 return []
190 postes = [p for p in self.id_rh.poste1.all()]
191 return sorted(postes, key=lambda poste: poste.id, reverse=True)
192
193 def get_complement_nom(self):
194 """
195 Inspecte les modèles rh v1 pour trouver dans le dernier dossier un complément de titre de poste.
196 """
197 dossiers = self.get_dossiers()
198 if len(dossiers) > 0:
199 nom = dossiers[0].complement1
200 else:
201 nom = ""
202 return nom
203
204 def get_employe(self):
205 """
206 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
207 """
208 dossiers = self.get_dossiers()
209 if len(dossiers) > 0:
210 return dossiers[0].employe
211 else:
212 return None
213
214 def get_default_devise(self):
215 """Récupère la devise par défaut en fonction de l'implantation (EUR autrement)"""
216 try:
217 implantation_devise = rh.TauxChange.objects.filter(implantation=self.implantation)[0].devise
218 except:
219 implantation_devise = 5 # EUR
220 return implantation_devise
221
222 def __unicode__(self):
223 """
224 Cette fonction est consommatrice SQL car elle cherche les dossiers qui ont été liés à celui-ci.
225 """
226 complement_nom_poste = self.get_complement_nom()
227 if complement_nom_poste is None:
228 complement_nom_poste = ""
229 employe = self.get_employe()
230 if employe is None:
231 employe = "VACANT"
232 data = (
233 self.implantation,
234 self.type_poste.nom,
235 self.nom,
236 self.id,
237 complement_nom_poste,
238 employe,
239 )
240 return u'%s - %s (%s) [dae-%s %s %s]' % data
241
242
243 # Tester l'enregistrement car les models.py sont importés au complet
244 if not reversion.is_registered(Poste):
245 reversion.register(Poste)
246
247
248 POSTE_FINANCEMENT_CHOICES = (
249 ('A', 'A - Frais de personnel'),
250 ('B', 'B - Projet(s)-Titre(s)'),
251 ('C', 'C - Autre')
252 )
253
254
255 class PosteFinancement(models.Model):
256 poste = models.ForeignKey('Poste', related_name='financements')
257 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
258 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
259 help_text="ex.: 33.33 % (décimale avec point)")
260 commentaire = models.TextField(
261 help_text="Spécifiez la source de financement.")
262
263 class Meta:
264 ordering = ['type']
265
266 GENRE_CHOICES = (
267 ('m', 'Homme'),
268 ('f', 'Femme'),
269 )
270
271
272 class Employe(models.Model):
273
274 # Modèle existant
275 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
276 verbose_name='Employé')
277 nom = models.CharField(max_length=255)
278 prenom = models.CharField(max_length=255, verbose_name='Prénom')
279 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
280
281 def __unicode__(self):
282 return u'%s %s' % (self.prenom, self.nom)
283
284
285 COMPTE_COMPTA_CHOICES = (
286 ('coda', 'CODA'),
287 ('scs', 'SCS'),
288 ('aucun', 'Aucun'),
289 )
290
291 class DossierPiece(models.Model):
292 dossier = models.ForeignKey("Dossier")
293 nom = models.CharField(verbose_name="Nom", max_length=255)
294 fichier = models.FileField(verbose_name="Fichier", upload_to=dossier_piece_dispatch, storage=storage_prive)
295
296 class Dossier(models.Model):
297
298 # Modèle existant
299 employe = models.ForeignKey('Employe', related_name='+', editable=False)
300 poste = models.ForeignKey('Poste', related_name='+', editable=False)
301 statut = models.ForeignKey(rh.Statut, related_name='+')
302 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
303 null=True, blank=True,
304 verbose_name="Organisme",
305 help_text="Si détaché (DET) ou mis à disposition (MAD), \
306 préciser l'organisme.",
307 related_name='+')
308 organisme_bstg_autre = models.CharField(max_length=255,
309 verbose_name="Autre organisme",
310 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
311 null=True,
312 blank=True,)
313
314 # Données antérieures de l'employé
315 statut_anterieur = models.ForeignKey(
316 rh.Statut, related_name='+', null=True, blank=True,
317 verbose_name='Statut antérieur')
318 classement_anterieur = models.ForeignKey(
319 rh.Classement, related_name='+', null=True, blank=True,
320 verbose_name='Classement précédent')
321 salaire_anterieur = models.DecimalField(
322 max_digits=12, decimal_places=2, null=True, default=None,
323 blank=True, verbose_name='Salaire précédent')
324
325 # Données du titulaire précédent
326 employe_anterieur = models.ForeignKey(
327 rh.Employe, related_name='+', null=True, blank=True,
328 verbose_name='Employé précédent')
329 statut_titulaire_anterieur = models.ForeignKey(
330 rh.Statut, related_name='+', null=True, blank=True,
331 verbose_name='Statut titulaire précédent')
332 classement_titulaire_anterieur = models.ForeignKey(
333 rh.Classement, related_name='+', null=True, blank=True,
334 verbose_name='Classement titulaire précédent')
335 salaire_titulaire_anterieur = models.DecimalField(
336 max_digits=12, decimal_places=2, default=None, null=True,
337 blank=True, verbose_name='Salaire titulaire précédent')
338
339 # Recrutement
340 remplacement = models.BooleanField()
341 statut_residence = models.CharField(max_length=10, default='local',
342 verbose_name="Statut",
343 choices=STATUT_RESIDENCE_CHOICES)
344
345 # Rémunération
346 classement = models.ForeignKey(rh.Classement, related_name='+',
347 verbose_name='Classement proposé')
348 salaire = models.DecimalField(max_digits=12, decimal_places=2,
349 verbose_name='Salaire de base',
350 null=True, default=None)
351 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
352 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
353 verbose_name="Régime de travail",
354 help_text="% du temps complet")
355 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
356 decimal_places=2, verbose_name="Nb. heures par semaine")
357
358 # Contrat
359 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
360 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
361 contrat_date_fin = models.DateField(null=True, blank=True,
362 help_text="format: aaaa-mm-jj")
363
364 # Comptes
365 compte_compta = models.CharField(max_length=10, default='aucun',
366 verbose_name=u'Compte comptabilité',
367 choices=COMPTE_COMPTA_CHOICES)
368 compte_courriel = models.BooleanField()
369
370 # Méta
371 date_creation = models.DateTimeField(auto_now_add=True)
372
373 def __unicode__(self):
374 return u'%s - %s' % (self.poste.nom, self.employe)
375
376 # Tester l'enregistrement car les models.py sont importés au complet
377 if not reversion.is_registered(Dossier):
378 reversion.register(Dossier)
379
380 class Remuneration(models.Model):
381 # Identification
382 dossier = models.ForeignKey('Dossier', db_column='dossier')
383 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
384 related_name='+')
385 # TODO: what's that?
386 # type_revalorisation = models.ForeignKey('TypeRevalorisation',
387 # db_column='type_revalorisation')
388 montant = models.DecimalField(max_digits=12, decimal_places=2,
389 null=True) # Annuel
390 devise = models.ForeignKey(rh.Devise, to_field='code',
391 db_column='devise', related_name='+')
392 precision = models.CharField(max_length=255, null=True, blank=True)
393 # date_effective = models.DateField(null=True, blank=True)
394 # pourcentage = models.IntegerField(null=True, blank=True)
395
396 # Méta
397 date_creation = models.DateField(auto_now_add=True)
398 user_creation = models.IntegerField(null=True, blank=True)
399 # desactivation = models.BooleanField(default=False, blank=True)
400 # date_desactivation = models.DateField(null=True, blank=True)
401 # user_desactivation = models.IntegerField(null=True, blank=True)
402 # annulation = models.BooleanField(default=False, blank=True)
403 # date_annulation = models.DateField(null=True, blank=True)
404 # user_annulation = models.IntegerField(null=True, blank=True)
405
406 def montant_mois(self):
407 return round(self.montant / 12, 2)
408
409 def taux_devise(self):
410 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
411
412 def montant_euro(self):
413 return round(float(self.montant) / float(self.taux_devise()), 2)
414
415 def montant_euro_mois(self):
416 return round(self.montant_euro() / 12, 2)
417
418
419 TYPE_JUSTIFICATIONS = (
420 ('N', 'Nouvel employé'),
421 ('R', 'Renouvellement employé'),
422 )
423
424 class JustificationQuestion(models.Model):
425 question = models.CharField(max_length=255)
426 type = models.CharField(max_length=255, choices=TYPE_JUSTIFICATIONS)
427
428 def __unicode__(self,):
429 return self.question
430
431 class JustificationNouvelEmploye(models.Model):
432 dossier = models.ForeignKey("Dossier")
433 question = models.ForeignKey("JustificationQuestion")
434 reponse = models.TextField()
435
436 class JustificationAutreEmploye(models.Model):
437 dossier = models.ForeignKey("Dossier")
438 question = models.ForeignKey("JustificationQuestion")
439 reponse = models.TextField()
440
441
442 class Validation(models.Model):
443 # user
444 date = models.DateField()
445
446 # avis = ? (CHOICES?)
447
448
449 class ValidationPoste(models.Model):
450 poste = models.ForeignKey('Poste')
451
452
453 class ValidationEmploye(models.Model):
454 employe = models.ForeignKey('Employe')