[#2416] Ne pas se baser sur l'implantation pour déterminer le taux de change.
[auf_rh_dae.git] / project / dae / forms.py
1 # -*- encoding: utf-8 -*-
2
3 from django.db.models import Q, Max
4 from django import forms
5 from django.forms.models import inlineformset_factory, modelformset_factory
6 from django.contrib.admin import widgets as admin_widgets
7 from ajax_select.fields import AutoCompleteSelectField
8 from auf.django.workflow.forms import WorkflowFormMixin
9 from datamaster_modeles import models as ref
10 from dae import models as dae
11 from utils import get_employe_from_user, is_user_dans_services_centraux
12 from rh_v1 import models as rh
13 from workflow import grp_drh, POSTE_ETATS_BOUTONS
14
15 def _implantation_choices(obj, request):
16 # TRAITEMENT NORMAL
17 employe = get_employe_from_user(request.user)
18 # SERVICE
19 if is_user_dans_services_centraux(request.user):
20 q = Q(**{ 'id' : employe.implantation_id })
21 # REGION
22 else:
23 q = Q(**{ 'region' : employe.implantation.region })
24
25 # TRAITEMENT DRH
26 if grp_drh in request.user.groups.all():
27 q = Q()
28 return [('', '----------')] + [(i.id, unicode(i), )for i in ref.Implantation.objects.filter(q)]
29
30 def _employe_choices(obj, request):
31 # TRAITEMENT NORMAL
32 employe = get_employe_from_user(request.user)
33 # SERVICE
34 if is_user_dans_services_centraux(request.user):
35 q_dae_region_service = Q(poste__implantation=employe.implantation)
36 q_rh_region_service = Q(implantation1=employe.implantation) | Q(implantation2=employe.implantation)
37 # REGION
38 else:
39 q_dae_region_service = Q(poste__implantation__region=employe.implantation.region)
40 q_rh_region_service = Q(implantation1__region=employe.implantation.region) | Q(implantation2__region=employe.implantation.region)
41 # TRAITEMENT DRH
42 if grp_drh in request.user.groups.all():
43 q_dae_region_service = Q()
44 q_rh_region_service = Q()
45
46 # On filtre les employes avec les droits régionaux et on s'assure que c'est bien le dernier dossier en date pour sortir l'employe
47 # On retient un employé qui travaille présentement dans la même région que le user connecté.
48 dossiers_regionaux_ids = [d.id for d in dae.Dossier.objects.filter(q_dae_region_service)]
49 employes_ids = [d['employe'] for d in dae.Dossier.objects.values('employe').annotate(dernier_dossier=Max('id')) if d['dernier_dossier'] in dossiers_regionaux_ids]
50 dae_employe = dae.Employe.objects.filter(id__in=employes_ids)
51 dae_ = dae_employe.filter(id_rh__isnull=True)
52 copies = dae_employe.filter(Q(id_rh__isnull=False))
53 id_copies = [p.id_rh_id for p in copies.all()]
54
55 dossiers_regionaux_ids = [d.id for d in rh.Dossier.objects.filter(q_rh_region_service)]
56 employes_ids = [d['employe'] for d in rh.Dossier.objects.values('employe').annotate(dernier_dossier=Max('id')) if d['dernier_dossier'] in dossiers_regionaux_ids]
57 rhv1 = rh.Employe.objects.filter(id__in=employes_ids).exclude(id__in=id_copies)
58
59 # On ajoute les nouveaux Employés DAE qui ont été crées, mais qui n'ont pas de Dossier associés
60 employes_avec_dae = [d.employe_id for d in dae.Dossier.objects.all()]
61 employes_orphelins = dae.Employe.objects.exclude(id__in=employes_avec_dae)
62
63
64 def option_label(employe):
65 return "%s %s" % (employe.nom.upper(), employe.prenom.title())
66
67 return [('', 'Nouvel employé')] + \
68 sorted([('dae-%s' % p.id, option_label(p)) for p in dae_ | copies | employes_orphelins] +
69 [('rh-%s' % p.id, option_label(p)) for p in rhv1],
70 key=lambda t: t[1])
71
72 def label_poste_display(poste):
73 """Formate un visuel pour un poste dans une liste déroulante"""
74 label = u"%s - %s [%s]" %(poste.type_poste, poste.type_poste.famille_emploi.nom, poste.id)
75 return label
76
77 PostePieceForm = inlineformset_factory(dae.Poste, dae.PostePiece)
78 DossierPieceForm = inlineformset_factory(dae.Dossier, dae.DossierPiece)
79 FinancementForm = inlineformset_factory(dae.Poste, dae.PosteFinancement, extra=2)
80
81 class DossierComparaisonForm(forms.ModelForm):
82
83 recherche = AutoCompleteSelectField('dossiers', required=False)
84 poste = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'size':'60'}))
85
86 class Meta:
87 model = dae.DossierComparaison
88 exclude = ('dossier',)
89
90 DossierComparaisonFormSet = modelformset_factory(
91 dae.DossierComparaison, extra=3, max_num=3, form=DossierComparaisonForm
92 )
93
94 class PosteComparaisonForm(forms.ModelForm):
95
96 recherche = AutoCompleteSelectField('postes', required=False)
97
98 class Meta:
99 model = dae.PosteComparaison
100 exclude = ('poste',)
101
102 PosteComparaisonFormSet = modelformset_factory(
103 dae.PosteComparaison, extra=3, max_num=3, form=PosteComparaisonForm
104 )
105
106 class FlexibleRemunForm(forms.ModelForm):
107
108 montant_mensuel = forms.DecimalField(required=False)
109 montant = forms.DecimalField(required=True, label='Montant annuel')
110
111 class Meta:
112 model = dae.Remuneration
113
114 def clean_devise(self):
115 devise = self.cleaned_data['devise']
116 if devise.code == 'EUR':
117 return devise
118 implantation = ref.Implantation.objects.get(id=self.data['implantation'])
119 liste_taux = devise.tauxchange_set.order_by('-annee')
120 if len(liste_taux) == 0:
121 raise forms.ValidationError(u"La devise %s n'a pas de taux pour l'implantation %s" % (devise, implantation))
122 else:
123 return devise
124
125 RemunForm = inlineformset_factory(
126 dae.Dossier, dae.Remuneration, extra=5, form=FlexibleRemunForm
127 )
128
129 class PosteForm(forms.ModelForm):
130 """ Formulaire des postes. """
131
132 # On ne propose que les services actifs
133 service = forms.ModelChoiceField(queryset=rh.Service.objects.filter(actif=True), required=True)
134
135 responsable=AutoCompleteSelectField('responsables', required=True)
136 #responsable = forms.ModelChoiceField(
137 # queryset=rh.Poste.objects.select_related(depth=1))
138
139 # La liste des choix est laissée vide. Voir __init__ pour la raison.
140 poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste",
141 choices=(), required=False)
142
143 valeur_point_min = forms.ModelChoiceField(queryset=rh.ValeurPoint.actuelles.all(), required=False)
144 valeur_point_max = forms.ModelChoiceField(queryset=rh.ValeurPoint.actuelles.all(), required=False)
145
146
147 class Meta:
148 model = dae.Poste
149 exclude = ('actif', )
150 fields = ('type_intervention',
151 'poste', 'implantation', 'type_poste', 'service', 'nom',
152 'responsable', 'local', 'expatrie', 'mise_a_disposition',
153 'appel', 'date_debut', 'date_fin',
154 'regime_travail', 'regime_travail_nb_heure_semaine',
155 'classement_min', 'classement_max',
156 'valeur_point_min', 'valeur_point_max',
157 'devise_min', 'devise_max',
158 'salaire_min', 'salaire_max',
159 'indemn_expat_min', 'indemn_expat_max',
160 'indemn_fct_min', 'indemn_fct_max',
161 'charges_patronales_min', 'charges_patronales_max',
162 'autre_min', 'autre_max', 'devise_comparaison',
163 'comp_locale_min', 'comp_locale_max',
164 'comp_universite_min', 'comp_universite_max',
165 'comp_fonctionpub_min', 'comp_fonctionpub_max',
166 'comp_ong_min', 'comp_ong_max',
167 'comp_autre_min', 'comp_autre_max',
168 'justification',
169 )
170 widgets = dict(type_intervention=forms.RadioSelect(),
171 appel=forms.RadioSelect(),
172 nom=forms.TextInput(attrs={'size': 60},),
173 date_debut=admin_widgets.AdminDateWidget(),
174 date_fin=admin_widgets.AdminDateWidget(),
175 justification=forms.Textarea(attrs={'cols': 80},),
176 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
177 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
178 )
179
180 def __init__(self, *args, **kwargs):
181 """ Mise à jour dynamique du contenu du menu des postes.
182
183 Si on ne met le menu à jour de cette façon, à chaque instantiation du
184 formulaire, son contenu est mis en cache par le système et il ne
185 reflète pas les changements apportés par les ajouts, modifications,
186 etc...
187
188 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
189 car le "id" de chaque choix est spécial (voir _poste_choices).
190
191 """
192 request = kwargs.pop('request')
193 super(PosteForm, self).__init__(*args, **kwargs)
194 self.fields['poste'].choices = self._poste_choices(request)
195 self.fields['implantation'].choices = _implantation_choices(self, request)
196
197 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
198 if self.instance and self.instance.id is None:
199 dossiers = self.instance.get_dossiers()
200 if len(dossiers) > 0:
201 self.initial['service'] = dossiers[0].service_id
202 self.initial['nom'] = "%s %s" % (self.initial['nom'], self.instance.get_complement_nom())
203
204
205 def _poste_choices(self, request):
206 """ Menu déroulant pour les postes.
207
208 Constitué des postes de dae et des postes de rh_v1 qui n'ont pas
209 d'équivalent dans dae.
210
211 """
212 dae_ = dae.Poste.objects.ma_region_ou_service(request.user).filter(actif=True, id_rh__isnull=True)
213 copies = dae.Poste.objects.ma_region_ou_service(request.user).exclude(id_rh__isnull=True)
214 id_copies = [p.id_rh_id for p in copies.all()]
215 rhv1 = rh.Poste.objects.ma_region_ou_service(request.user).filter(actif=True).exclude(id__in=id_copies)
216 # Optimisation de la requête
217 rhv1 = rhv1.select_related(depth=1)
218
219 return [('', 'Nouveau poste')] + \
220 sorted([('dae-%s' % p.id, label_poste_display(p)) for p in dae_ | copies] +
221 [('rh-%s' % p.id, label_poste_display(p)) for p in rhv1],
222 key=lambda t: t[1])
223
224 def clean(self):
225 """
226 Validation conditionnelles de certains champs.
227 """
228 cleaned_data = self.cleaned_data
229
230 # Gestion de la mise à disposition
231 mise_a_disposition = cleaned_data.get("mise_a_disposition")
232 valeur_point_min = cleaned_data.get("valeur_point_min")
233 valeur_point_max = cleaned_data.get("valeur_point_max")
234 if mise_a_disposition is False and (valeur_point_min is None or valeur_point_max is None):
235 msg = u"Ce champ est obligatoire."
236 self._errors["valeur_point_min"] = self.error_class([msg])
237 self._errors["valeur_point_max"] = self.error_class([msg])
238 raise forms.ValidationError("Les valeurs de point sont vides")
239
240 if cleaned_data.get("local") is False and cleaned_data.get("expatrie") is False:
241 msg = "Le poste doit au moins être ouvert localement ou aux expatriés"
242 self._errors["local"] = self.error_class([msg])
243 self._errors["expatrie"] = ''
244 raise forms.ValidationError(msg)
245
246
247 return cleaned_data
248
249
250
251 def save(self, *args, **kwargs):
252 kwargs2 = kwargs.copy()
253 kwargs2['commit'] = False
254 poste = super(PosteForm, self).save(*args, **kwargs2)
255 # id_rh
256 if 'commit' not in kwargs or kwargs['commit']:
257 poste.save()
258 return poste
259
260
261 class ChoosePosteForm(forms.ModelForm):
262 class Meta:
263 model = dae.Poste
264 fields = ('poste',)
265
266 # La liste des choix est laissée vide. Voir PosteForm.__init__.
267 poste = forms.ChoiceField(choices=(), required=False)
268
269 def __init__(self, request=None, *args, **kwargs):
270 super(ChoosePosteForm, self).__init__(*args, **kwargs)
271 self.fields['poste'].choices = self._poste_choices(request)
272
273 def _poste_choices(self, request):
274 """ Menu déroulant pour les postes. """
275 dae_ = dae.Poste.objects.ma_region_ou_service(request.user).filter(id_rh__isnull=True)
276 copies = dae.Poste.objects.ma_region_ou_service(request.user).exclude(id_rh__isnull=True)
277 id_copies = [p.id_rh_id for p in copies.all()]
278
279 return [('', '----------')] + \
280 sorted([('dae-%s' % p.id, unicode(p)) for p in dae_ | copies],
281 key=lambda t: t[1])
282
283
284 class EmployeForm(forms.ModelForm):
285 """ Formulaire des employés. """
286 class Meta:
287 model = dae.Employe
288 fields = ('employe', 'nom', 'prenom', 'genre')
289
290 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
291 employe = forms.ChoiceField(choices=(), required=False)
292
293 def __init__(self, *args, **kwargs):
294 """ Mise à jour dynamique du contenu du menu des employés. """
295 request = kwargs.pop('request', None)
296 super(EmployeForm, self).__init__(*args, **kwargs)
297 self.fields['employe'].choices = _employe_choices(self, request)
298
299
300 class DossierForm(forms.ModelForm):
301 """ Formulaire des dossiers. """
302 class Meta:
303 exclude= ('etat', )
304 model = dae.Dossier
305 widgets = dict(statut_residence=forms.RadioSelect(),
306 contrat_date_debut=admin_widgets.AdminDateWidget(),
307 contrat_date_fin=admin_widgets.AdminDateWidget(),
308 )
309
310 WF_HELP_TEXT = ""
311
312 class PosteWorkflowForm(WorkflowFormMixin):
313 bouton_libelles = POSTE_ETATS_BOUTONS
314 class Meta:
315 fields = ('etat', )
316 model = dae.Poste
317
318 def __init__(self, *args, **kwargs):
319 super(PosteWorkflowForm, self).__init__(*args, **kwargs)
320 self.fields['etat'].help_text = WF_HELP_TEXT
321
322
323 class DossierWorkflowForm(WorkflowFormMixin):
324 bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste...
325 class Meta:
326 fields = ('etat', )
327 model = dae.Dossier
328
329 def __init__(self, *args, **kwargs):
330 super(DossierWorkflowForm, self).__init__(*args, **kwargs)
331 self.fields['etat'].help_text = WF_HELP_TEXT
332 self._etat_initial = self.instance.etat
333
334 def save(self):
335 super(DossierWorkflowForm, self).save()
336 poste = self.instance.poste
337 if poste.etat == self._etat_initial:
338 poste.etat = self.instance.etat
339 poste.save()
340
341 class ContratForm(forms.ModelForm):
342
343 class Meta:
344 fields = ('type', 'fichier')
345 model = dae.Contrat
346