refact models (incomplet)
[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.filter(implantation=self.implantation)[0].devise
221 except:
222 implantation_devise = 5 # EUR
223 return implantation_devise
224
225 def __unicode__(self):
226 """
227 Cette fonction est consommatrice SQL car elle cherche les dossiers
228 qui ont été liés à celui-ci.
229 """
230 complement_nom_poste = self.get_complement_nom()
231 if complement_nom_poste is None:
232 complement_nom_poste = ""
233 employe = self.get_employe()
234 if employe is None:
235 employe = "VACANT"
236 data = (
237 self.implantation,
238 self.type_poste.nom,
239 self.nom,
240 self.id,
241 complement_nom_poste,
242 employe,
243 )
244 return u'%s - %s (%s) [dae-%s %s %s]' % data
245
246
247 # Tester l'enregistrement car les models.py sont importés au complet
248 if not reversion.is_registered(Poste):
249 reversion.register(Poste)
250
251
252 POSTE_FINANCEMENT_CHOICES = (
253 ('A', 'A - Frais de personnel'),
254 ('B', 'B - Projet(s)-Titre(s)'),
255 ('C', 'C - Autre')
256 )
257
258 class PosteFinancement(models.Model):
259 poste = models.ForeignKey('Poste', related_name='financements')
260 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
261 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
262 help_text="ex.: 33.33 % (décimale avec point)")
263 commentaire = models.TextField(
264 help_text="Spécifiez la source de financement.")
265
266 class Meta:
267 ordering = ['type']
268
269 class PostePiece(models.Model):
270 """Documents relatifs au Poste
271 Ex.: Description de poste
272 """
273 poste = models.ForeignKey("Poste")
274 nom = models.CharField(verbose_name="Nom", max_length=255)
275 fichier = models.FileField(verbose_name="Fichier",
276 upload_to=poste_piece_dispatch,
277 storage=storage_prive)
278
279 ### EMPLOYÉ/PERSONNE
280
281 # TODO : migration pour m -> M, f -> F
282 GENRE_CHOICES = (
283 ('m', 'Homme'),
284 ('f', 'Femme'),
285 )
286
287 class Employe(models.Model):
288
289 # Modèle existant
290 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
291 verbose_name='Employé')
292 nom = models.CharField(max_length=255)
293 prenom = models.CharField(max_length=255, verbose_name='Prénom')
294 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
295
296 def __unicode__(self):
297 return u'%s %s' % (self.prenom, self.nom.upper())
298
299
300 ### DOSSIER
301
302 STATUT_RESIDENCE_CHOICES = (
303 ('local', 'Local'),
304 ('expat', 'Expatrié'),
305 )
306
307 COMPTE_COMPTA_CHOICES = (
308 ('coda', 'CODA'),
309 ('scs', 'SCS'),
310 ('aucun', 'Aucun'),
311 )
312
313 class Dossier(models.Model):
314
315 # Modèle existant
316 employe = models.ForeignKey('Employe', related_name='+', editable=False)
317 poste = models.ForeignKey('Poste', related_name='+', editable=False)
318 statut = models.ForeignKey(rh.Statut, related_name='+')
319 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
320 null=True, blank=True,
321 verbose_name="Organisme",
322 help_text="Si détaché (DET) ou mis à disposition (MAD), \
323 préciser l'organisme.",
324 related_name='+')
325 organisme_bstg_autre = models.CharField(max_length=255,
326 verbose_name="Autre organisme",
327 help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
328 null=True,
329 blank=True,)
330
331 # Données antérieures de l'employé
332 statut_anterieur = models.ForeignKey(
333 rh.Statut, related_name='+', null=True, blank=True,
334 verbose_name='Statut antérieur')
335 classement_anterieur = models.ForeignKey(
336 rh.Classement, related_name='+', null=True, blank=True,
337 verbose_name='Classement précédent')
338 salaire_anterieur = models.DecimalField(
339 max_digits=12, decimal_places=2, null=True, default=None,
340 blank=True, verbose_name='Salaire précédent')
341
342 # Données du titulaire précédent
343 employe_anterieur = models.ForeignKey(
344 rh.Employe, related_name='+', null=True, blank=True,
345 verbose_name='Employé précédent')
346 statut_titulaire_anterieur = models.ForeignKey(
347 rh.Statut, related_name='+', null=True, blank=True,
348 verbose_name='Statut titulaire précédent')
349 classement_titulaire_anterieur = models.ForeignKey(
350 rh.Classement, related_name='+', null=True, blank=True,
351 verbose_name='Classement titulaire précédent')
352 salaire_titulaire_anterieur = models.DecimalField(
353 max_digits=12, decimal_places=2, default=None, null=True,
354 blank=True, verbose_name='Salaire titulaire précédent')
355
356 # Recrutement
357 remplacement = models.BooleanField()
358 statut_residence = models.CharField(max_length=10, default='local',
359 verbose_name="Statut",
360 choices=STATUT_RESIDENCE_CHOICES)
361
362 # Rémunération
363 classement = models.ForeignKey(rh.Classement, related_name='+',
364 null=True, blank=True,
365 verbose_name='Classement proposé')
366 salaire = models.DecimalField(max_digits=12, decimal_places=2,
367 verbose_name='Salaire de base',
368 null=True, default=None)
369 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
370 regime_travail = models.DecimalField(max_digits=12,
371 decimal_places=2,
372 default=REGIME_TRAVAIL_DEFAULT,
373 verbose_name="Régime de travail",
374 help_text="% du temps complet")
375 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
376 decimal_places=2,
377 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
378 verbose_name="Nb. heures par semaine")
379
380 # Contrat
381 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
382 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
383 contrat_date_fin = models.DateField(null=True, blank=True,
384 help_text="format: aaaa-mm-jj")
385
386 # Comptes
387 compte_compta = models.CharField(max_length=10, default='aucun',
388 verbose_name=u'Compte comptabilité',
389 choices=COMPTE_COMPTA_CHOICES)
390 compte_courriel = models.BooleanField()
391
392 # Méta
393 date_creation = models.DateTimeField(auto_now_add=True)
394
395 def __unicode__(self):
396 return u'%s - %s' % (self.poste.nom, self.employe)
397
398 # Tester l'enregistrement car les models.py sont importés au complet
399 if not reversion.is_registered(Dossier):
400 reversion.register(Dossier)
401
402 class DossierPiece(models.Model):
403 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
404 Ex.: Lettre de motivation.
405 """
406 dossier = models.ForeignKey("Dossier")
407 nom = models.CharField(verbose_name="Nom", max_length=255)
408 fichier = models.FileField(verbose_name="Fichier",
409 upload_to=dossier_piece_dispatch,
410 storage=storage_prive)
411
412
413 ### RÉMUNÉRATION
414
415 class Remuneration(models.Model):
416 # Identification
417 dossier = models.ForeignKey('Dossier', db_column='dossier')
418 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
419 related_name='+')
420 montant = models.DecimalField(max_digits=12, decimal_places=2,
421 null=True) # Annuel
422 devise = models.ForeignKey(rh.Devise, to_field='code',
423 db_column='devise', related_name='+')
424 precision = models.CharField(max_length=255, null=True, blank=True)
425
426 # Méta
427 date_creation = models.DateField(auto_now_add=True)
428 user_creation = models.IntegerField(null=True, blank=True) # TODO : user
429
430 def montant_mois(self):
431 return round(self.montant / 12, 2)
432
433 def taux_devise(self):
434 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
435
436 def montant_euro(self):
437 return round(float(self.montant) / float(self.taux_devise()), 2)
438
439 def montant_euro_mois(self):
440 return round(self.montant_euro() / 12, 2)
441
442
443 ### JUSTIFICATIONS
444
445 TYPE_JUSTIFICATIONS = (
446 ('N', 'Nouvel employé'),
447 ('R', 'Renouvellement employé'),
448 )
449
450 class JustificationQuestion(models.Model):
451 question = models.CharField(max_length=255)
452 type = models.CharField(max_length=255, choices=TYPE_JUSTIFICATIONS)
453
454 def __unicode__(self,):
455 return self.question
456
457 class JustificationNouvelEmploye(models.Model):
458 dossier = models.ForeignKey("Dossier")
459 question = models.ForeignKey("JustificationQuestion")
460 reponse = models.TextField()
461
462 class JustificationAutreEmploye(models.Model):
463 dossier = models.ForeignKey("Dossier")
464 question = models.ForeignKey("JustificationQuestion")
465 reponse = models.TextField()
466
467
468 ### VALIDATIONS
469
470 class Validation(models.Model):
471 # user
472 date = models.DateField()
473
474 # avis = ? (CHOICES?)
475
476 class ValidationPoste(models.Model):
477 poste = models.ForeignKey('Poste')
478
479 class ValidationEmploye(models.Model):
480 employe = models.ForeignKey('Employe')