1 # -=- encoding: utf-8 -=-
3 from django
.conf
import settings
4 from django
.core
.files
.storage
import FileSystemStorage
5 from django
.db
import models
7 from rh
import models
as rh
8 from workflow
import PosteWorkflow
, DossierWorkflow
9 from workflow
import DOSSIER_ETAT_DRH_FINALISATION
, DOSSIER_ETAT_REGION_FINALISATION
, \
11 from auf
.django
.metadata
.models
import AUFMetadata
12 from managers
import *
13 from rh
.models
import HELP_TEXT_DATE
14 from exporter
import DossierCopier
, PosteCopier
17 UPLOAD_STORAGE
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
)
22 POSTE_APPEL_CHOICES
= (
23 ('interne', 'Interne'),
24 ('externe', 'Externe'),
27 ('N', u
"Nouveau poste"),
28 ('M', u
"Poste existant"),
29 ('E', u
"Évolution de poste"),
33 class DeviseException(Exception):
34 silent_variable_failure
= True
37 class Poste(PosteWorkflow
, rh
.Poste_
):
39 type_intervention
= models
.CharField(max_length
=1, choices
=POSTE_ACTION
, default
='N')
42 id_rh
= models
.ForeignKey(rh
.Poste
, null
=True, related_name
='+',
44 verbose_name
=u
"Mise à jour du poste")
47 indemn_expat_min
= models
.DecimalField(max_digits
=13, decimal_places
=2, default
=0)
48 indemn_expat_max
= models
.DecimalField(max_digits
=12, decimal_places
=2, default
=0)
49 indemn_fct_min
= models
.DecimalField(max_digits
=12, decimal_places
=2, default
=0)
50 indemn_fct_max
= models
.DecimalField(max_digits
=12, decimal_places
=2, default
=0)
51 charges_patronales_min
= models
.DecimalField(max_digits
=12, decimal_places
=2, default
=0)
52 charges_patronales_max
= models
.DecimalField(max_digits
=12, decimal_places
=2, default
=0)
55 objects
= PosteManager()
59 Les vues sont montées selon une clef spéciale
60 pour identifier la provenance du poste.
61 Cette méthode fournit un moyen de reconstruire cette clef
62 afin de générer les URLs.
64 return "dae-%s" % self
.id
65 key
= property(_get_key
)
67 def get_dossiers(self
):
69 Liste tous les anciens dossiers liés à ce poste.
70 (Le nom de la relation sur le rh.Poste est mal choisi
71 poste1 au lieu de dossier1)
72 Note1 : seulement le dosssier principal fait l'objet de la recherche.
73 Note2 : les dossiers sont retournés du plus récent au plus vieux.
74 (Ce test est fait en fonction du id,
75 car les dates de création sont absentes de rh v1).
77 if self
.id_rh
is None:
79 return self
.id_rh
.rh_dossiers
.all()
81 def rh_importation(self
):
82 if ImportPoste
.objects
.filter(dae
=self
).exists():
83 return ImportPoste
.objects
.get(dae
=self
).rh
87 def importer(self
, verbosity
=0, dry_run
=False):
88 copieur
= PosteCopier(verbosity
=verbosity
, dry_run
=dry_run
)
89 return copieur
.copy(self
)
91 def get_employe(self
):
93 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
95 dossiers
= self
.get_dossiers()
97 return dossiers
[0].employe
101 def get_default_devise(self
):
102 """Récupère la devise par défaut en fonction de l'implantation
106 implantation_devise
= rh
.TauxChange
.objects \
107 .filter(implantation
=self
.implantation
)[0].devise
109 implantation_devise
= 5 # EUR
110 return implantation_devise
112 #####################
113 # Classement de poste
114 #####################
116 def get_couts_minimum(self
):
117 return self
.salaire_min
+ self
.indemn_expat_min
+ self
.indemn_fct_min
+ self
.charges_patronales_min
+ self
.autre_min
119 def get_salaire_minimum(self
):
120 return self
.get_couts_minimum() - self
.charges_patronales_min
122 def get_taux_minimum(self
):
123 if self
.devise_min
.code
== 'EUR':
125 liste_taux
= self
.devise_min
.tauxchange_set
.order_by('-annee')
126 if len(liste_taux
) == 0:
127 raise DeviseException(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise_min
, self
.implantation
))
129 return liste_taux
[0].taux
131 def get_couts_minimum_euros(self
):
132 return float(self
.get_couts_minimum()) * self
.get_taux_minimum()
134 def get_salaire_minimum_euros(self
):
135 return float(self
.get_salaire_minimum()) * self
.get_taux_minimum()
137 def get_couts_maximum(self
):
138 return self
.salaire_max
+ self
.indemn_expat_max
+ self
.indemn_fct_max
+ self
.charges_patronales_max
+ self
.autre_max
140 def get_salaire_maximum(self
):
141 return self
.get_couts_maximum() - self
.charges_patronales_max
143 def get_taux_maximum(self
):
144 if self
.devise_max
.code
== 'EUR':
146 liste_taux
= self
.devise_max
.tauxchange_set
.order_by('-annee')
147 if len(liste_taux
) == 0:
148 raise DeviseException(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise_max
, self
.implantation
))
150 return liste_taux
[0].taux
152 def get_couts_maximum_euros(self
):
153 return float(self
.get_couts_maximum()) * self
.get_taux_maximum()
155 def get_salaire_maximum_euros(self
):
156 return float(self
.get_salaire_maximum()) * self
.get_taux_maximum()
158 def show_taux_minimum(self
):
160 return self
.get_taux_minimum()
161 except DeviseException
, e
:
164 def show_couts_minimum_euros(self
):
166 return self
.get_couts_minimum_euros()
167 except DeviseException
, e
:
170 def show_salaire_minimum_euros(self
):
172 return self
.get_salaire_minimum_euros()
173 except DeviseException
, e
:
176 def show_taux_maximum(self
):
178 return self
.get_taux_maximum()
179 except DeviseException
, e
:
182 def show_couts_maximum_euros(self
):
184 return self
.get_couts_maximum_euros()
185 except DeviseException
, e
:
188 def show_salaire_maximum_euros(self
):
190 return self
.get_salaire_maximum_euros()
191 except DeviseException
, e
:
195 ######################
196 # Comparaison de poste
197 ######################
199 def est_comparable(self
):
201 Si on a au moins une valeur de saisie dans les comparaisons, alors le poste
204 if self
.comp_universite_min
is None and \
205 self
.comp_fonctionpub_min
is None and \
206 self
.comp_locale_min
is None and \
207 self
.comp_ong_min
is None and \
208 self
.comp_autre_min
is None and \
209 self
.comp_universite_max
is None and \
210 self
.comp_fonctionpub_max
is None and \
211 self
.comp_locale_max
is None and \
212 self
.comp_ong_max
is None and \
213 self
.comp_autre_max
is None:
219 def get_taux_comparaison(self
):
221 return rh
.TauxChange
.objects
.filter(devise
=self
.devise_comparaison
)[0].taux
225 def get_comp_universite_min_euros(self
):
226 return (float)(self
.comp_universite_min
) * self
.get_taux_comparaison()
228 def get_comp_fonctionpub_min_euros(self
):
229 return (float)(self
.comp_fonctionpub_min
) * self
.get_taux_comparaison()
231 def get_comp_locale_min_euros(self
):
232 return (float)(self
.comp_locale_min
) * self
.get_taux_comparaison()
234 def get_comp_ong_min_euros(self
):
235 return (float)(self
.comp_ong_min
) * self
.get_taux_comparaison()
237 def get_comp_autre_min_euros(self
):
238 return (float)(self
.comp_autre_min
) * self
.get_taux_comparaison()
240 def get_comp_universite_max_euros(self
):
241 return (float)(self
.comp_universite_max
) * self
.get_taux_comparaison()
243 def get_comp_fonctionpub_max_euros(self
):
244 return (float)(self
.comp_fonctionpub_max
) * self
.get_taux_comparaison()
246 def get_comp_locale_max_euros(self
):
247 return (float)(self
.comp_locale_max
) * self
.get_taux_comparaison()
249 def get_comp_ong_max_euros(self
):
250 return (float)(self
.comp_ong_max
) * self
.get_taux_comparaison()
252 def get_comp_autre_max_euros(self
):
253 return (float)(self
.comp_autre_max
) * self
.get_taux_comparaison()
256 def __unicode__(self
):
258 Cette fonction est consommatrice SQL car elle cherche les dossiers
259 qui ont été liés à celui-ci.
266 return u
'%s - %s (%s)' % data
269 # Tester l'enregistrement car les models.py sont importés au complet
270 if not reversion
.is_registered(Poste
):
271 reversion
.register(Poste
)
274 POSTE_FINANCEMENT_CHOICES
= (
275 ('A', 'A - Frais de personnel'),
276 ('B', 'B - Projet(s)-Titre(s)'),
280 class PosteFinancement(rh
.PosteFinancement_
):
283 class PostePiece(rh
.PostePiece_
):
286 class PosteComparaison(rh
.PosteComparaison_
):
287 statut
= models
.ForeignKey(rh
.Statut
, related_name
='+', verbose_name
=u
'Statut', null
=True, blank
=True, )
288 classement
= models
.ForeignKey(rh
.Classement
, related_name
='+', verbose_name
=u
'Classement', null
=True, blank
=True, )
292 # TODO : migration pour m -> M, f -> F
299 class Employe(AUFMetadata
):
302 id_rh
= models
.ForeignKey(rh
.Employe
, null
=True, related_name
='+',
303 verbose_name
=u
'Employé')
304 nom
= models
.CharField(max_length
=255)
305 prenom
= models
.CharField(max_length
=255, verbose_name
=u
'Prénom')
306 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
308 def __unicode__(self
):
309 return u
'%s %s' % (self
.prenom
, self
.nom
.upper())
314 STATUT_RESIDENCE_CHOICES
= (
316 ('expat', 'Expatrié'),
319 COMPTE_COMPTA_CHOICES
= (
325 class Dossier(DossierWorkflow
, rh
.Dossier_
):
326 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='%(app_label)s_dossiers')
327 employe
= models
.ForeignKey('Employe', db_column
='employe',
328 related_name
='%(app_label)s_dossiers',
329 verbose_name
=u
"Employé")
330 organisme_bstg_autre
= models
.CharField(max_length
=255,
331 verbose_name
=u
"Autre organisme",
332 help_text
="indiquer l'organisme ici s'il n'est pas dans la liste",
336 # Données antérieures de l'employé
337 statut_anterieur
= models
.ForeignKey(
338 rh
.Statut
, related_name
='+', null
=True, blank
=True,
339 verbose_name
=u
'Statut antérieur')
340 classement_anterieur
= models
.ForeignKey(
341 rh
.Classement
, related_name
='+', null
=True, blank
=True,
342 verbose_name
=u
'Classement précédent')
343 salaire_anterieur
= models
.DecimalField(
344 max_digits
=12, decimal_places
=2, null
=True, default
=None,
345 blank
=True, verbose_name
=u
'Salaire précédent')
346 devise_anterieur
= models
.ForeignKey(rh
.Devise
, related_name
='+',
347 null
=True, blank
=True)
348 type_contrat_anterieur
= models
.ForeignKey(rh
.TypeContrat
,
349 related_name
='+', null
=True, blank
=True,
350 verbose_name
=u
'Type contrat antérieur', )
352 # Données du titulaire précédent
353 employe_anterieur
= models
.ForeignKey(
354 rh
.Employe
, related_name
='+', null
=True, blank
=True,
355 verbose_name
=u
'Employé précédent')
356 statut_titulaire_anterieur
= models
.ForeignKey(
357 rh
.Statut
, related_name
='+', null
=True, blank
=True,
358 verbose_name
=u
'Statut titulaire précédent')
359 classement_titulaire_anterieur
= models
.ForeignKey(
360 rh
.Classement
, related_name
='+', null
=True, blank
=True,
361 verbose_name
=u
'Classement titulaire précédent')
362 salaire_titulaire_anterieur
= models
.DecimalField(
363 max_digits
=12, decimal_places
=2, default
=None, null
=True,
364 blank
=True, verbose_name
=u
'Salaire titulaire précédent')
365 devise_titulaire_anterieur
= models
.ForeignKey(rh
.Devise
, related_name
='+', null
=True, blank
=True)
368 salaire
= models
.DecimalField(max_digits
=13, decimal_places
=2,
369 verbose_name
=u
'Salaire de base',
370 null
=True, default
=None)
371 devise
= models
.ForeignKey(rh
.Devise
, default
=5, related_name
='+')
374 type_contrat
= models
.ForeignKey(rh
.TypeContrat
, related_name
='+')
375 contrat_date_debut
= models
.DateField(help_text
=HELP_TEXT_DATE
)
376 contrat_date_fin
= models
.DateField(null
=True, blank
=True,
377 help_text
=HELP_TEXT_DATE
)
380 justif_nouveau_statut_label
= u
'Justifier le statut que ce type de poste nécessite (national, expatrié, màd ou détachement)'
381 justif_nouveau_statut
= models
.TextField(verbose_name
=justif_nouveau_statut_label
, null
=True, blank
=True)
382 justif_nouveau_tmp_remplacement_label
= u
"Si l'employé effectue un remplacement temporaire, préciser"
383 justif_nouveau_tmp_remplacement
= models
.TextField(verbose_name
=justif_nouveau_tmp_remplacement_label
, null
=True, blank
=True)
384 justif_nouveau_salaire_label
= u
"Si le salaire de l'employé ne correspond pas au classement du poste ou est différent du salaire antérieur, justifier "
385 justif_nouveau_salaire
= models
.TextField(verbose_name
=justif_nouveau_salaire_label
, null
=True, blank
=True)
386 justif_nouveau_commentaire_label
= u
"COMMENTAIRES ADDITIONNELS"
387 justif_nouveau_commentaire
= models
.TextField(verbose_name
=justif_nouveau_commentaire_label
, null
=True, blank
=True)
388 justif_rempl_type_contrat_label
= u
"Changement de type de contrat, ex : d'un CDD en CDI"
389 justif_rempl_type_contrat
= models
.TextField(verbose_name
=justif_rempl_type_contrat_label
, null
=True, blank
=True)
390 justif_rempl_statut_employe_label
= u
"Si le statut de l'employé a été modifié pour ce poste ; ex :national, expatrié, màd, détachement ? Si oui, justifier"
391 justif_rempl_statut_employe
= models
.TextField(verbose_name
=justif_rempl_statut_employe_label
, null
=True, blank
=True)
392 justif_rempl_evaluation_label
= u
"L'évaluation de l'employé est-elle favorable? Préciser"
393 justif_rempl_evaluation
= models
.TextField(verbose_name
=justif_rempl_evaluation_label
, null
=True, blank
=True)
394 justif_rempl_salaire_label
= u
"Si le salaire de l'employé est modifié et/ou ne correspond pas à son classement, justifier"
395 justif_rempl_salaire
= models
.TextField(verbose_name
=justif_rempl_salaire_label
, null
=True, blank
=True)
396 justif_rempl_commentaire_label
= u
"COMMENTAIRES ADDITIONNELS"
397 justif_rempl_commentaire
= models
.TextField(verbose_name
=justif_rempl_commentaire_label
, null
=True, blank
=True)
400 compte_compta
= models
.CharField(max_length
=10, default
='aucun',
401 verbose_name
=u
'Compte comptabilité',
402 choices
=COMPTE_COMPTA_CHOICES
)
403 compte_courriel
= models
.BooleanField()
406 dae_numerisee
= models
.FileField(upload_to
='dae/dae_numerisee', storage
=UPLOAD_STORAGE
,
407 blank
=True, null
=True, verbose_name
="DAE numérisée")
410 objects
= DossierManager()
412 def __init__(self
, *args
, **kwargs
):
413 # Bouchon pour créer une date fictive necessaire pour valider un dossier
414 # à cause de l'héritage
415 super(rh
.Dossier_
, self
).__init__(*args
, **kwargs
)
416 super(DossierWorkflow
, self
).__init__(*args
, **kwargs
)
418 self
.date_debut
= datetime
.datetime
.today()
420 def __unicode__(self
):
421 return u
'[%s] %s - %s' % (self
.poste
.implantation
, self
.poste
.nom
, self
.employe
)
423 def importer(self
, verbosity
=0, dry_run
=False):
424 copieur
= DossierCopier(verbosity
=verbosity
, dry_run
=dry_run
)
425 return copieur
.copy(self
)
427 def get_salaire_anterieur_euros(self
):
428 if self
.devise_anterieur
is None:
431 taux
= self
.taux_devise(self
.devise_anterieur
)
436 return int(round(float(self
.salaire_anterieur
) * float(taux
), 2))
439 def get_salaire_titulaire_anterieur_euros(self
):
440 if self
.devise_titulaire_anterieur
is None:
443 taux
= self
.taux_devise(self
.devise_titulaire_anterieur
)
448 return int(round(float(self
.salaire_titulaire_anterieur
) * float(taux
), 2))
451 return self
.etat
in (DOSSIER_ETAT_REGION_FINALISATION
,
452 DOSSIER_ETAT_DRH_FINALISATION
,
453 DOSSIER_ETAT_FINALISE
)
456 # Tester l'enregistrement car les models.py sont importés au complet
457 if not reversion
.is_registered(Dossier
):
458 reversion
.register(Dossier
)
460 class DossierPiece(rh
.DossierPiece_
):
461 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
462 Ex.: Lettre de motivation.
466 class DossierComparaison(rh
.DossierComparaison_
):
468 Photo d'une comparaison salariale au moment de l'embauche.
470 statut
= models
.ForeignKey(rh
.Statut
, related_name
='+', verbose_name
='Statut', null
=True, blank
=True, )
471 classement
= models
.ForeignKey(rh
.Classement
, related_name
='+', verbose_name
='Classement', null
=True, blank
=True, )
476 class Remuneration(rh
.Remuneration_
):
481 class Contrat(rh
.Contrat_
):
485 class DossierFinalise(Dossier
):
487 objects
= DossierFinaliseManager()
491 verbose_name
= "Import d'un dossier dans RH"
492 verbose_name_plural
= "Import des dossiers dans RH"
494 class PosteFinalise(Poste
):
496 objects
= PosteFinaliseManager()
500 verbose_name
= "Import d'un poste dans RH"
501 verbose_name_plural
= "Import des postes dans RH"
503 # modèle de liaison entre les systèmes
505 class ImportDossier(models
.Model
):
506 dae
= models
.ForeignKey('dae.Dossier', related_name
='+')
507 rh
= models
.ForeignKey('rh.Dossier', related_name
='+')
509 class ImportPoste(models
.Model
):
510 dae
= models
.ForeignKey('dae.Poste', related_name
='+')
511 rh
= models
.ForeignKey('rh.Poste', related_name
='+')