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 rh
import models
as rh
9 from workflow
import PosteWorkflow
, DossierWorkflow
10 from workflow
import DOSSIER_ETAT_DRH_FINALISATION
, DOSSIER_ETAT_REGION_FINALISATION
, \
12 import auf
.django
.references
.models
as ref
13 from managers
import *
17 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
18 REGIME_TRAVAIL_DEFAULT
=100.00
19 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
=35.00
22 UPLOAD_STORAGE
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
)
27 POSTE_APPEL_CHOICES
= (
28 ('interne', 'Interne'),
29 ('externe', 'Externe'),
32 ('N', u
"Nouveau poste"),
33 ('M', u
"Poste existant"),
34 ('E', u
"Évolution de poste"),
38 class DeviseException(Exception):
39 silent_variable_failure
= True
42 class Poste(PosteWorkflow
, rh
.Poste_
):
44 type_intervention
= models
.CharField(max_length
=1, choices
=POSTE_ACTION
, default
='N')
47 id_rh
= models
.ForeignKey(rh
.Poste
, null
=True, related_name
='+',
49 verbose_name
=u
"Mise à jour du poste")
52 indemn_expat_min
= models
.DecimalField(max_digits
=13, decimal_places
=2, default
=0)
53 indemn_expat_max
= models
.DecimalField(max_digits
=12, decimal_places
=2, default
=0)
54 indemn_fct_min
= models
.DecimalField(max_digits
=12, decimal_places
=2, default
=0)
55 indemn_fct_max
= models
.DecimalField(max_digits
=12, decimal_places
=2, default
=0)
56 charges_patronales_min
= models
.DecimalField(max_digits
=12, decimal_places
=2, default
=0)
57 charges_patronales_max
= models
.DecimalField(max_digits
=12, decimal_places
=2, default
=0)
60 objects
= PosteManager()
63 def est_importe(self
):
64 """Test si le poste a déjà été importé"""
65 return ImportPoste
.objects
.filter(dae
=self
).exists()
68 from django
.db
.models
import AutoField
69 if self
.est_importe():
70 return ImportPoste
.objects
.get(dae
=self
)
72 # Faire une copie profonde de l'objet.
73 # PosteFinancement, PosteComparaison, Remun modele a ajuster...
78 Les vues sont montées selon une clef spéciale
79 pour identifier la provenance du poste.
80 Cette méthode fournit un moyen de reconstruire cette clef
81 afin de générer les URLs.
83 return "dae-%s" % self
.id
84 key
= property(_get_key
)
86 def get_dossiers(self
):
88 Liste tous les anciens dossiers liés à ce poste.
89 (Le nom de la relation sur le rh.Poste est mal choisi
90 poste1 au lieu de dossier1)
91 Note1 : seulement le dosssier principal fait l'objet de la recherche.
92 Note2 : les dossiers sont retournés du plus récent au plus vieux.
93 (Ce test est fait en fonction du id,
94 car les dates de création sont absentes de rh v1).
96 if self
.id_rh
is None:
98 return self
.id_rh
.rh_dossiers
.all()
100 def get_complement_nom(self
):
102 Inspecte les modèles rh v1 pour trouver dans le dernier dossier
103 un complément de titre de poste.
105 dossiers
= self
.get_dossiers()
106 if len(dossiers
) > 0:
107 nom
= dossiers
[0].poste
.nom
112 def get_employe(self
):
114 Inspecte les modèles rh v1 pour trouver l'employé du dernier dossier.
116 dossiers
= self
.get_dossiers()
117 if len(dossiers
) > 0:
118 return dossiers
[0].employe
122 def get_default_devise(self
):
123 """Récupère la devise par défaut en fonction de l'implantation
127 implantation_devise
= rh
.TauxChange
.objects \
128 .filter(implantation
=self
.implantation
)[0].devise
130 implantation_devise
= 5 # EUR
131 return implantation_devise
133 #####################
134 # Classement de poste
135 #####################
137 def get_couts_minimum(self
):
138 return self
.salaire_min
+ self
.indemn_expat_min
+ self
.indemn_fct_min
+ self
.charges_patronales_min
+ self
.autre_min
140 def get_salaire_minimum(self
):
141 return self
.get_couts_minimum() - self
.charges_patronales_min
143 def get_taux_minimum(self
):
144 if self
.devise_min
.code
== 'EUR':
146 liste_taux
= self
.devise_min
.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_min
, self
.implantation
))
150 return liste_taux
[0].taux
152 def get_couts_minimum_euros(self
):
153 return float(self
.get_couts_minimum()) * self
.get_taux_minimum()
155 def get_salaire_minimum_euros(self
):
156 return float(self
.get_salaire_minimum()) * self
.get_taux_minimum()
158 def get_couts_maximum(self
):
159 return self
.salaire_max
+ self
.indemn_expat_max
+ self
.indemn_fct_max
+ self
.charges_patronales_max
+ self
.autre_max
161 def get_salaire_maximum(self
):
162 return self
.get_couts_maximum() - self
.charges_patronales_max
164 def get_taux_maximum(self
):
165 if self
.devise_max
.code
== 'EUR':
167 liste_taux
= self
.devise_max
.tauxchange_set
.order_by('-annee')
168 if len(liste_taux
) == 0:
169 raise DeviseException(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise_max
, self
.implantation
))
171 return liste_taux
[0].taux
173 def get_couts_maximum_euros(self
):
174 return float(self
.get_couts_maximum()) * self
.get_taux_maximum()
176 def get_salaire_maximum_euros(self
):
177 return float(self
.get_salaire_maximum()) * self
.get_taux_maximum()
179 def show_taux_minimum(self
):
181 return self
.get_taux_minimum()
182 except DeviseException
, e
:
185 def show_couts_minimum_euros(self
):
187 return self
.get_couts_minimum_euros()
188 except DeviseException
, e
:
191 def show_salaire_minimum_euros(self
):
193 return self
.get_salaire_minimum_euros()
194 except DeviseException
, e
:
197 def show_taux_maximum(self
):
199 return self
.get_taux_maximum()
200 except DeviseException
, e
:
203 def show_couts_maximum_euros(self
):
205 return self
.get_couts_maximum_euros()
206 except DeviseException
, e
:
209 def show_salaire_maximum_euros(self
):
211 return self
.get_salaire_maximum_euros()
212 except DeviseException
, e
:
216 ######################
217 # Comparaison de poste
218 ######################
220 def est_comparable(self
):
222 Si on a au moins une valeur de saisie dans les comparaisons, alors le poste
225 if self
.comp_universite_min
is None and \
226 self
.comp_fonctionpub_min
is None and \
227 self
.comp_locale_min
is None and \
228 self
.comp_ong_min
is None and \
229 self
.comp_autre_min
is None and \
230 self
.comp_universite_max
is None and \
231 self
.comp_fonctionpub_max
is None and \
232 self
.comp_locale_max
is None and \
233 self
.comp_ong_max
is None and \
234 self
.comp_autre_max
is None:
240 def get_taux_comparaison(self
):
242 return rh
.TauxChange
.objects
.filter(devise
=self
.devise_comparaison
)[0].taux
246 def get_comp_universite_min_euros(self
):
247 return (float)(self
.comp_universite_min
) * self
.get_taux_comparaison()
249 def get_comp_fonctionpub_min_euros(self
):
250 return (float)(self
.comp_fonctionpub_min
) * self
.get_taux_comparaison()
252 def get_comp_locale_min_euros(self
):
253 return (float)(self
.comp_locale_min
) * self
.get_taux_comparaison()
255 def get_comp_ong_min_euros(self
):
256 return (float)(self
.comp_ong_min
) * self
.get_taux_comparaison()
258 def get_comp_autre_min_euros(self
):
259 return (float)(self
.comp_autre_min
) * self
.get_taux_comparaison()
261 def get_comp_universite_max_euros(self
):
262 return (float)(self
.comp_universite_max
) * self
.get_taux_comparaison()
264 def get_comp_fonctionpub_max_euros(self
):
265 return (float)(self
.comp_fonctionpub_max
) * self
.get_taux_comparaison()
267 def get_comp_locale_max_euros(self
):
268 return (float)(self
.comp_locale_max
) * self
.get_taux_comparaison()
270 def get_comp_ong_max_euros(self
):
271 return (float)(self
.comp_ong_max
) * self
.get_taux_comparaison()
273 def get_comp_autre_max_euros(self
):
274 return (float)(self
.comp_autre_max
) * self
.get_taux_comparaison()
277 def __unicode__(self
):
279 Cette fonction est consommatrice SQL car elle cherche les dossiers
280 qui ont été liés à celui-ci.
282 complement_nom_poste
= self
.get_complement_nom()
283 if complement_nom_poste
is None:
284 complement_nom_poste
= ""
290 return u
'%s - %s (%s)' % data
293 # Tester l'enregistrement car les models.py sont importés au complet
294 if not reversion
.is_registered(Poste
):
295 reversion
.register(Poste
)
298 POSTE_FINANCEMENT_CHOICES
= (
299 ('A', 'A - Frais de personnel'),
300 ('B', 'B - Projet(s)-Titre(s)'),
304 class PosteFinancement(rh
.PosteFinancement_
):
307 class PostePiece(rh
.PostePiece_
):
310 class PosteComparaison(rh
.PosteComparaison_
):
315 # TODO : migration pour m -> M, f -> F
322 class Employe(models
.Model
):
325 id_rh
= models
.ForeignKey(rh
.Employe
, null
=True, related_name
='+',
326 verbose_name
=u
'Employé')
327 nom
= models
.CharField(max_length
=255)
328 prenom
= models
.CharField(max_length
=255, verbose_name
=u
'Prénom')
329 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
331 def __unicode__(self
):
332 return u
'%s %s' % (self
.prenom
, self
.nom
.upper())
337 STATUT_RESIDENCE_CHOICES
= (
339 ('expat', 'Expatrié'),
342 COMPTE_COMPTA_CHOICES
= (
348 class Dossier(DossierWorkflow
, rh
.Dossier_
):
349 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='%(app_label)s_dossiers')
350 employe
= models
.ForeignKey('Employe', db_column
='employe',
351 related_name
='%(app_label)s_dossiers',
352 verbose_name
=u
"Employé")
353 organisme_bstg_autre
= models
.CharField(max_length
=255,
354 verbose_name
=u
"Autre organisme",
355 help_text
="indiquer l'organisme ici s'il n'est pas dans la liste",
359 # Données antérieures de l'employé
360 statut_anterieur
= models
.ForeignKey(
361 rh
.Statut
, related_name
='+', null
=True, blank
=True,
362 verbose_name
=u
'Statut antérieur')
363 classement_anterieur
= models
.ForeignKey(
364 rh
.Classement
, related_name
='+', null
=True, blank
=True,
365 verbose_name
=u
'Classement précédent')
366 salaire_anterieur
= models
.DecimalField(
367 max_digits
=12, decimal_places
=2, null
=True, default
=None,
368 blank
=True, verbose_name
=u
'Salaire précédent')
369 devise_anterieur
= models
.ForeignKey(rh
.Devise
, related_name
='+',
370 null
=True, blank
=True)
371 type_contrat_anterieur
= models
.ForeignKey(rh
.TypeContrat
,
372 related_name
='+', null
=True, blank
=True,
373 verbose_name
=u
'Type contrat antérieur', )
375 # Données du titulaire précédent
376 employe_anterieur
= models
.ForeignKey(
377 rh
.Employe
, related_name
='+', null
=True, blank
=True,
378 verbose_name
=u
'Employé précédent')
379 statut_titulaire_anterieur
= models
.ForeignKey(
380 rh
.Statut
, related_name
='+', null
=True, blank
=True,
381 verbose_name
=u
'Statut titulaire précédent')
382 classement_titulaire_anterieur
= models
.ForeignKey(
383 rh
.Classement
, related_name
='+', null
=True, blank
=True,
384 verbose_name
=u
'Classement titulaire précédent')
385 salaire_titulaire_anterieur
= models
.DecimalField(
386 max_digits
=12, decimal_places
=2, default
=None, null
=True,
387 blank
=True, verbose_name
=u
'Salaire titulaire précédent')
388 devise_titulaire_anterieur
= models
.ForeignKey(rh
.Devise
, related_name
='+', null
=True, blank
=True)
391 salaire
= models
.DecimalField(max_digits
=13, decimal_places
=2,
392 verbose_name
=u
'Salaire de base',
393 null
=True, default
=None)
394 devise
= models
.ForeignKey(rh
.Devise
, default
=5, related_name
='+')
397 type_contrat
= models
.ForeignKey(rh
.TypeContrat
, related_name
='+')
398 contrat_date_debut
= models
.DateField(help_text
=HELP_TEXT_DATE
)
399 contrat_date_fin
= models
.DateField(null
=True, blank
=True,
400 help_text
=HELP_TEXT_DATE
)
403 justif_nouveau_statut_label
= u
'Justifier le statut que ce type de poste nécessite (national, expatrié, màd ou détachement)'
404 justif_nouveau_statut
= models
.TextField(verbose_name
=justif_nouveau_statut_label
, null
=True, blank
=True)
405 justif_nouveau_tmp_remplacement_label
= u
"Si l'employé effectue un remplacement temporaire, préciser"
406 justif_nouveau_tmp_remplacement
= models
.TextField(verbose_name
=justif_nouveau_tmp_remplacement_label
, null
=True, blank
=True)
407 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 "
408 justif_nouveau_salaire
= models
.TextField(verbose_name
=justif_nouveau_salaire_label
, null
=True, blank
=True)
409 justif_nouveau_commentaire_label
= u
"COMMENTAIRES ADDITIONNELS"
410 justif_nouveau_commentaire
= models
.TextField(verbose_name
=justif_nouveau_commentaire_label
, null
=True, blank
=True)
411 justif_rempl_type_contrat_label
= u
"Changement de type de contrat, ex : d'un CDD en CDI"
412 justif_rempl_type_contrat
= models
.TextField(verbose_name
=justif_rempl_type_contrat_label
, null
=True, blank
=True)
413 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"
414 justif_rempl_statut_employe
= models
.TextField(verbose_name
=justif_rempl_statut_employe_label
, null
=True, blank
=True)
415 justif_rempl_evaluation_label
= u
"L'évaluation de l'employé est-elle favorable? Préciser"
416 justif_rempl_evaluation
= models
.TextField(verbose_name
=justif_rempl_evaluation_label
, null
=True, blank
=True)
417 justif_rempl_salaire_label
= u
"Si le salaire de l'employé est modifié et/ou ne correspond pas à son classement, justifier"
418 justif_rempl_salaire
= models
.TextField(verbose_name
=justif_rempl_salaire_label
, null
=True, blank
=True)
419 justif_rempl_commentaire_label
= u
"COMMENTAIRES ADDITIONNELS"
420 justif_rempl_commentaire
= models
.TextField(verbose_name
=justif_rempl_commentaire_label
, null
=True, blank
=True)
423 compte_compta
= models
.CharField(max_length
=10, default
='aucun',
424 verbose_name
=u
'Compte comptabilité',
425 choices
=COMPTE_COMPTA_CHOICES
)
426 compte_courriel
= models
.BooleanField()
429 dae_numerisee
= models
.FileField(upload_to
='dae/dae_numerisee', storage
=UPLOAD_STORAGE
,
430 blank
=True, null
=True, verbose_name
="DAE numérisée")
433 objects
= DossierManager()
435 def __init__(self
, *args
, **kwargs
):
436 # Bouchon pour créer une date fictive necessaire pour valider un dossier
437 # à cause de l'héritage
438 super(rh
.Dossier_
, self
).__init__(*args
, **kwargs
)
439 super(DossierWorkflow
, self
).__init__(*args
, **kwargs
)
441 self
.date_debut
= datetime
.datetime
.today()
443 def __unicode__(self
):
444 return u
'[%s] %s - %s' % (self
.poste
.implantation
, self
.poste
.nom
, self
.employe
)
446 def est_importe(self
):
447 """Test si le dossier a déjà été importé"""
448 return dae
.ImportDossier
.objects
.filter(dae
=self
).exists()
451 if not self
.poste
.est_importe():
452 raise Exception('Le poste de cette DAE doît être importé')
455 def taux_devise(self
):
456 if self
.devise
.code
== 'EUR':
458 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee')
459 if len(liste_taux
) == 0:
460 raise DeviseException(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.poste
.implantation
))
462 return liste_taux
[0].taux
464 def get_salaire_anterieur_euros(self
):
465 if self
.devise_anterieur
.code
== 'EUR':
468 liste_taux
= self
.devise_anterieur
.tauxchange_set
.order_by('-annee')
469 if len(liste_taux
) == 0:
470 raise DeviseException(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise_anterieur
, self
.poste
.implantation
))
471 tx
= liste_taux
[0].taux
472 return (float)(tx
) * (float)(self
.salaire_anterieur
)
474 def get_salaire_titulaire_anterieur_euros(self
):
475 if self
.devise_titulaire_anterieur
is None:
477 if self
.devise_titulaire_anterieur
.code
== 'EUR':
480 liste_taux
= self
.devise_titulaire_anterieur
.tauxchange_set
.order_by('-annee')
481 if len(liste_taux
) == 0:
482 raise DeviseException(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise_titulaire_anterieur
, self
.poste
.implantation
))
483 tx
= liste_taux
[0].taux
484 return (float)(tx
) * (float)(self
.salaire_titulaire_anterieur
)
486 def get_salaire_euros(self
):
487 tx
= self
.taux_devise()
488 return (float)(tx
) * (float)(self
.salaire
)
490 def get_remunerations_brutes(self
):
494 4 Indemnité d'expatriation
495 5 Indemnité pour frais
496 6 Indemnité de logement
497 7 Indemnité de fonction
498 8 Indemnité de responsabilité
499 9 Indemnité de transport
500 10 Indemnité compensatrice
501 11 Indemnité de subsistance
502 12 Indemnité différentielle
503 13 Prime d'installation
506 16 Indemnité de départ
507 18 Prime de 13ième mois
510 ids
= [1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19]
511 return [r
for r
in self
.dae_remunerations
.all() if r
.type_id
in ids
]
513 def get_charges_salariales(self
):
515 20 Charges salariales ?
518 return [r
for r
in self
.dae_remunerations
.all() if r
.type_id
in ids
]
520 def get_total_charges_salariales(self
):
522 for r
in self
.get_charges_salariales():
523 total
+= r
.montant_euro()
526 def get_charges_patronales(self
):
528 17 Charges patronales
531 return [r
for r
in self
.dae_remunerations
.all() if r
.type_id
in ids
]
533 def get_total_charges_patronales(self
):
535 for r
in self
.get_charges_patronales():
536 total
+= r
.montant_euro()
539 def get_salaire_brut(self
):
541 somme des rémuérations brutes
544 for r
in self
.get_remunerations_brutes():
545 total
+= r
.montant_euro()
548 def get_salaire_net(self
):
550 salaire brut - charges salariales
553 for r
in self
.get_charges_salariales():
554 total_charges
+= r
.montant_euro()
555 return self
.get_salaire_brut() - total_charges
557 def get_couts_auf(self
):
559 salaire net + charges patronales
562 for r
in self
.get_charges_patronales():
563 total_charges
+= r
.montant_euro()
564 return self
.get_salaire_net() + total_charges
566 def get_remunerations_tierces(self
):
570 return [r
for r
in self
.dae_remunerations
.all() if r
.type_id
in (2, )]
572 def get_total_remunerations_tierces(self
):
574 for r
in self
.get_remunerations_tierces():
575 total
+= r
.montant_euro()
579 return self
.etat
in (DOSSIER_ETAT_REGION_FINALISATION
,
580 DOSSIER_ETAT_DRH_FINALISATION
,
581 DOSSIER_ETAT_FINALISE
)
584 # Tester l'enregistrement car les models.py sont importés au complet
585 if not reversion
.is_registered(Dossier
):
586 reversion
.register(Dossier
)
588 class DossierPiece(rh
.DossierPiece_
):
589 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
590 Ex.: Lettre de motivation.
594 class DossierComparaison(rh
.DossierComparaison_
):
596 Photo d'une comparaison salariale au moment de l'embauche.
603 class Remuneration(rh
.Remuneration_
):
608 class Contrat(rh
.Contrat_
):
611 # modèle de liaison entre les systèmes
613 class ImportDossier(models
.Model
):
614 dae
= models
.ForeignKey('dae.Dossier', related_name
='+')
615 rh
= models
.ForeignKey('rh.Dossier', related_name
='+')
617 class ImportPoste(models
.Model
):
618 dae
= models
.ForeignKey('dae.Poste', related_name
='+')
619 rh
= models
.ForeignKey('rh.Poste', related_name
='+')