1 # -=- encoding: utf-8 -=-
4 from django
.core
.files
.storage
import FileSystemStorage
5 from django
.db
import models
6 from django
.contrib
.auth
.models
import Group
8 from workflow
import PosteWorkflow
, DossierWorkflow
9 import datamaster_modeles
.models
as ref
10 from rh_v1
import models
as rh
13 # Groupes impliqués dans le Worflow
14 grp_administrateurs
, created
= Group
.objects
.get_or_create(name
='Administrateurs')
15 grp_gestionnaires
, created
= Group
.objects
.get_or_create(name
='Gestionnaires')
16 grp_directeurs_bureau
, created
= Group
.objects
.get_or_create(name
='Directeurs de bureau')
17 grp_drh
, created
= Group
.objects
.get_or_create(name
='DRH')
18 grp_pole_financier
, created
= Group
.objects
.get_or_create(name
='Pôle financier')
19 grp_haute_direction
, created
= Group
.objects
.get_or_create(name
='Haute direction')
20 grp_service_utilisateurs
, created
= Group
.objects
.get_or_create(name
='Service utilisateurs')
21 grp_directeurs_service
, created
= Group
.objects
.get_or_create(name
='Directeurs de service / pôle')
22 grp_correspondants_rh
, created
= Group
.objects
.get_or_create(name
='Correspondants RH')
24 STATUT_RESIDENCE_CHOICES
= (
26 ('expat', 'Expatrié'),
29 POSTE_APPEL_CHOICES
= (
30 ('interne', 'Interne'),
31 ('externe', 'Externe'),
34 POSTE_STATUT_CHOICES
= (
35 ('MAD', 'Mise à disposition'),
36 ('DET', 'Détachement'),
40 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
, base_url
=settings
.PRIVE_MEDIA_URL
)
42 def poste_piece_dispatch(instance
, filename
):
43 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
46 def dossier_piece_dispatch(instance
, filename
):
47 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
51 class PostePiece(models
.Model
):
52 poste
= models
.ForeignKey("Poste")
53 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
54 fichier
= models
.FileField(verbose_name
="Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
)
56 class PosteManager(models
.Manager
):
58 Chargement de tous les objets FK existants sur chaque QuerySet.
60 def get_query_set(self
):
72 return super(PosteManager
, self
).get_query_set() \
73 .select_related(*fkeys
).all()
76 class Poste(PosteWorkflow
, models
.Model
):
78 id_rh
= models
.ForeignKey(rh
.Poste
, null
=True, related_name
='+',
80 verbose_name
="Mise à jour du poste")
81 nom
= models
.CharField(verbose_name
="Titre du poste", max_length
=255)
82 implantation
= models
.ForeignKey(ref
.Implantation
)
83 type_poste
= models
.ForeignKey(rh
.TypePoste
, null
=True, related_name
='+')
84 service
= models
.ForeignKey(rh
.Service
, related_name
='+',
85 verbose_name
=u
"Direction/Service/Pôle support")
86 responsable
= models
.ForeignKey(rh
.Poste
, related_name
='+',
87 verbose_name
="Poste du responsable")
89 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
91 verbose_name
="Temps de travail",
92 help_text
="% du temps complet")
93 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
96 verbose_name
="Nb. heures par semaine")
99 local
= models
.BooleanField(verbose_name
="Local", default
=True, blank
=True)
100 expatrie
= models
.BooleanField(verbose_name
="Expatrié", default
=False, blank
=True)
103 mise_a_disposition
= models
.BooleanField(verbose_name
="Mise à disposition")
104 appel
= models
.CharField(max_length
=10, default
='interne',
105 verbose_name
="Appel à candidature",
106 choices
=POSTE_APPEL_CHOICES
)
109 classement_min
= models
.ForeignKey(rh
.Classement
, related_name
='+')
110 classement_max
= models
.ForeignKey(rh
.Classement
, related_name
='+')
112 # En fait, les coefficient n'ont pas de valeur dans ces cas, les couts sont calculés
113 # et mis dans les coûts globals
114 #coefficient_min = models.FloatField(null=True) # pour classement "hors grille"
115 #coefficient_max = models.FloatField(null=True) # pour classement "hors grille"
117 valeur_point_min
= models
.ForeignKey(rh
.ValeurPoint
, related_name
='+', blank
=True, null
=True)
118 valeur_point_max
= models
.ForeignKey(rh
.ValeurPoint
, related_name
='+', blank
=True, null
=True)
119 devise_min
= models
.ForeignKey(rh
.Devise
, default
=5, related_name
='+')
120 devise_max
= models
.ForeignKey(rh
.Devise
, default
=5, related_name
='+')
121 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
123 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
125 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
127 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
129 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
131 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
134 # Comparatifs de rémunération
135 devise_comparaison
= models
.ForeignKey(rh
.Devise
, related_name
='+',
137 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
138 null
=True, blank
=True)
139 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
140 null
=True, blank
=True)
141 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
142 null
=True, blank
=True)
143 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
144 null
=True, blank
=True)
145 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
146 null
=True, blank
=True)
147 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
148 null
=True, blank
=True)
149 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
150 null
=True, blank
=True)
151 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
152 null
=True, blank
=True)
153 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
154 null
=True, blank
=True)
155 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
156 null
=True, blank
=True)
159 justification
= models
.TextField()
162 date_creation
= models
.DateTimeField(auto_now_add
=True)
163 date_modification
= models
.DateTimeField(auto_now
=True)
164 date_debut
= models
.DateField(verbose_name
="Date de début",
165 help_text
="format: aaaa-mm-jj")
166 date_fin
= models
.DateField(null
=True, blank
=True,
167 verbose_name
="Date de fin",
168 help_text
="format: aaaa-mm-jj")
169 actif
= models
.BooleanField(default
=True)
172 objects
= PosteManager()
176 Les vues sont montées selon une clef spéciale pour identifier la provenance du poste.
177 Cette méthode fournit un moyen de reconstruire cette clef afin de générer les URLs.
179 return "dae-%s" % self
.id
180 key
= property(_get_key
)
182 def get_dossiers(self
):
184 Liste tous les anciens dossiers liés à ce poste.
185 (Le nom de la relation sur le rh.Poste est mal choisi poste1 au lieu de dossier1)
186 Note1 : seulement le dosssier principal fait l'objet de la recherche.
187 Note2 : les dossiers sont retournés du plus récent au plus vieux. (Ce test est fait
188 en fonction du id, car les dates de création sont absentes de rh v1).
190 if self
.id_rh
is None:
192 postes
= [p
for p
in self
.id_rh
.poste1
.all()]
193 return sorted(postes
, key
=lambda poste
: poste
.id, reverse
=True)
195 def get_complement_nom(self
):
197 Inspecte les modèles rh v1 pour trouver dans le dernier dossier un complément de titre de poste.
199 dossiers
= self
.get_dossiers()
200 if len(dossiers
) > 0:
201 nom
= dossiers
[0].complement1
206 def get_employe(self
):
208 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
210 dossiers
= self
.get_dossiers()
211 if len(dossiers
) > 0:
212 return dossiers
[0].employe
216 def get_default_devise(self
):
217 """Récupère la devise par défaut en fonction de l'implantation (EUR autrement)"""
219 implantation_devise
= rh
.TauxChange
.objects
.filter(implantation
=self
.implantation
)[0].devise
221 implantation_devise
= 5 # EUR
222 return implantation_devise
224 #####################
225 # Classement de poste
226 #####################
228 def get_couts_minimum(self
):
229 return (float)(self
.salaire_min
+ self
.indemn_min
+ self
.autre_min
)
231 def get_taux_minimum(self
):
233 return rh
.TauxChange
.objects
.filter(implantation
=self
.implantation
, devise
=self
.devise_min
)[0].taux
237 def get_couts_minimum_euros(self
):
238 return self
.get_couts_minimum() * self
.get_taux_minimum()
240 def get_couts_maximum(self
):
241 return (float)(self
.salaire_max
+ self
.indemn_max
+ self
.autre_max
)
243 def get_taux_maximum(self
):
245 return rh
.TauxChange
.objects
.filter(implantation
=self
.implantation
, devise
=self
.devise_max
)[0].taux
249 def get_couts_maximum_euros(self
):
250 return self
.get_couts_maximum() * self
.get_taux_maximum()
252 ######################
253 # Comparaison de poste
254 ######################
256 def get_taux_comparaison(self
):
258 return rh
.TauxChange
.objects
.filter(implantation
=self
.implantation
, devise
=self
.devise_comparaison
)[0].taux
262 def get_comp_universite_min_euros(self
):
263 return (float)(self
.comp_universite_min
) * self
.get_taux_comparaison()
265 def get_comp_fonctionpub_min_euros(self
):
266 return (float)(self
.comp_fonctionpub_min
) * self
.get_taux_comparaison()
268 def get_comp_locale_min_euros(self
):
269 return (float)(self
.comp_locale_min
) * self
.get_taux_comparaison()
271 def get_comp_ong_min_euros(self
):
272 return (float)(self
.comp_ong_min
) * self
.get_taux_comparaison()
274 def get_comp_autre_min_euros(self
):
275 return (float)(self
.comp_autre_min
) * self
.get_taux_comparaison()
277 def get_comp_universite_max_euros(self
):
278 return (float)(self
.comp_universite_max
) * self
.get_taux_comparaison()
280 def get_comp_fonctionpub_max_euros(self
):
281 return (float)(self
.comp_fonctionpub_max
) * self
.get_taux_comparaison()
283 def get_comp_locale_max_euros(self
):
284 return (float)(self
.comp_locale_max
) * self
.get_taux_comparaison()
286 def get_comp_ong_max_euros(self
):
287 return (float)(self
.comp_ong_max
) * self
.get_taux_comparaison()
289 def get_comp_autre_max_euros(self
):
290 return (float)(self
.comp_autre_max
) * self
.get_taux_comparaison()
293 def __unicode__(self
):
295 Cette fonction est consommatrice SQL car elle cherche les dossiers qui ont été liés à celui-ci.
297 complement_nom_poste
= self
.get_complement_nom()
298 if complement_nom_poste
is None:
299 complement_nom_poste
= ""
300 employe
= self
.get_employe()
308 complement_nom_poste
,
311 return u
'%s - %s (%s) [dae-%s %s %s]' % data
314 # Tester l'enregistrement car les models.py sont importés au complet
315 if not reversion
.is_registered(Poste
):
316 reversion
.register(Poste
)
319 POSTE_FINANCEMENT_CHOICES
= (
320 ('A', 'A - Frais de personnel'),
321 ('B', 'B - Projet(s)-Titre(s)'),
326 class PosteFinancement(models
.Model
):
327 poste
= models
.ForeignKey('Poste', related_name
='financements')
328 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
329 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
330 help_text
="ex.: 33.33 % (décimale avec point)")
331 commentaire
= models
.TextField(
332 help_text
="Spécifiez la source de financement.")
337 def __unicode__(self
):
338 return u
"%s %s %s" % (self
.get_type_display(), self
.pourcentage
, self
.commentaire
)
346 class Employe(models
.Model
):
349 id_rh
= models
.ForeignKey(rh
.Employe
, null
=True, related_name
='+',
350 verbose_name
='Employé')
351 nom
= models
.CharField(max_length
=255)
352 prenom
= models
.CharField(max_length
=255, verbose_name
='Prénom')
353 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
,
354 null
=True, blank
=True)
356 def __unicode__(self
):
357 return u
'%s %s' % (self
.prenom
, self
.nom
)
360 COMPTE_COMPTA_CHOICES
= (
366 class DossierPiece(models
.Model
):
367 dossier
= models
.ForeignKey("Dossier")
368 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
369 fichier
= models
.FileField(verbose_name
="Fichier", upload_to
=dossier_piece_dispatch
, storage
=storage_prive
)
371 class Dossier(DossierWorkflow
, models
.Model
):
374 employe
= models
.ForeignKey('Employe', related_name
='+', editable
=False)
375 poste
= models
.ForeignKey('Poste', related_name
='+', editable
=False)
376 statut
= models
.ForeignKey(rh
.Statut
, related_name
='+')
377 organisme_bstg
= models
.ForeignKey(rh
.OrganismeBstg
,
378 null
=True, blank
=True,
379 verbose_name
="Organisme",
380 help_text
="Si détaché (DET) ou mis à disposition (MAD), \
381 préciser l'organisme.",
383 organisme_bstg_autre
= models
.CharField(max_length
=255,
384 verbose_name
="Autre organisme",
385 help_text
="indiquer l'organisme ici s'il n'est pas dans la liste",
389 # Données antérieures de l'employé
390 statut_anterieur
= models
.ForeignKey(
391 rh
.Statut
, related_name
='+', null
=True, blank
=True,
392 verbose_name
='Statut antérieur')
393 classement_anterieur
= models
.ForeignKey(
394 rh
.Classement
, related_name
='+', null
=True, blank
=True,
395 verbose_name
='Classement précédent')
396 salaire_anterieur
= models
.DecimalField(
397 max_digits
=12, decimal_places
=2, null
=True, default
=None,
398 blank
=True, verbose_name
='Salaire précédent')
400 # Données du titulaire précédent
401 employe_anterieur
= models
.ForeignKey(
402 rh
.Employe
, related_name
='+', null
=True, blank
=True,
403 verbose_name
='Employé précédent')
404 statut_titulaire_anterieur
= models
.ForeignKey(
405 rh
.Statut
, related_name
='+', null
=True, blank
=True,
406 verbose_name
='Statut titulaire précédent')
407 classement_titulaire_anterieur
= models
.ForeignKey(
408 rh
.Classement
, related_name
='+', null
=True, blank
=True,
409 verbose_name
='Classement titulaire précédent')
410 salaire_titulaire_anterieur
= models
.DecimalField(
411 max_digits
=12, decimal_places
=2, default
=None, null
=True,
412 blank
=True, verbose_name
='Salaire titulaire précédent')
415 remplacement
= models
.BooleanField()
416 statut_residence
= models
.CharField(max_length
=10, default
='local',
417 verbose_name
="Statut",
418 choices
=STATUT_RESIDENCE_CHOICES
)
421 classement
= models
.ForeignKey(rh
.Classement
, related_name
='+',
422 verbose_name
='Classement proposé')
423 salaire
= models
.DecimalField(max_digits
=12, decimal_places
=2,
424 verbose_name
='Salaire de base',
425 null
=True, default
=None)
426 devise
= models
.ForeignKey(rh
.Devise
, default
=5, related_name
='+')
427 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
428 verbose_name
="Régime de travail",
429 help_text
="% du temps complet")
430 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
431 decimal_places
=2, verbose_name
="Nb. heures par semaine")
434 type_contrat
= models
.ForeignKey(rh
.TypeContrat
, related_name
='+')
435 contrat_date_debut
= models
.DateField(help_text
="format: aaaa-mm-jj")
436 contrat_date_fin
= models
.DateField(null
=True, blank
=True,
437 help_text
="format: aaaa-mm-jj")
440 compte_compta
= models
.CharField(max_length
=10, default
='aucun',
441 verbose_name
=u
'Compte comptabilité',
442 choices
=COMPTE_COMPTA_CHOICES
)
443 compte_courriel
= models
.BooleanField()
446 date_creation
= models
.DateTimeField(auto_now_add
=True)
448 def __unicode__(self
):
449 return u
'%s - %s' % (self
.poste
.nom
, self
.employe
)
451 def get_salaire_euros(self
):
453 tx
= rh
.TauxChange
.objects
.filter(implantation
=self
.poste
.implantation
, devise
=self
.devise
)[0].taux
456 return (float)(tx
) * (float)(self
.salaire
)
458 def get_couts_auf(self
):
460 On retire les MAD BSTG
462 return [r
for r
in self
.remuneration_set
.all() if r
.type_id
not in (2, )]
464 def get_total_couts_auf(self
):
466 for r
in self
.get_couts_auf():
467 total
+= r
.montant_euro()
470 def get_aides_auf(self
):
472 On récupère les MAD BSTG
474 return [r
for r
in self
.remuneration_set
.all() if r
.type_id
in (2, )]
476 def get_total_aides_auf(self
):
478 for r
in self
.get_aides_auf():
479 total
+= r
.montant_euro()
482 def get_total_remun(self
):
483 return self
.get_total_couts_auf() + self
.get_total_aides_auf()
485 # Tester l'enregistrement car les models.py sont importés au complet
486 if not reversion
.is_registered(Dossier
):
487 reversion
.register(Dossier
)
489 class Remuneration(models
.Model
):
491 dossier
= models
.ForeignKey('Dossier', db_column
='dossier')
492 type = models
.ForeignKey(rh
.TypeRemuneration
, db_column
='type',
495 # type_revalorisation = models.ForeignKey('TypeRevalorisation',
496 # db_column='type_revalorisation')
497 montant
= models
.DecimalField(max_digits
=12, decimal_places
=2,
499 devise
= models
.ForeignKey(rh
.Devise
, to_field
='code',
500 db_column
='devise', related_name
='+')
501 precision
= models
.CharField(max_length
=255, null
=True, blank
=True)
502 # date_effective = models.DateField(null=True, blank=True)
503 # pourcentage = models.IntegerField(null=True, blank=True)
506 date_creation
= models
.DateField(auto_now_add
=True)
507 user_creation
= models
.IntegerField(null
=True, blank
=True)
508 # desactivation = models.BooleanField(default=False, blank=True)
509 # date_desactivation = models.DateField(null=True, blank=True)
510 # user_desactivation = models.IntegerField(null=True, blank=True)
511 # annulation = models.BooleanField(default=False, blank=True)
512 # date_annulation = models.DateField(null=True, blank=True)
513 # user_annulation = models.IntegerField(null=True, blank=True)
515 def montant_mois(self
):
516 return round(self
.montant
/ 12, 2)
518 def taux_devise(self
):
519 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
521 def montant_euro(self
):
522 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
524 def montant_euro_mois(self
):
525 return round(self
.montant_euro() / 12, 2)
528 TYPE_JUSTIFICATIONS
= (
529 ('N', 'Nouvel employé'),
530 ('R', 'Renouvellement employé'),
533 class JustificationQuestion(models
.Model
):
534 question
= models
.CharField(max_length
=255)
535 type = models
.CharField(max_length
=255, choices
=TYPE_JUSTIFICATIONS
)
537 def __unicode__(self
,):
540 class JustificationNouvelEmploye(models
.Model
):
541 dossier
= models
.ForeignKey("Dossier")
542 question
= models
.ForeignKey("JustificationQuestion")
543 reponse
= models
.TextField()
545 class JustificationAutreEmploye(models
.Model
):
546 dossier
= models
.ForeignKey("Dossier")
547 question
= models
.ForeignKey("JustificationQuestion")
548 reponse
= models
.TextField()