model rh + admin import dans projet
[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
13 # Constantes
14 HELP_TEXT_DATE = "format: aaaa-mm-jj"
15 REGIME_TRAVAIL_DEFAULT=100.00
16 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT=35.00
17
18
19 # Upload de fichiers
20 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
21 base_url=settings.PRIVE_MEDIA_URL)
22
23 def poste_piece_dispatch(instance, filename):
24 path = "poste/%s/%s" % (instance.poste_id, filename)
25 return path
26
27 def dossier_piece_dispatch(instance, filename):
28 path = "dossier/%s/%s" % (instance.dossier_id, filename)
29 return path
30
31
32 ### POSTE
33
34 POSTE_APPEL_CHOICES = (
35 ('interne', 'Interne'),
36 ('externe', 'Externe'),
37 )
38
39 class PosteManager(models.Manager):
40 """
41 Chargement de tous les objets FK existants sur chaque QuerySet.
42 """
43 def get_query_set(self):
44 fkeys = (
45 'id_rh',
46 'responsable',
47 'implantation',
48 'type_poste',
49 'service',
50 'classement_min',
51 'classement_max',
52 'valeur_point_min',
53 'valeur_point_max',
54 )
55 return super(PosteManager, self).get_query_set() \
56 .select_related(*fkeys).all()
57
58
59 class Poste(PosteWorkflow, models.Model):
60 # Modèle existant
61 id_rh = models.ForeignKey(rh.Poste, null=True, related_name='+',
62 editable=False,
63 verbose_name="Mise à jour du poste")
64 nom = models.CharField(verbose_name="Titre du poste", max_length=255)
65 implantation = models.ForeignKey(ref.Implantation)
66 type_poste = models.ForeignKey(rh.TypePoste, null=True, related_name='+')
67 service = models.ForeignKey(rh.Service, related_name='+',
68 verbose_name=u"Direction/Service/Pôle support")
69 responsable = models.ForeignKey(rh.Poste, related_name='+',
70 verbose_name="Poste du responsable")
71
72 # Contrat
73 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
74 default=REGIME_TRAVAIL_DEFAULT,
75 verbose_name="Temps de travail",
76 help_text="% du temps complet")
77 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
78 decimal_places=2,
79 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
80 verbose_name="Nb. heures par semaine")
81
82 # Recrutement
83 local = models.BooleanField(verbose_name="Local", default=True, blank=True)
84 expatrie = models.BooleanField(verbose_name="Expatrié", default=False,
85 blank=True)
86 mise_a_disposition = models.BooleanField(verbose_name="Mise à disposition")
87 appel = models.CharField(max_length=10, default='interne',
88 verbose_name="Appel à candidature",
89 choices=POSTE_APPEL_CHOICES)
90
91 # Rémunération
92 classement_min = models.ForeignKey(rh.Classement, related_name='+',
93 blank=True, null=True)
94 classement_max = models.ForeignKey(rh.Classement, related_name='+',
95 blank=True, null=True)
96 valeur_point_min = models.ForeignKey(rh.ValeurPoint, related_name='+',
97 blank=True, null=True)
98 valeur_point_max = models.ForeignKey(rh.ValeurPoint, related_name='+',
99 blank=True, null=True)
100 devise_min = models.ForeignKey(rh.Devise, default=5, related_name='+')
101 devise_max = models.ForeignKey(rh.Devise, default=5, related_name='+')
102 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
103 default=0)
104 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
105 default=0)
106 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
107 default=0)
108 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
109 default=0)
110 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
111 default=0)
112 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
113 default=0)
114
115 # Comparatifs de rémunération
116 devise_comparaison = models.ForeignKey(rh.Devise, related_name='+',
117 default=5)
118 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
119 null=True, blank=True)
120 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
121 null=True, blank=True)
122 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
123 null=True, blank=True)
124 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
125 null=True, blank=True)
126 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
127 null=True, blank=True)
128 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
129 null=True, blank=True)
130 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
131 null=True, blank=True)
132 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
133 null=True, blank=True)
134 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
135 null=True, blank=True)
136 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
137 null=True, blank=True)
138
139 # Justification
140 justification = models.TextField()
141
142 # Validations
143 validation_bureau_regional = models.BooleanField(
144 verbose_name="Validation bureau régional")
145 validation_bureau_regional_date = models.DateField(blank=True, null=True)
146 validation_drh = models.BooleanField(verbose_name="Validation DRH")
147 validation_drh_date = models.DateField(blank=True, null=True)
148 validation_secretaire_general = models.BooleanField(
149 verbose_name="Validation secrétaire général")
150 validation_secretaire_general_date = models.DateField(blank=True,
151 null=True)
152 validation_recteur = models.BooleanField(verbose_name="Validation recteur")
153 validation_recteur_date = models.DateField(blank=True, null=True)
154
155 # Méta
156 date_creation = models.DateTimeField(auto_now_add=True)
157 date_modification = models.DateTimeField(auto_now=True)
158 date_debut = models.DateField(verbose_name="Date de début",
159 help_text=HELP_TEXT_DATE)
160 date_fin = models.DateField(null=True, blank=True,
161 verbose_name="Date de fin",
162 help_text=HELP_TEXT_DATE)
163 actif = models.BooleanField(default=True)
164
165 # Managers
166 objects = PosteManager()
167
168 def _get_key(self):
169 """
170 Les vues sont montées selon une clef spéciale
171 pour identifier la provenance du poste.
172 Cette méthode fournit un moyen de reconstruire cette clef
173 afin de générer les URLs.
174 """
175 return "dae-%s" % self.id
176 key = property(_get_key)
177
178 def get_dossiers(self):
179 """
180 Liste tous les anciens dossiers liés à ce poste.
181 (Le nom de la relation sur le rh.Poste est mal choisi
182 poste1 au lieu de dossier1)
183 Note1 : seulement le dosssier principal fait l'objet de la recherche.
184 Note2 : les dossiers sont retournés du plus récent au plus vieux.
185 (Ce test est fait en fonction du id,
186 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
196 un complément de titre de poste.
197 """
198 dossiers = self.get_dossiers()
199 if len(dossiers) > 0:
200 nom = dossiers[0].complement1
201 else:
202 nom = ""
203 return nom
204
205 def get_employe(self):
206 """
207 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
208 """
209 dossiers = self.get_dossiers()
210 if len(dossiers) > 0:
211 return dossiers[0].employe
212 else:
213 return None
214
215 def get_default_devise(self):
216 """Récupère la devise par défaut en fonction de l'implantation
217 (EUR autrement)
218 """
219 try:
220 implantation_devise = rh.TauxChange.objects \
221 .filter(implantation=self.implantation)[0].devise
222 except:
223 implantation_devise = 5 # EUR
224 return implantation_devise
225
226 def __unicode__(self):
227 """
228 Cette fonction est consommatrice SQL car elle cherche les dossiers
229 qui ont été liés à celui-ci.
230 """
231 complement_nom_poste = self.get_complement_nom()
232 if complement_nom_poste is None:
233 complement_nom_poste = ""
234 employe = self.get_employe()
235 if employe is None:
236 employe = "VACANT"
237 data = (
238 self.implantation,
239 self.type_poste.nom,
240 self.nom,
241 self.id,
242 complement_nom_poste,
243 employe,
244 )
245 return u'%s - %s (%s) [dae-%s %s %s]' % data
246
247
248 # Tester l'enregistrement car les models.py sont importés au complet
249 if not reversion.is_registered(Poste):
250 reversion.register(Poste)
251
252
253 POSTE_FINANCEMENT_CHOICES = (
254 ('A', 'A - Frais de personnel'),
255 ('B', 'B - Projet(s)-Titre(s)'),
256 ('C', 'C - Autre')
257 )
258
259 class PosteFinancement(models.Model):
260 poste = models.ForeignKey('Poste', related_name='financements')
261 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
262 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
263 help_text="ex.: 33.33 % (décimale avec point)")
264 commentaire = models.TextField(
265 help_text="Spécifiez la source de financement.")
266
267 class Meta:
268 ordering = ['type']
269
270 class PostePiece(models.Model):
271 """Documents relatifs au Poste
272 Ex.: Description de poste
273 """
274 poste = models.ForeignKey("Poste")
275 nom = models.CharField(verbose_name="Nom", max_length=255)
276 fichier = models.FileField(verbose_name="Fichier",
277 upload_to=poste_piece_dispatch,
278 storage=storage_prive)
279
280 ### EMPLOYÉ/PERSONNE
281
282 # TODO : migration pour m -> M, f -> F
283 GENRE_CHOICES = (
284 ('m', 'Homme'),
285 ('f', 'Femme'),
286 )
287
288 class Employe(models.Model):
289
290 # Modèle existant
291 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
292 verbose_name='Employé')
293 nom = models.CharField(max_length=255)
294 prenom = models.CharField(max_length=255, verbose_name='Prénom')
295 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
296
297 def __unicode__(self):
298 return u'%s %s' % (self.prenom, self.nom.upper())
299
300
301 ### DOSSIER
302
303 STATUT_RESIDENCE_CHOICES = (
304 ('local', 'Local'),
305 ('expat', 'Expatrié'),
306 )
307
308 COMPTE_COMPTA_CHOICES = (
309 ('coda', 'CODA'),
310 ('scs', 'SCS'),
311 ('aucun', 'Aucun'),
312 )
313
314 class Dossier(models.Model):
315
316 # Modèle existant
317 employe = models.ForeignKey('Employe', related_name='+', editable=False)
318 poste = models.ForeignKey('Poste', related_name='+', editable=False)
319 statut = models.ForeignKey(rh.Statut, related_name='+')
320 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
321 null=True, blank=True,
322 verbose_name="Organisme",
323 help_text="Si détaché (DET) ou mis à disposition (MAD), \
324 préciser l'organisme.",
325 related_name='+')
326 organisme_bstg_autre = models.CharField(max_length=255,
327 verbose_name="Autre organisme",
328 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
329 null=True,
330 blank=True,)
331
332 # Données antérieures de l'employé
333 statut_anterieur = models.ForeignKey(
334 rh.Statut, related_name='+', null=True, blank=True,
335 verbose_name='Statut antérieur')
336 classement_anterieur = models.ForeignKey(
337 rh.Classement, related_name='+', null=True, blank=True,
338 verbose_name='Classement précédent')
339 salaire_anterieur = models.DecimalField(
340 max_digits=12, decimal_places=2, null=True, default=None,
341 blank=True, verbose_name='Salaire précédent')
342
343 # Données du titulaire précédent
344 employe_anterieur = models.ForeignKey(
345 rh.Employe, related_name='+', null=True, blank=True,
346 verbose_name='Employé précédent')
347 statut_titulaire_anterieur = models.ForeignKey(
348 rh.Statut, related_name='+', null=True, blank=True,
349 verbose_name='Statut titulaire précédent')
350 classement_titulaire_anterieur = models.ForeignKey(
351 rh.Classement, related_name='+', null=True, blank=True,
352 verbose_name='Classement titulaire précédent')
353 salaire_titulaire_anterieur = models.DecimalField(
354 max_digits=12, decimal_places=2, default=None, null=True,
355 blank=True, verbose_name='Salaire titulaire précédent')
356
357 # Recrutement
358 remplacement = models.BooleanField()
359 statut_residence = models.CharField(max_length=10, default='local',
360 verbose_name="Statut",
361 choices=STATUT_RESIDENCE_CHOICES)
362
363 # Rémunération
364 classement = models.ForeignKey(rh.Classement, related_name='+',
365 null=True, blank=True,
366 verbose_name='Classement proposé')
367 salaire = models.DecimalField(max_digits=12, decimal_places=2,
368 verbose_name='Salaire de base',
369 null=True, default=None)
370 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
371 regime_travail = models.DecimalField(max_digits=12,
372 decimal_places=2,
373 default=REGIME_TRAVAIL_DEFAULT,
374 verbose_name="Régime de travail",
375 help_text="% du temps complet")
376 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
377 decimal_places=2,
378 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
379 verbose_name="Nb. heures par semaine")
380
381 # Contrat
382 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
383 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
384 contrat_date_fin = models.DateField(null=True, blank=True,
385 help_text="format: aaaa-mm-jj")
386
387 # Comptes
388 compte_compta = models.CharField(max_length=10, default='aucun',
389 verbose_name=u'Compte comptabilité',
390 choices=COMPTE_COMPTA_CHOICES)
391 compte_courriel = models.BooleanField()
392
393 # Méta
394 date_creation = models.DateTimeField(auto_now_add=True)
395
396 def __unicode__(self):
397 return u'%s - %s' % (self.poste.nom, self.employe)
398
399 # Tester l'enregistrement car les models.py sont importés au complet
400 if not reversion.is_registered(Dossier):
401 reversion.register(Dossier)
402
403 class DossierPiece(models.Model):
404 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
405 Ex.: Lettre de motivation.
406 """
407 dossier = models.ForeignKey("Dossier")
408 nom = models.CharField(verbose_name="Nom", max_length=255)
409 fichier = models.FileField(verbose_name="Fichier",
410 upload_to=dossier_piece_dispatch,
411 storage=storage_prive)
412
413
414 ### RÉMUNÉRATION
415
416 class Remuneration(models.Model):
417 # Identification
418 dossier = models.ForeignKey('Dossier', db_column='dossier')
419 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
420 related_name='+')
421 montant = models.DecimalField(max_digits=12, decimal_places=2,
422 null=True) # Annuel
423 devise = models.ForeignKey(rh.Devise, to_field='code',
424 db_column='devise', related_name='+')
425 precision = models.CharField(max_length=255, null=True, blank=True)
426
427 # Méta
428 date_creation = models.DateField(auto_now_add=True)
429 user_creation = models.IntegerField(null=True, blank=True) # TODO : user
430
431 def montant_mois(self):
432 return round(self.montant / 12, 2)
433
434 def taux_devise(self):
435 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
436
437 def montant_euro(self):
438 return round(float(self.montant) / float(self.taux_devise()), 2)
439
440 def montant_euro_mois(self):
441 return round(self.montant_euro() / 12, 2)
442
443
444 ### JUSTIFICATIONS
445
446 TYPE_JUSTIFICATIONS = (
447 ('N', 'Nouvel employé'),
448 ('R', 'Renouvellement employé'),
449 )
450
451 class JustificationQuestion(models.Model):
452 question = models.CharField(max_length=255)
453 type = models.CharField(max_length=255, choices=TYPE_JUSTIFICATIONS)
454
455 def __unicode__(self,):
456 return self.question
457
458 class JustificationNouvelEmploye(models.Model):
459 dossier = models.ForeignKey("Dossier")
460 question = models.ForeignKey("JustificationQuestion")
461 reponse = models.TextField()
462
463 class JustificationAutreEmploye(models.Model):
464 dossier = models.ForeignKey("Dossier")
465 question = models.ForeignKey("JustificationQuestion")
466 reponse = models.TextField()
467
468
469 ### VALIDATIONS
470
471 class Validation(models.Model):
472 # user
473 date = models.DateField()
474
475 # avis = ? (CHOICES?)
476
477 class ValidationPoste(models.Model):
478 poste = models.ForeignKey('Poste')
479
480 class ValidationEmploye(models.Model):
481 employe = models.ForeignKey('Employe')