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