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