model rh + admin import dans projet
[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 11
bd28238f 12
2d4d2fcf 13# Constantes
14HELP_TEXT_DATE = "format: aaaa-mm-jj"
15REGIME_TRAVAIL_DEFAULT=100.00
16REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT=35.00
bd28238f 17
bd28238f 18
d766bf2c 19# Upload de fichiers
2d4d2fcf 20storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
21 base_url=settings.PRIVE_MEDIA_URL)
36341125
OL
22
23def poste_piece_dispatch(instance, filename):
fe13cd48 24 path = "poste/%s/%s" % (instance.poste_id, filename)
36341125
OL
25 return path
26
d766bf2c 27def dossier_piece_dispatch(instance, filename):
fe13cd48 28 path = "dossier/%s/%s" % (instance.dossier_id, filename)
d766bf2c
OL
29 return path
30
36341125 31
2d4d2fcf 32### POSTE
33
34POSTE_APPEL_CHOICES = (
35 ('interne', 'Interne'),
36 ('externe', 'Externe'),
37)
36341125 38
1c7d67ce
OL
39class 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 )
98d51b59
NC
55 return super(PosteManager, self).get_query_set() \
56 .select_related(*fkeys).all()
1c7d67ce
OL
57
58
8fa94e8b 59class Poste(PosteWorkflow, models.Model):
bd28238f 60 # Modèle existant
5d680e84 61 id_rh = models.ForeignKey(rh.Poste, null=True, related_name='+',
2d4d2fcf 62 editable=False,
63 verbose_name="Mise à jour du poste")
ce110fb9 64 nom = models.CharField(verbose_name="Titre du poste", max_length=255)
5d680e84
NC
65 implantation = models.ForeignKey(ref.Implantation)
66 type_poste = models.ForeignKey(rh.TypePoste, null=True, related_name='+')
98d51b59 67 service = models.ForeignKey(rh.Service, related_name='+',
2d4d2fcf 68 verbose_name=u"Direction/Service/Pôle support")
98d51b59 69 responsable = models.ForeignKey(rh.Poste, related_name='+',
2d4d2fcf 70 verbose_name="Poste du responsable")
9a85768a 71
2d4d2fcf 72 # Contrat
5d680e84 73 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
2d4d2fcf 74 default=REGIME_TRAVAIL_DEFAULT,
75 verbose_name="Temps de travail",
76 help_text="% du temps complet")
5d680e84 77 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 78 decimal_places=2,
79 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
80 verbose_name="Nb. heures par semaine")
bd28238f
NC
81
82 # Recrutement
154677c3 83 local = models.BooleanField(verbose_name="Local", default=True, blank=True)
2d4d2fcf 84 expatrie = models.BooleanField(verbose_name="Expatrié", default=False,
85 blank=True)
a3508c67 86 mise_a_disposition = models.BooleanField(verbose_name="Mise à disposition")
98d51b59 87 appel = models.CharField(max_length=10, default='interne',
2d4d2fcf 88 verbose_name="Appel à candidature",
89 choices=POSTE_APPEL_CHOICES)
bd28238f
NC
90
91 # Rémunération
2d4d2fcf 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)
3d627bfd 100 devise_min = models.ForeignKey(rh.Devise, default=5, related_name='+')
101 devise_max = models.ForeignKey(rh.Devise, default=5, related_name='+')
5d680e84
NC
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='+',
a3508c67 117 default=5)
5d680e84
NC
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)
bd28238f 138
2e092e0c
OL
139 # Justification
140 justification = models.TextField()
141
a05cc82d 142 # Validations
2d4d2fcf 143 validation_bureau_regional = models.BooleanField(
144 verbose_name="Validation bureau régional")
a05cc82d
OL
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)
2d4d2fcf 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)
a05cc82d
OL
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",
2d4d2fcf 159 help_text=HELP_TEXT_DATE)
9fb2ccd9 160 date_fin = models.DateField(null=True, blank=True,
161 verbose_name="Date de fin",
2d4d2fcf 162 help_text=HELP_TEXT_DATE)
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 """
2d4d2fcf 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.
03858ba5
OL
174 """
175 return "dae-%s" % self.id
176 key = property(_get_key)
177
f3333b0e
OL
178 def get_dossiers(self):
179 """
180 Liste tous les anciens dossiers liés à ce poste.
2d4d2fcf 181 (Le nom de la relation sur le rh.Poste est mal choisi
182 poste1 au lieu de dossier1)
f3333b0e 183 Note1 : seulement le dosssier principal fait l'objet de la recherche.
2d4d2fcf 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).
f3333b0e
OL
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 """
2d4d2fcf 195 Inspecte les modèles rh v1 pour trouver dans le dernier dossier
196 un complément de titre de poste.
f3333b0e
OL
197 """
198 dossiers = self.get_dossiers()
199 if len(dossiers) > 0:
200 nom = dossiers[0].complement1
201 else:
202 nom = ""
a9c281dd 203 return nom
f3333b0e
OL
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
179f6b49 215 def get_default_devise(self):
2d4d2fcf 216 """Récupère la devise par défaut en fonction de l'implantation
217 (EUR autrement)
218 """
179f6b49 219 try:
6e4600ef 220 implantation_devise = rh.TauxChange.objects \
221 .filter(implantation=self.implantation)[0].devise
179f6b49
OL
222 except:
223 implantation_devise = 5 # EUR
224 return implantation_devise
225
5d680e84 226 def __unicode__(self):
f3333b0e 227 """
2d4d2fcf 228 Cette fonction est consommatrice SQL car elle cherche les dossiers
229 qui ont été liés à celui-ci.
f3333b0e
OL
230 """
231 complement_nom_poste = self.get_complement_nom()
c7a4aa2b
OL
232 if complement_nom_poste is None:
233 complement_nom_poste = ""
f3333b0e 234 employe = self.get_employe()
c7a4aa2b
OL
235 if employe is None:
236 employe = "VACANT"
f3333b0e
OL
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
5d680e84 246
bd28238f 247
a9c281dd
OL
248# Tester l'enregistrement car les models.py sont importés au complet
249if not reversion.is_registered(Poste):
250 reversion.register(Poste)
251
bd28238f 252
5d680e84
NC
253POSTE_FINANCEMENT_CHOICES = (
254 ('A', 'A - Frais de personnel'),
255 ('B', 'B - Projet(s)-Titre(s)'),
256 ('C', 'C - Autre')
257)
bd28238f 258
5d680e84 259class PosteFinancement(models.Model):
5d680e84
NC
260 poste = models.ForeignKey('Poste', related_name='financements')
261 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
43d04712 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.")
bd28238f 266
43d04712 267 class Meta:
268 ordering = ['type']
bd28238f 269
2d4d2fcf 270class 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
bd28238f 283GENRE_CHOICES = (
139686f2
NC
284 ('m', 'Homme'),
285 ('f', 'Femme'),
bd28238f
NC
286)
287
bd28238f 288class Employe(models.Model):
bd28238f
NC
289
290 # Modèle existant
da3ca955 291 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
292 verbose_name='Employé')
bd28238f 293 nom = models.CharField(max_length=255)
da3ca955 294 prenom = models.CharField(max_length=255, verbose_name='Prénom')
07b40eda 295 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
bd28238f 296
139686f2 297 def __unicode__(self):
2d4d2fcf 298 return u'%s %s' % (self.prenom, self.nom.upper())
139686f2 299
bd28238f 300
2d4d2fcf 301### DOSSIER
302
303STATUT_RESIDENCE_CHOICES = (
304 ('local', 'Local'),
305 ('expat', 'Expatrié'),
306)
307
bd28238f 308COMPTE_COMPTA_CHOICES = (
494ff2be
NC
309 ('coda', 'CODA'),
310 ('scs', 'SCS'),
311 ('aucun', 'Aucun'),
bd28238f
NC
312)
313
bd28238f 314class Dossier(models.Model):
bd28238f
NC
315
316 # Modèle existant
139686f2
NC
317 employe = models.ForeignKey('Employe', related_name='+', editable=False)
318 poste = models.ForeignKey('Poste', related_name='+', editable=False)
5d680e84 319 statut = models.ForeignKey(rh.Statut, related_name='+')
dfc2c344 320 organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
1f109689 321 null=True, blank=True,
dfc2c344 322 verbose_name="Organisme",
323 help_text="Si détaché (DET) ou mis à disposition (MAD), \
324 préciser l'organisme.",
325 related_name='+')
0288adb5
OL
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,)
bd28238f 331
139686f2
NC
332 # Données antérieures de l'employé
333 statut_anterieur = models.ForeignKey(
334 rh.Statut, related_name='+', null=True, blank=True,
da3ca955 335 verbose_name='Statut antérieur')
139686f2
NC
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')
494ff2be 356
bd28238f
NC
357 # Recrutement
358 remplacement = models.BooleanField()
4d25e2ba 359 statut_residence = models.CharField(max_length=10, default='local',
2d4d2fcf 360 verbose_name="Statut",
361 choices=STATUT_RESIDENCE_CHOICES)
bd28238f
NC
362
363 # Rémunération
494ff2be 364 classement = models.ForeignKey(rh.Classement, related_name='+',
2d4d2fcf 365 null=True, blank=True,
366 verbose_name='Classement proposé')
494ff2be 367 salaire = models.DecimalField(max_digits=12, decimal_places=2,
2d4d2fcf 368 verbose_name='Salaire de base',
369 null=True, default=None)
e8e75458 370 devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
2d4d2fcf 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")
139686f2 376 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 377 decimal_places=2,
378 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
379 verbose_name="Nb. heures par semaine")
bd28238f
NC
380
381 # Contrat
5d680e84 382 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
4d25e2ba 383 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
1f109689 384 contrat_date_fin = models.DateField(null=True, blank=True,
385 help_text="format: aaaa-mm-jj")
bd28238f
NC
386
387 # Comptes
dfc2c344 388 compte_compta = models.CharField(max_length=10, default='aucun',
389 verbose_name=u'Compte comptabilité',
390 choices=COMPTE_COMPTA_CHOICES)
bd28238f 391 compte_courriel = models.BooleanField()
0140cbd2 392
393 # Méta
394 date_creation = models.DateTimeField(auto_now_add=True)
aec2c91e 395
396 def __unicode__(self):
397 return u'%s - %s' % (self.poste.nom, self.employe)
bd28238f 398
0140cbd2 399# Tester l'enregistrement car les models.py sont importés au complet
400if not reversion.is_registered(Dossier):
401 reversion.register(Dossier)
bd28238f 402
2d4d2fcf 403class 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
bd28238f 416class Remuneration(models.Model):
5d680e84 417 # Identification
bd28238f 418 dossier = models.ForeignKey('Dossier', db_column='dossier')
cb1d62b5
NC
419 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
420 related_name='+')
cb1d62b5
NC
421 montant = models.DecimalField(max_digits=12, decimal_places=2,
422 null=True) # Annuel
494ff2be 423 devise = models.ForeignKey(rh.Devise, to_field='code',
5d680e84 424 db_column='devise', related_name='+')
cb1d62b5 425 precision = models.CharField(max_length=255, null=True, blank=True)
bd28238f 426
5d680e84 427 # Méta
bd28238f 428 date_creation = models.DateField(auto_now_add=True)
2d4d2fcf 429 user_creation = models.IntegerField(null=True, blank=True) # TODO : user
bd28238f 430
ee91bc95
NC
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)
bd28238f
NC
442
443
2d4d2fcf 444### JUSTIFICATIONS
445
72db8238
OL
446TYPE_JUSTIFICATIONS = (
447 ('N', 'Nouvel employé'),
448 ('R', 'Renouvellement employé'),
449)
bd28238f 450
72db8238
OL
451class 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
bd28238f 457
72db8238
OL
458class JustificationNouvelEmploye(models.Model):
459 dossier = models.ForeignKey("Dossier")
460 question = models.ForeignKey("JustificationQuestion")
461 reponse = models.TextField()
bd28238f 462
72db8238
OL
463class JustificationAutreEmploye(models.Model):
464 dossier = models.ForeignKey("Dossier")
465 question = models.ForeignKey("JustificationQuestion")
466 reponse = models.TextField()
bd28238f
NC
467
468
2d4d2fcf 469### VALIDATIONS
470
bd28238f
NC
471class Validation(models.Model):
472 # user
473 date = models.DateField()
474
475 # avis = ? (CHOICES?)
476
bd28238f
NC
477class ValidationPoste(models.Model):
478 poste = models.ForeignKey('Poste')
479
bd28238f
NC
480class ValidationEmploye(models.Model):
481 employe = models.ForeignKey('Employe')