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