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