ajout validateur contre devise manquante pour implantation #1818
[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
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 def option_label(employe):
60 return "%s %s" % (employe.nom.upper(), employe.prenom.title())
61
62 return [('', 'Nouvel employé')] + \
63 sorted([('dae-%s' % p.id, option_label(p)) for p in dae_ | copies] +
64 [('rh-%s' % p.id, option_label(p)) for p in rhv1],
65 key=lambda t: t[1])
66
67
68 def label_poste_display(poste):
69 """Formate un visuel pour un poste dans une liste déroulante"""
70 label = u"%s - %s [%s]" %(poste.type_poste, poste.type_poste.famille_emploi.nom, poste.id)
71 return label
72
73 class PostePieceForm(inlineformset_factory(dae.Poste, dae.PostePiece)):
74 pass
75
76 class DossierPieceForm(inlineformset_factory(dae.Dossier, dae.DossierPiece)):
77 pass
78
79 class FinancementForm(inlineformset_factory(dae.Poste, dae.PosteFinancement, extra=2)):
80 pass
81
82
83 class DossierComparaisonForm(forms.ModelForm):
84
85 recherche = AutoCompleteSelectField('dossiers', required=False)
86 poste = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'size':'60'}))
87
88 class Model:
89 model = dae.DossierComparaison
90
91 class DossierComparaisonForm(inlineformset_factory(dae.Dossier, dae.DossierComparaison, extra=3, max_num=3, form=DossierComparaisonForm)):
92 pass
93
94 class PosteComparaisonForm(forms.ModelForm):
95
96 recherche = AutoCompleteSelectField('postes', required=False)
97
98 class Model:
99 model = dae.PosteComparaison
100
101 class PosteComparaisonForm(inlineformset_factory(dae.Poste, dae.PosteComparaison, extra=3, max_num=3, form=PosteComparaisonForm)):
102 pass
103
104 class FlexibleRemunForm(forms.ModelForm):
105
106 montant_mensuel = forms.DecimalField(required=False)
107 montant = forms.DecimalField(required=True, label='Montant annuel')
108
109 class Meta:
110 model = dae.Remuneration
111
112 def clean_devise(self):
113 devise = self.cleaned_data['devise']
114 liste_taux = devise.tauxchange_set.order_by('-annee').filter(implantation=self.instance.dossier.poste.implantation)
115 if len(liste_taux) == 0:
116 raise forms.ValidationError(u"La devise %s n'a pas de taux pour l'implantation %s" % (devise, self.instance.dossier.poste.implantation))
117 else:
118 return devise
119
120
121 class RemunForm(inlineformset_factory(dae.Dossier, dae.Remuneration, extra=5, form=FlexibleRemunForm)):
122 pass
123
124 class PosteForm(forms.ModelForm):
125 """ Formulaire des postes. """
126
127 # On ne propose que les services actifs
128 service = forms.ModelChoiceField(queryset=rh.Service.objects.filter(actif=True), required=True)
129
130 responsable=AutoCompleteSelectField('responsables', required=True)
131 #responsable = forms.ModelChoiceField(
132 # queryset=rh.Poste.objects.select_related(depth=1))
133
134 # La liste des choix est laissée vide. Voir __init__ pour la raison.
135 poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste",
136 choices=(), required=False)
137
138 valeur_point_min = forms.ModelChoiceField(queryset=rh.ValeurPoint.actuelles.all(), required=False)
139 valeur_point_max = forms.ModelChoiceField(queryset=rh.ValeurPoint.actuelles.all(), required=False)
140
141
142 class Meta:
143 model = dae.Poste
144 exclude = ('actif', )
145 fields = ('type_intervention',
146 'poste', 'implantation', 'type_poste', 'service', 'nom',
147 'responsable', 'local', 'expatrie', 'mise_a_disposition',
148 'appel', 'date_debut', 'date_fin',
149 'regime_travail', 'regime_travail_nb_heure_semaine',
150 'classement_min', 'classement_max',
151 'valeur_point_min', 'valeur_point_max',
152 'devise_min', 'devise_max',
153 'salaire_min', 'salaire_max',
154 'indemn_expat_min', 'indemn_expat_max',
155 'indemn_fct_min', 'indemn_fct_max',
156 'charges_patronales_min', 'charges_patronales_max',
157 'autre_min', 'autre_max', 'devise_comparaison',
158 'comp_locale_min', 'comp_locale_max',
159 'comp_universite_min', 'comp_universite_max',
160 'comp_fonctionpub_min', 'comp_fonctionpub_max',
161 'comp_ong_min', 'comp_ong_max',
162 'comp_autre_min', 'comp_autre_max',
163 'justification',
164 )
165 widgets = dict(type_intervention=forms.RadioSelect(),
166 appel=forms.RadioSelect(),
167 nom=forms.TextInput(attrs={'size': 60},),
168 date_debut=admin_widgets.AdminDateWidget(),
169 date_fin=admin_widgets.AdminDateWidget(),
170 justification=forms.Textarea(attrs={'cols': 80},),
171 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
172 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
173 )
174
175 def __init__(self, *args, **kwargs):
176 """ Mise à jour dynamique du contenu du menu des postes.
177
178 Si on ne met le menu à jour de cette façon, à chaque instantiation du
179 formulaire, son contenu est mis en cache par le système et il ne
180 reflète pas les changements apportés par les ajouts, modifications,
181 etc...
182
183 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
184 car le "id" de chaque choix est spécial (voir _poste_choices).
185
186 """
187 request = kwargs.pop('request')
188 super(PosteForm, self).__init__(*args, **kwargs)
189 self.fields['poste'].choices = self._poste_choices(request)
190 self.fields['implantation'].choices = _implantation_choices(self, request)
191
192 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
193 if self.instance and self.instance.id is None:
194 dossiers = self.instance.get_dossiers()
195 if len(dossiers) > 0:
196 self.initial['service'] = dossiers[0].service_id
197 self.initial['nom'] = "%s %s" % (self.initial['nom'], self.instance.get_complement_nom())
198
199
200 def _poste_choices(self, request):
201 """ Menu déroulant pour les postes.
202
203 Constitué des postes de dae et des postes de rh_v1 qui n'ont pas
204 d'équivalent dans dae.
205
206 """
207 dae_ = dae.Poste.objects.ma_region_ou_service(request.user).filter(actif=True, id_rh__isnull=True)
208 copies = dae.Poste.objects.ma_region_ou_service(request.user).exclude(id_rh__isnull=True)
209 id_copies = [p.id_rh_id for p in copies.all()]
210 rhv1 = rh.Poste.objects.ma_region_ou_service(request.user).filter(actif=True).exclude(id__in=id_copies)
211 # Optimisation de la requête
212 rhv1 = rhv1.select_related(depth=1)
213
214 return [('', 'Nouveau poste')] + \
215 sorted([('dae-%s' % p.id, label_poste_display(p)) for p in dae_ | copies] +
216 [('rh-%s' % p.id, label_poste_display(p)) for p in rhv1],
217 key=lambda t: t[1])
218
219 def clean(self):
220 """
221 Validation conditionnelles de certains champs.
222 """
223 cleaned_data = self.cleaned_data
224
225 # Gestion de la mise à disposition
226 mise_a_disposition = cleaned_data.get("mise_a_disposition")
227 valeur_point_min = cleaned_data.get("valeur_point_min")
228 valeur_point_max = cleaned_data.get("valeur_point_max")
229 if mise_a_disposition is False and (valeur_point_min is None or valeur_point_max is None):
230 msg = u"Ce champ est obligatoire."
231 self._errors["valeur_point_min"] = self.error_class([msg])
232 self._errors["valeur_point_max"] = self.error_class([msg])
233 raise forms.ValidationError("Les valeurs de point sont vides")
234
235 if cleaned_data.get("local") is False and cleaned_data.get("expatrie") is False:
236 msg = "Le poste doit au moins être ouvert localement ou aux expatriés"
237 self._errors["local"] = self.error_class([msg])
238 self._errors["expatrie"] = ''
239 raise forms.ValidationError(msg)
240
241
242 return cleaned_data
243
244
245
246 def save(self, *args, **kwargs):
247 kwargs2 = kwargs.copy()
248 kwargs2['commit'] = False
249 poste = super(PosteForm, self).save(*args, **kwargs2)
250 # id_rh
251 if 'commit' not in kwargs or kwargs['commit']:
252 poste.save()
253 return poste
254
255
256 class ChoosePosteForm(forms.ModelForm):
257 class Meta:
258 model = dae.Poste
259 fields = ('poste',)
260
261 # La liste des choix est laissée vide. Voir PosteForm.__init__.
262 poste = forms.ChoiceField(choices=(), required=False)
263
264 def __init__(self, request=None, *args, **kwargs):
265 super(ChoosePosteForm, self).__init__(*args, **kwargs)
266 self.fields['poste'].choices = self._poste_choices(request)
267
268 def _poste_choices(self, request):
269 """ Menu déroulant pour les postes. """
270 dae_ = dae.Poste.objects.ma_region_ou_service(request.user).filter(id_rh__isnull=True)
271 copies = dae.Poste.objects.ma_region_ou_service(request.user).exclude(id_rh__isnull=True)
272 id_copies = [p.id_rh_id for p in copies.all()]
273
274 return [('', '----------')] + \
275 sorted([('dae-%s' % p.id, unicode(p)) for p in dae_ | copies],
276 key=lambda t: t[1])
277
278
279 class EmployeForm(forms.ModelForm):
280 """ Formulaire des employés. """
281 class Meta:
282 model = dae.Employe
283 fields = ('employe', 'nom', 'prenom', 'genre')
284
285 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
286 employe = forms.ChoiceField(choices=(), required=False)
287
288 def __init__(self, *args, **kwargs):
289 """ Mise à jour dynamique du contenu du menu des employés. """
290 request = kwargs.pop('request', None)
291 super(EmployeForm, self).__init__(*args, **kwargs)
292 self.fields['employe'].choices = _employe_choices(self, request)
293
294
295
296 class DossierForm(forms.ModelForm):
297 """ Formulaire des dossiers. """
298 class Meta:
299 exclude= ('etat', )
300 model = dae.Dossier
301 widgets = dict(statut_residence=forms.RadioSelect(),
302 contrat_date_debut=admin_widgets.AdminDateWidget(),
303 contrat_date_fin=admin_widgets.AdminDateWidget(),
304 )
305
306 WF_HELP_TEXT = ""
307
308 class PosteWorkflowForm(WorkflowFormMixin):
309 bouton_libelles = POSTE_ETATS_BOUTONS
310 class Meta:
311 fields = ('etat', )
312 model = dae.Poste
313
314 def __init__(self, *args, **kwargs):
315 super(self.__class__, self).__init__(*args, **kwargs)
316 self.fields['etat'].help_text = WF_HELP_TEXT
317
318
319 class DossierWorkflowForm(WorkflowFormMixin):
320 bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste...
321 class Meta:
322 fields = ('etat', )
323 model = dae.Dossier
324
325 def __init__(self, *args, **kwargs):
326 super(self.__class__, self).__init__(*args, **kwargs)
327 self.fields['etat'].help_text = WF_HELP_TEXT
328