Merge branch 'dev' of ssh://git.auf/auf_rh_dae into dev
[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 import datamaster_modeles.models as ref
8 from rh_v1 import models as rh
9 import settings
10
11 STATUT_RESIDENCE_CHOICES = (
12 ('local', 'Local'),
13 ('expat', 'Expatrié'),
14 )
15
16 POSTE_APPEL_CHOICES = (
17 ('interne', 'Interne'),
18 ('externe', 'Externe'),
19 )
20
21 POSTE_STATUT_CHOICES = (
22 ('MAD', 'Mise à disposition'),
23 ('DET', 'Détachement'),
24 )
25
26 # Upload de fichiers
27 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT, base_url=settings.PRIVE_MEDIA_URL)
28
29 def poste_piece_dispatch(instance, filename):
30 path = "poste/%s/%s" % (instance.poste.id, filename)
31 return path
32
33 def dossier_piece_dispatch(instance, filename):
34 path = "dossier/%s/%s" % (instance.dossier.id, filename)
35 return path
36
37
38 class PostePiece(models.Model):
39 poste = models.ForeignKey("Poste")
40 nom = models.CharField(verbose_name="Nom", max_length=255)
41 fichier = models.FileField(verbose_name="Fichier", upload_to=poste_piece_dispatch, storage=storage_prive)
42
43 class PosteManager(models.Manager):
44 """
45 Chargement de tous les objets FK existants sur chaque QuerySet.
46 """
47 def get_query_set(self):
48 fkeys = (
49 'id_rh',
50 'responsable',
51 'implantation',
52 'type_poste',
53 'service',
54 'classement_min',
55 'classement_max',
56 'valeur_point_min',
57 'valeur_point_max',
58 )
59 return super(PosteManager, self).get_query_set() \
60 .select_related(*fkeys).all()
61
62
63 class Poste(models.Model):
64 # Modèle existant
65 id_rh = models.ForeignKey(rh.Poste, null=True, related_name='+',
66 editable=False,
67 verbose_name="Mise à jour du poste")
68 nom = models.CharField(verbose_name="Titre du poste", max_length=255)
69 implantation = models.ForeignKey(ref.Implantation)
70 type_poste = models.ForeignKey(rh.TypePoste, null=True, related_name='+')
71 service = models.ForeignKey(rh.Service, related_name='+',
72 verbose_name=u"Direction/Service/Pôle support")
73 responsable = models.ForeignKey(rh.Poste, related_name='+',
74 verbose_name="Poste du responsable")
75
76 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
77 default=100,
78 verbose_name="Temps de travail",
79 help_text="% du temps complet")
80 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
81 decimal_places=2,
82 default=40,
83 verbose_name="Nb. heures par semaine")
84
85 # Recrutement
86 statut_residence = models.CharField(max_length=10, default='local',
87 verbose_name="Statut",
88 choices=STATUT_RESIDENCE_CHOICES)
89 # TODO null?
90 mise_a_disposition = models.BooleanField(verbose_name="Mise à disposition")
91 appel = models.CharField(max_length=10, default='interne',
92 verbose_name="Appel à candidature",
93 choices=POSTE_APPEL_CHOICES)
94
95 # Rémunération
96 classement_min = models.ForeignKey(rh.Classement, related_name='+')
97 classement_max = models.ForeignKey(rh.Classement, related_name='+')
98 coefficient_min = models.FloatField(null=True) # pour classement "hors grille"
99 coefficient_max = models.FloatField(null=True) # pour classement "hors grille"
100 valeur_point_min = models.ForeignKey(rh.ValeurPoint, related_name='+', blank=True, null=True)
101 valeur_point_max = models.ForeignKey(rh.ValeurPoint, related_name='+', blank=True, null=True)
102 devise_min = models.ForeignKey(rh.Devise, default=5, related_name='+')
103 devise_max = models.ForeignKey(rh.Devise, default=5, related_name='+')
104 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
105 default=0)
106 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
107 default=0)
108 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
109 default=0)
110 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
111 default=0)
112 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
113 default=0)
114 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
115 default=0)
116
117 # Comparatifs de rémunération
118 devise_comparaison = models.ForeignKey(rh.Devise, related_name='+',
119 default=5)
120 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
121 null=True, blank=True)
122 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
123 null=True, blank=True)
124 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
125 null=True, blank=True)
126 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
127 null=True, blank=True)
128 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
129 null=True, blank=True)
130 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
131 null=True, blank=True)
132 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
133 null=True, blank=True)
134 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
135 null=True, blank=True)
136 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
137 null=True, blank=True)
138 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
139 null=True, blank=True)
140
141 # Méta
142 date_creation = models.DateTimeField(auto_now_add=True)
143 date_modification = models.DateTimeField(auto_now=True)
144 date_debut = models.DateField(verbose_name="Date de début",
145 help_text="format: aaaa-mm-jj")
146 date_fin = models.DateField(null=True, blank=True,
147 verbose_name="Date de fin",
148 help_text="format: aaaa-mm-jj")
149 actif = models.BooleanField(default=True)
150
151 # Managers
152 objects = PosteManager()
153
154 def _get_key(self):
155 """
156 Les vues sont montées selon une clef spéciale pour identifier la provenance du poste.
157 Cette méthode fournit un moyen de reconstruire cette clef afin de générer les URLs.
158 """
159 return "dae-%s" % self.id
160 key = property(_get_key)
161
162 def get_dossiers(self):
163 """
164 Liste tous les anciens dossiers liés à ce poste.
165 (Le nom de la relation sur le rh.Poste est mal choisi poste1 au lieu de dossier1)
166 Note1 : seulement le dosssier principal fait l'objet de la recherche.
167 Note2 : les dossiers sont retournés du plus récent au plus vieux. (Ce test est fait
168 en fonction du id, car les dates de création sont absentes de rh v1).
169 """
170 if self.id_rh is None:
171 return []
172 postes = [p for p in self.id_rh.poste1.all()]
173 return sorted(postes, key=lambda poste: poste.id, reverse=True)
174
175 def get_complement_nom(self):
176 """
177 Inspecte les modèles rh v1 pour trouver dans le dernier dossier un complément de titre de poste.
178 """
179 dossiers = self.get_dossiers()
180 if len(dossiers) > 0:
181 nom = dossiers[0].complement1
182 else:
183 nom = ""
184 return nom
185
186 def get_employe(self):
187 """
188 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
189 """
190 dossiers = self.get_dossiers()
191 if len(dossiers) > 0:
192 return dossiers[0].employe
193 else:
194 return None
195
196 def __unicode__(self):
197 """
198 Cette fonction est consommatrice SQL car elle cherche les dossiers qui ont été liés à celui-ci.
199 """
200 complement_nom_poste = self.get_complement_nom()
201 if complement_nom_poste is None:
202 complement_nom_poste = ""
203 employe = self.get_employe()
204 if employe is None:
205 employe = "VACANT"
206 data = (
207 self.implantation,
208 self.type_poste.nom,
209 self.nom,
210 self.id,
211 complement_nom_poste,
212 employe,
213 )
214 return u'%s - %s (%s) [dae-%s %s %s]' % data
215
216
217 # Tester l'enregistrement car les models.py sont importés au complet
218 if not reversion.is_registered(Poste):
219 reversion.register(Poste)
220
221
222 POSTE_FINANCEMENT_CHOICES = (
223 ('A', 'A - Frais de personnel'),
224 ('B', 'B - Projet(s)-Titre(s)'),
225 ('C', 'C - Autre')
226 )
227
228
229 class PosteFinancement(models.Model):
230 montant = models.DecimalField(max_digits=12, decimal_places=2,
231 help_text="ex.: 12000.00 € (décimale avec point, devise = EUR)")
232 poste = models.ForeignKey('Poste', related_name='financements')
233 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
234 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
235 help_text="ex.: 33.33 % (décimale avec point)")
236 commentaire = models.TextField(
237 help_text="Spécifiez la source de financement.")
238
239 class Meta:
240 ordering = ['type']
241
242 GENRE_CHOICES = (
243 ('m', 'Homme'),
244 ('f', 'Femme'),
245 )
246
247
248 class Employe(models.Model):
249
250 # Modèle existant
251 id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
252 verbose_name='Employé')
253 nom = models.CharField(max_length=255)
254 prenom = models.CharField(max_length=255, verbose_name='Prénom')
255 genre = models.CharField(max_length=1, choices=GENRE_CHOICES,
256 null=True, blank=True)
257
258 def __unicode__(self):
259 return u'%s %s' % (self.prenom, self.nom)
260
261
262 COMPTE_COMPTA_CHOICES = (
263 ('coda', 'CODA'),
264 ('scs', 'SCS'),
265 ('aucun', 'Aucun'),
266 )
267
268 class DossierPiece(models.Model):
269 dossier = models.ForeignKey("Dossier")
270 nom = models.CharField(verbose_name="Nom", max_length=255)
271 fichier = models.FileField(verbose_name="Fichier", upload_to=dossier_piece_dispatch, storage=storage_prive)
272
273 class Dossier(models.Model):
274
275 # Modèle existant
276 employe = models.ForeignKey('Employe', related_name='+', editable=False)
277 poste = models.ForeignKey('Poste', related_name='+', editable=False)
278 statut = models.ForeignKey(rh.Statut, related_name='+')
279 organisme_bstg = models.ForeignKey(rh.OrganismeBstg, related_name='+')
280
281 # Données antérieures de l'employé
282 statut_anterieur = models.ForeignKey(
283 rh.Statut, related_name='+', null=True, blank=True,
284 verbose_name='Statut antérieur')
285 #type contrat
286 classement_anterieur = models.ForeignKey(
287 rh.Classement, related_name='+', null=True, blank=True,
288 verbose_name='Classement précédent')
289 salaire_anterieur = models.DecimalField(
290 max_digits=12, decimal_places=2, null=True, default=None,
291 blank=True, verbose_name='Salaire précédent')
292
293 # Données du titulaire précédent
294 employe_anterieur = models.ForeignKey(
295 rh.Employe, related_name='+', null=True, blank=True,
296 verbose_name='Employé précédent')
297 statut_titulaire_anterieur = models.ForeignKey(
298 rh.Statut, related_name='+', null=True, blank=True,
299 verbose_name='Statut titulaire précédent')
300 classement_titulaire_anterieur = models.ForeignKey(
301 rh.Classement, related_name='+', null=True, blank=True,
302 verbose_name='Classement titulaire précédent')
303 salaire_titulaire_anterieur = models.DecimalField(
304 max_digits=12, decimal_places=2, default=None, null=True,
305 blank=True, verbose_name='Salaire titulaire précédent')
306
307 # Recrutement
308 remplacement = models.BooleanField()
309 statut_residence = models.CharField(max_length=10, default='local',
310 verbose_name="Statut",
311 choices=STATUT_RESIDENCE_CHOICES)
312
313 # Rémunération
314 classement = models.ForeignKey(rh.Classement, related_name='+',
315 verbose_name='Classement proposé')
316 salaire = models.DecimalField(max_digits=12, decimal_places=2,
317 verbose_name='Salaire de base',
318 null=True, default=None)
319 devise = models.ForeignKey(rh.Devise, related_name='+')
320 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
321 verbose_name="Régime de travail",
322 help_text="% du temps complet")
323 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
324 decimal_places=2, verbose_name="Nb. heures par semaine")
325
326 # Contrat
327 type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
328 contrat_date_debut = models.DateField(help_text="format: aaaa-mm-jj")
329 contrat_date_fin = models.DateField(help_text="format: aaaa-mm-jj")
330
331 # Comptes
332 compte_compta = models.CharField(max_length=10,
333 choices=COMPTE_COMPTA_CHOICES)
334 compte_courriel = models.BooleanField()
335
336
337 class Remuneration(models.Model):
338 # Identification
339 dossier = models.ForeignKey('Dossier', db_column='dossier')
340 type = models.ForeignKey(rh.TypeRemuneration, db_column='type',
341 related_name='+')
342 # TODO: what's that?
343 # type_revalorisation = models.ForeignKey('TypeRevalorisation',
344 # db_column='type_revalorisation')
345 montant = models.DecimalField(max_digits=12, decimal_places=2,
346 null=True) # Annuel
347 devise = models.ForeignKey(rh.Devise, to_field='code',
348 db_column='devise', related_name='+')
349 precision = models.CharField(max_length=255, null=True, blank=True)
350 # date_effective = models.DateField(null=True, blank=True)
351 # pourcentage = models.IntegerField(null=True, blank=True)
352
353 # Méta
354 date_creation = models.DateField(auto_now_add=True)
355 user_creation = models.IntegerField(null=True, blank=True)
356 # desactivation = models.BooleanField(default=False, blank=True)
357 # date_desactivation = models.DateField(null=True, blank=True)
358 # user_desactivation = models.IntegerField(null=True, blank=True)
359 # annulation = models.BooleanField(default=False, blank=True)
360 # date_annulation = models.DateField(null=True, blank=True)
361 # user_annulation = models.IntegerField(null=True, blank=True)
362
363 def montant_mois(self):
364 return round(self.montant / 12, 2)
365
366 def taux_devise(self):
367 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
368
369 def montant_euro(self):
370 return round(float(self.montant) / float(self.taux_devise()), 2)
371
372 def montant_euro_mois(self):
373 return round(self.montant_euro() / 12, 2)
374
375
376 class JustificationPoste(models.Model):
377 pass
378
379
380 class JustificationEmploye(models.Model):
381 pass
382
383
384 class DocumentPoste(models.Model):
385 pass
386
387
388 class DocumentEmploye(models.Model):
389 pass
390
391
392 class Validation(models.Model):
393 # user
394 date = models.DateField()
395
396 # avis = ? (CHOICES?)
397
398
399 class ValidationPoste(models.Model):
400 poste = models.ForeignKey('Poste')
401
402
403 class ValidationEmploye(models.Model):
404 employe = models.ForeignKey('Employe')