1 # -=- encoding: utf-8 -=-
4 from django
.conf
import settings
5 from django
.core
.files
.storage
import FileSystemStorage
6 from django
.db
import models
8 from workflow
import PosteWorkflow
, DossierWorkflow
9 from managers
import DossierManager
, PosteManager
10 import datamaster_modeles
.models
as ref
11 from rh_v1
import models
as rh
13 STATUT_RESIDENCE_CHOICES
= (
15 ('expat', 'Expatrié'),
18 POSTE_APPEL_CHOICES
= (
19 ('interne', 'Interne'),
20 ('externe', 'Externe'),
23 POSTE_STATUT_CHOICES
= (
24 ('MAD', 'Mise à disposition'),
25 ('DET', 'Détachement'),
29 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
, base_url
=settings
.PRIVE_MEDIA_URL
)
31 def poste_piece_dispatch(instance
, filename
):
32 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
35 def dossier_piece_dispatch(instance
, filename
):
36 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
40 class PostePiece(models
.Model
):
41 poste
= models
.ForeignKey("Poste")
42 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
43 fichier
= models
.FileField(verbose_name
="Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
)
46 class Poste(PosteWorkflow
, models
.Model
):
48 id_rh
= models
.ForeignKey(rh
.Poste
, null
=True, related_name
='+',
50 verbose_name
="Mise à jour du poste")
51 nom
= models
.CharField(verbose_name
="Titre du poste", max_length
=255)
52 implantation
= models
.ForeignKey(ref
.Implantation
)
53 type_poste
= models
.ForeignKey(rh
.TypePoste
, null
=True, related_name
='+')
54 service
= models
.ForeignKey(rh
.Service
, related_name
='+',
55 verbose_name
=u
"Direction/Service/Pôle support")
56 responsable
= models
.ForeignKey(rh
.Poste
, related_name
='+',
57 verbose_name
="Poste du responsable")
59 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
61 verbose_name
="Temps de travail",
62 help_text
="% du temps complet")
63 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
66 verbose_name
="Nb. heures par semaine")
69 local
= models
.BooleanField(verbose_name
="Local", default
=True, blank
=True)
70 expatrie
= models
.BooleanField(verbose_name
="Expatrié", default
=False, blank
=True)
73 mise_a_disposition
= models
.BooleanField(verbose_name
="Mise à disposition")
74 appel
= models
.CharField(max_length
=10, default
='interne',
75 verbose_name
="Appel à candidature",
76 choices
=POSTE_APPEL_CHOICES
)
79 classement_min
= models
.ForeignKey(rh
.Classement
, related_name
='+')
80 classement_max
= models
.ForeignKey(rh
.Classement
, related_name
='+')
82 # En fait, les coefficient n'ont pas de valeur dans ces cas, les couts sont calculés
83 # et mis dans les coûts globals
84 #coefficient_min = models.FloatField(null=True) # pour classement "hors grille"
85 #coefficient_max = models.FloatField(null=True) # pour classement "hors grille"
87 valeur_point_min
= models
.ForeignKey(rh
.ValeurPoint
, related_name
='+', blank
=True, null
=True)
88 valeur_point_max
= models
.ForeignKey(rh
.ValeurPoint
, related_name
='+', blank
=True, null
=True)
89 devise_min
= models
.ForeignKey(rh
.Devise
, default
=5, related_name
='+')
90 devise_max
= models
.ForeignKey(rh
.Devise
, default
=5, related_name
='+')
91 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
93 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
95 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
97 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
99 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
101 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
104 # Comparatifs de rémunération
105 devise_comparaison
= models
.ForeignKey(rh
.Devise
, related_name
='+',
107 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
108 null
=True, blank
=True)
109 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
110 null
=True, blank
=True)
111 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
112 null
=True, blank
=True)
113 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
114 null
=True, blank
=True)
115 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
116 null
=True, blank
=True)
117 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
118 null
=True, blank
=True)
119 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
120 null
=True, blank
=True)
121 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
122 null
=True, blank
=True)
123 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
124 null
=True, blank
=True)
125 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
126 null
=True, blank
=True)
129 justification
= models
.TextField()
132 date_creation
= models
.DateTimeField(auto_now_add
=True)
133 date_modification
= models
.DateTimeField(auto_now
=True)
134 date_debut
= models
.DateField(verbose_name
="Date de début",
135 help_text
="format: aaaa-mm-jj")
136 date_fin
= models
.DateField(null
=True, blank
=True,
137 verbose_name
="Date de fin",
138 help_text
="format: aaaa-mm-jj")
139 actif
= models
.BooleanField(default
=True)
142 objects
= PosteManager()
146 Les vues sont montées selon une clef spéciale pour identifier la provenance du poste.
147 Cette méthode fournit un moyen de reconstruire cette clef afin de générer les URLs.
149 return "dae-%s" % self
.id
150 key
= property(_get_key
)
152 def get_dossiers(self
):
154 Liste tous les anciens dossiers liés à ce poste.
155 (Le nom de la relation sur le rh.Poste est mal choisi poste1 au lieu de dossier1)
156 Note1 : seulement le dosssier principal fait l'objet de la recherche.
157 Note2 : les dossiers sont retournés du plus récent au plus vieux. (Ce test est fait
158 en fonction du id, car les dates de création sont absentes de rh v1).
160 if self
.id_rh
is None:
162 postes
= [p
for p
in self
.id_rh
.poste1
.all()]
163 return sorted(postes
, key
=lambda poste
: poste
.id, reverse
=True)
165 def get_complement_nom(self
):
167 Inspecte les modèles rh v1 pour trouver dans le dernier dossier un complément de titre de poste.
169 dossiers
= self
.get_dossiers()
170 if len(dossiers
) > 0:
171 nom
= dossiers
[0].complement1
176 def get_employe(self
):
178 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
180 dossiers
= self
.get_dossiers()
181 if len(dossiers
) > 0:
182 return dossiers
[0].employe
186 def get_default_devise(self
):
187 """Récupère la devise par défaut en fonction de l'implantation (EUR autrement)"""
189 implantation_devise
= rh
.TauxChange
.objects
.filter(implantation
=self
.implantation
)[0].devise
191 implantation_devise
= 5 # EUR
192 return implantation_devise
194 #####################
195 # Classement de poste
196 #####################
198 def get_couts_minimum(self
):
199 return (float)(self
.salaire_min
+ self
.indemn_min
+ self
.autre_min
)
201 def get_taux_minimum(self
):
203 return rh
.TauxChange
.objects
.filter(implantation
=self
.implantation
, devise
=self
.devise_min
)[0].taux
207 def get_couts_minimum_euros(self
):
208 return self
.get_couts_minimum() * self
.get_taux_minimum()
210 def get_couts_maximum(self
):
211 return (float)(self
.salaire_max
+ self
.indemn_max
+ self
.autre_max
)
213 def get_taux_maximum(self
):
215 return rh
.TauxChange
.objects
.filter(implantation
=self
.implantation
, devise
=self
.devise_max
)[0].taux
219 def get_couts_maximum_euros(self
):
220 return self
.get_couts_maximum() * self
.get_taux_maximum()
222 ######################
223 # Comparaison de poste
224 ######################
226 def get_taux_comparaison(self
):
228 return rh
.TauxChange
.objects
.filter(implantation
=self
.implantation
, devise
=self
.devise_comparaison
)[0].taux
232 def get_comp_universite_min_euros(self
):
233 return (float)(self
.comp_universite_min
) * self
.get_taux_comparaison()
235 def get_comp_fonctionpub_min_euros(self
):
236 return (float)(self
.comp_fonctionpub_min
) * self
.get_taux_comparaison()
238 def get_comp_locale_min_euros(self
):
239 return (float)(self
.comp_locale_min
) * self
.get_taux_comparaison()
241 def get_comp_ong_min_euros(self
):
242 return (float)(self
.comp_ong_min
) * self
.get_taux_comparaison()
244 def get_comp_autre_min_euros(self
):
245 return (float)(self
.comp_autre_min
) * self
.get_taux_comparaison()
247 def get_comp_universite_max_euros(self
):
248 return (float)(self
.comp_universite_max
) * self
.get_taux_comparaison()
250 def get_comp_fonctionpub_max_euros(self
):
251 return (float)(self
.comp_fonctionpub_max
) * self
.get_taux_comparaison()
253 def get_comp_locale_max_euros(self
):
254 return (float)(self
.comp_locale_max
) * self
.get_taux_comparaison()
256 def get_comp_ong_max_euros(self
):
257 return (float)(self
.comp_ong_max
) * self
.get_taux_comparaison()
259 def get_comp_autre_max_euros(self
):
260 return (float)(self
.comp_autre_max
) * self
.get_taux_comparaison()
263 def __unicode__(self
):
265 Cette fonction est consommatrice SQL car elle cherche les dossiers qui ont été liés à celui-ci.
267 complement_nom_poste
= self
.get_complement_nom()
268 if complement_nom_poste
is None:
269 complement_nom_poste
= ""
270 employe
= self
.get_employe()
278 complement_nom_poste
,
281 return u
'%s - %s (%s) [dae-%s %s %s]' % data
284 # Tester l'enregistrement car les models.py sont importés au complet
285 if not reversion
.is_registered(Poste
):
286 reversion
.register(Poste
)
289 POSTE_FINANCEMENT_CHOICES
= (
290 ('A', 'A - Frais de personnel'),
291 ('B', 'B - Projet(s)-Titre(s)'),
296 class PosteFinancement(models
.Model
):
297 poste
= models
.ForeignKey('Poste', related_name
='financements')
298 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
299 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
300 help_text
="ex.: 33.33 % (décimale avec point)")
301 commentaire
= models
.TextField(
302 help_text
="Spécifiez la source de financement.")
307 def __unicode__(self
):
308 return u
"%s %s %s" % (self
.get_type_display(), self
.pourcentage
, self
.commentaire
)
316 class Employe(models
.Model
):
319 id_rh
= models
.ForeignKey(rh
.Employe
, null
=True, related_name
='+',
320 verbose_name
='Employé')
321 nom
= models
.CharField(max_length
=255)
322 prenom
= models
.CharField(max_length
=255, verbose_name
='Prénom')
323 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
,
324 null
=True, blank
=True)
326 def __unicode__(self
):
327 return u
'%s %s' % (self
.prenom
, self
.nom
)
330 COMPTE_COMPTA_CHOICES
= (
336 class DossierPiece(models
.Model
):
337 dossier
= models
.ForeignKey("Dossier")
338 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
339 fichier
= models
.FileField(verbose_name
="Fichier", upload_to
=dossier_piece_dispatch
, storage
=storage_prive
)
342 class Dossier(DossierWorkflow
, models
.Model
):
345 employe
= models
.ForeignKey('Employe', related_name
='+', editable
=False)
346 poste
= models
.ForeignKey('Poste', related_name
='+', editable
=False)
347 statut
= models
.ForeignKey(rh
.Statut
, related_name
='+')
348 organisme_bstg
= models
.ForeignKey(rh
.OrganismeBstg
,
349 null
=True, blank
=True,
350 verbose_name
="Organisme",
351 help_text
="Si détaché (DET) ou mis à disposition (MAD), \
352 préciser l'organisme.",
354 organisme_bstg_autre
= models
.CharField(max_length
=255,
355 verbose_name
="Autre organisme",
356 help_text
="indiquer l'organisme ici s'il n'est pas dans la liste",
360 # Données antérieures de l'employé
361 statut_anterieur
= models
.ForeignKey(
362 rh
.Statut
, related_name
='+', null
=True, blank
=True,
363 verbose_name
='Statut antérieur')
364 classement_anterieur
= models
.ForeignKey(
365 rh
.Classement
, related_name
='+', null
=True, blank
=True,
366 verbose_name
='Classement précédent')
367 salaire_anterieur
= models
.DecimalField(
368 max_digits
=12, decimal_places
=2, null
=True, default
=None,
369 blank
=True, verbose_name
='Salaire précédent')
371 # Données du titulaire précédent
372 employe_anterieur
= models
.ForeignKey(
373 rh
.Employe
, related_name
='+', null
=True, blank
=True,
374 verbose_name
='Employé précédent')
375 statut_titulaire_anterieur
= models
.ForeignKey(
376 rh
.Statut
, related_name
='+', null
=True, blank
=True,
377 verbose_name
='Statut titulaire précédent')
378 classement_titulaire_anterieur
= models
.ForeignKey(
379 rh
.Classement
, related_name
='+', null
=True, blank
=True,
380 verbose_name
='Classement titulaire précédent')
381 salaire_titulaire_anterieur
= models
.DecimalField(
382 max_digits
=12, decimal_places
=2, default
=None, null
=True,
383 blank
=True, verbose_name
='Salaire titulaire précédent')
386 remplacement
= models
.BooleanField()
387 statut_residence
= models
.CharField(max_length
=10, default
='local',
388 verbose_name
="Statut",
389 choices
=STATUT_RESIDENCE_CHOICES
)
392 classement
= models
.ForeignKey(rh
.Classement
, related_name
='+',
393 verbose_name
='Classement proposé')
394 salaire
= models
.DecimalField(max_digits
=12, decimal_places
=2,
395 verbose_name
='Salaire de base',
396 null
=True, default
=None)
397 devise
= models
.ForeignKey(rh
.Devise
, default
=5, related_name
='+')
398 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
399 verbose_name
="Régime de travail",
400 help_text
="% du temps complet")
401 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
402 decimal_places
=2, verbose_name
="Nb. heures par semaine")
405 type_contrat
= models
.ForeignKey(rh
.TypeContrat
, related_name
='+')
406 contrat_date_debut
= models
.DateField(help_text
="format: aaaa-mm-jj")
407 contrat_date_fin
= models
.DateField(null
=True, blank
=True,
408 help_text
="format: aaaa-mm-jj")
411 compte_compta
= models
.CharField(max_length
=10, default
='aucun',
412 verbose_name
=u
'Compte comptabilité',
413 choices
=COMPTE_COMPTA_CHOICES
)
414 compte_courriel
= models
.BooleanField()
417 date_creation
= models
.DateTimeField(auto_now_add
=True)
420 objects
= DossierManager()
422 def __unicode__(self
):
423 return u
'[%s] %s - %s' % (self
.poste
.implantation
, self
.poste
.nom
, self
.employe
)
425 def get_salaire_euros(self
):
427 tx
= rh
.TauxChange
.objects
.filter(implantation
=self
.poste
.implantation
, devise
=self
.devise
)[0].taux
430 return (float)(tx
) * (float)(self
.salaire
)
432 def get_couts_auf(self
):
434 On retire les MAD BSTG
436 return [r
for r
in self
.remuneration_set
.all() if r
.type_id
not in (2, )]
438 def get_total_couts_auf(self
):
440 for r
in self
.get_couts_auf():
441 total
+= r
.montant_euro()
444 def get_aides_auf(self
):
446 On récupère les MAD BSTG
448 return [r
for r
in self
.remuneration_set
.all() if r
.type_id
in (2, )]
450 def get_total_aides_auf(self
):
452 for r
in self
.get_aides_auf():
453 total
+= r
.montant_euro()
456 def get_total_remun(self
):
457 return self
.get_total_couts_auf() + self
.get_total_aides_auf()
459 # Tester l'enregistrement car les models.py sont importés au complet
460 if not reversion
.is_registered(Dossier
):
461 reversion
.register(Dossier
)
463 class Remuneration(models
.Model
):
465 dossier
= models
.ForeignKey('Dossier', db_column
='dossier')
466 type = models
.ForeignKey(rh
.TypeRemuneration
, db_column
='type',
469 # type_revalorisation = models.ForeignKey('TypeRevalorisation',
470 # db_column='type_revalorisation')
471 montant
= models
.DecimalField(max_digits
=12, decimal_places
=2,
473 devise
= models
.ForeignKey(rh
.Devise
, to_field
='code',
474 db_column
='devise', related_name
='+')
475 precision
= models
.CharField(max_length
=255, null
=True, blank
=True)
476 # date_effective = models.DateField(null=True, blank=True)
477 # pourcentage = models.IntegerField(null=True, blank=True)
480 date_creation
= models
.DateField(auto_now_add
=True)
481 user_creation
= models
.IntegerField(null
=True, blank
=True)
482 # desactivation = models.BooleanField(default=False, blank=True)
483 # date_desactivation = models.DateField(null=True, blank=True)
484 # user_desactivation = models.IntegerField(null=True, blank=True)
485 # annulation = models.BooleanField(default=False, blank=True)
486 # date_annulation = models.DateField(null=True, blank=True)
487 # user_annulation = models.IntegerField(null=True, blank=True)
489 def montant_mois(self
):
490 return round(self
.montant
/ 12, 2)
492 def taux_devise(self
):
493 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
495 def montant_euro(self
):
496 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
498 def montant_euro_mois(self
):
499 return round(self
.montant_euro() / 12, 2)
502 TYPE_JUSTIFICATIONS
= (
503 ('N', 'Nouvel employé'),
504 ('R', 'Renouvellement employé'),
507 class JustificationQuestion(models
.Model
):
508 question
= models
.CharField(max_length
=255)
509 type = models
.CharField(max_length
=255, choices
=TYPE_JUSTIFICATIONS
)
511 def __unicode__(self
,):
514 class JustificationNouvelEmploye(models
.Model
):
515 dossier
= models
.ForeignKey("Dossier")
516 question
= models
.ForeignKey("JustificationQuestion")
517 reponse
= models
.TextField()
519 class JustificationAutreEmploye(models
.Model
):
520 dossier
= models
.ForeignKey("Dossier")
521 question
= models
.ForeignKey("JustificationQuestion")
522 reponse
= models
.TextField()