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