recherche postem
[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, Count
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, POSTE_ETAT_FINALISE
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 label = u"%s (%s) %s - %s [%s]" % (
181 annee,
182 poste.implantation.nom_court,
183 nom,
184 poste.type_poste.categorie_emploi.nom,
185 poste.id,
186 )
187 return label
188
189
190 PostePieceFormSet = inlineformset_factory(dae.Poste, dae.PostePiece,)
191 DossierPieceForm = inlineformset_factory(dae.Dossier, dae.DossierPiece)
192
193 # Ce formset est utilisé dans le cas de la création de poste prépopulé avec les
194 # données de RH
195 FinancementFormSetInitial = inlineformset_factory(
196 dae.Poste,
197 dae.PosteFinancement,
198 formset=BaseInlineFormSetWithInitial,
199 extra=2
200 )
201 FinancementFormSet = inlineformset_factory(
202 dae.Poste,
203 dae.PosteFinancement,
204 extra=2
205 )
206
207
208 class DossierComparaisonForm(forms.ModelForm):
209
210 recherche = AutoCompleteSelectField('dossiers', required=False)
211 poste = forms.CharField(
212 max_length=255, widget=forms.TextInput(attrs={'size': '60'})
213 )
214
215 class Meta:
216 model = dae.DossierComparaison
217 exclude = ('dossier',)
218
219 DossierComparaisonFormSet = modelformset_factory(
220 dae.DossierComparaison, extra=3, max_num=3, form=DossierComparaisonForm
221 )
222
223
224 class PosteComparaisonForm(forms.ModelForm):
225
226 recherche = AutoCompleteSelectField('dae_postes', required=False)
227
228 class Meta:
229 model = dae.PosteComparaison
230 exclude = ('poste',)
231
232 # Ce formset est utilisé dans le cas de la création de poste prépopulé avec les
233 # données de RH
234 PosteComparaisonFormSetInitial = inlineformset_factory(
235 dae.Poste,
236 dae.PosteComparaison,
237 extra=3,
238 max_num=3,
239 form=PosteComparaisonForm,
240 formset=BaseInlineFormSetWithInitial,
241 )
242 PosteComparaisonFormSet = inlineformset_factory(
243 dae.Poste,
244 dae.PosteComparaison,
245 extra=3,
246 max_num=3,
247 form=PosteComparaisonForm,
248 )
249
250
251 class FlexibleRemunForm(forms.ModelForm):
252
253 montant_mensuel = forms.DecimalField(required=False)
254 montant = forms.DecimalField(required=True, label='Montant annuel')
255
256 class Meta:
257 model = dae.Remuneration
258
259 def clean_devise(self):
260 devise = self.cleaned_data['devise']
261 if devise.code == 'EUR':
262 return devise
263 implantation = ref.Implantation.objects.get(
264 id=self.data['implantation']
265 )
266 liste_taux = devise.tauxchange_set.order_by('-annee')
267 if len(liste_taux) == 0:
268 raise forms.ValidationError(
269 u"La devise %s n'a pas de taux pour l'implantation %s" %
270 (devise, implantation)
271 )
272 else:
273 return devise
274
275 RemunForm = inlineformset_factory(
276 dae.Dossier, dae.Remuneration, extra=5, form=FlexibleRemunForm
277 )
278
279
280 class PosteForm(forms.ModelForm):
281 """ Formulaire des postes. """
282
283 # On ne propose que les services actifs
284 service = forms.ModelChoiceField(
285 queryset=rh.Service.objects.all(), required=True
286 )
287
288 responsable = AutoCompleteSelectField('responsables', required=True)
289 #responsable = forms.ModelChoiceField(
290 # queryset=rh.Poste.objects.select_related(depth=1))
291
292 # La liste des choix est laissée vide. Voir __init__ pour la raison.
293 poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste",
294 choices=(), required=False)
295
296 valeur_point_min = forms.ModelChoiceField(
297 queryset=rh.ValeurPoint.actuelles.all(), required=False
298 )
299 valeur_point_max = forms.ModelChoiceField(
300 queryset=rh.ValeurPoint.actuelles.all(), required=False
301 )
302
303 class Meta:
304 model = dae.Poste
305 fields = ('type_intervention',
306 'poste', 'implantation', 'type_poste', 'service', 'nom',
307 'responsable', 'local', 'expatrie', 'mise_a_disposition',
308 'appel', 'date_debut', 'date_fin',
309 'regime_travail', 'regime_travail_nb_heure_semaine',
310 'classement_min', 'classement_max',
311 'valeur_point_min', 'valeur_point_max',
312 'devise_min', 'devise_max',
313 'salaire_min', 'salaire_max',
314 'indemn_expat_min', 'indemn_expat_max',
315 'indemn_fct_min', 'indemn_fct_max',
316 'charges_patronales_min', 'charges_patronales_max',
317 'autre_min', 'autre_max', 'devise_comparaison',
318 'comp_locale_min', 'comp_locale_max',
319 'comp_universite_min', 'comp_universite_max',
320 'comp_fonctionpub_min', 'comp_fonctionpub_max',
321 'comp_ong_min', 'comp_ong_max',
322 'comp_autre_min', 'comp_autre_max',
323 'justification',
324 )
325 widgets = dict(type_intervention=forms.RadioSelect(),
326 appel=forms.RadioSelect(),
327 nom=forms.TextInput(attrs={'size': 60},),
328 date_debut=admin_widgets.AdminDateWidget(),
329 date_fin=admin_widgets.AdminDateWidget(),
330 justification=forms.Textarea(attrs={'cols': 80},),
331 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
332 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
333 )
334
335 def __init__(self, *args, **kwargs):
336 """ Mise à jour dynamique du contenu du menu des postes.
337
338 Si on ne met le menu à jour de cette façon, à chaque instantiation du
339 formulaire, son contenu est mis en cache par le système et il ne
340 reflète pas les changements apportés par les ajouts, modifications,
341 etc...
342
343 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
344 car le "id" de chaque choix est spécial (voir _poste_choices).
345
346 """
347 request = kwargs.pop('request')
348 super(PosteForm, self).__init__(*args, **kwargs)
349 self.fields['poste'].choices = self._poste_choices(request)
350
351 self.fields['implantation'].choices = \
352 _implantation_choices(self, request)
353
354 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
355 if self.instance and self.instance.id is None:
356 dossiers = self.instance.get_dossiers()
357 if len(dossiers) > 0:
358 self.initial['service'] = dossiers[0].poste.service
359
360 def _poste_choices(self, request):
361 """ Menu déroulant pour les postes.
362 Constitué des postes de RH
363 """
364 postes_rh = rh.Poste.objects.ma_region_ou_service(request.user).all()
365 postes_rh = postes_rh.select_related(depth=1)
366
367 return [('', 'Nouveau poste')] + \
368 sorted([('rh-%s' % p.id, label_poste_display(p)) for p in
369 postes_rh],
370 key=lambda t: t[1])
371
372 def clean(self):
373 """
374 Validation conditionnelles de certains champs.
375 """
376 cleaned_data = self.cleaned_data
377
378 if cleaned_data.get("local") is False \
379 and cleaned_data.get("expatrie") is False:
380 msg = "Le poste doit au moins être ouvert localement " \
381 "ou aux expatriés"
382 self._errors["local"] = self.error_class([msg])
383 self._errors["expatrie"] = ''
384 raise forms.ValidationError(msg)
385
386 return cleaned_data
387
388
389 class ChoosePosteForm(forms.Form):
390 class Meta:
391 fields = ('poste',)
392
393 # La liste des choix est laissée vide. Voir PosteForm.__init__.
394 postes_dae = forms.ChoiceField(choices=(), required=False)
395 postes_rh = forms.ChoiceField(choices=(), required=False)
396
397 def __init__(self, request=None, *args, **kwargs):
398 super(ChoosePosteForm, self).__init__(*args, **kwargs)
399 self.fields['postes_dae'].choices = self._poste_dae_choices(request)
400 self.fields['postes_rh'].choices = self._poste_rh_choices(request)
401
402 def _poste_dae_choices(self, request):
403 """ Menu déroulant pour les postes."""
404 postes_dae = dae.Poste.objects.ma_region_ou_service(request.user) \
405 .exclude(etat__in=(POSTE_ETAT_FINALISE, )) \
406 .annotate(num_dae=Count('dae_dossiers')) \
407 .filter(num_dae=0) \
408 .order_by('-date_debut')
409
410 return [('', '----------')] + \
411 [('dae-%s' % p.id, label_poste_display(p)) for p in postes_dae]
412
413 def _poste_rh_choices(self, request):
414 """ Menu déroulant pour les postes."""
415 postes_rh = rh.Poste.objects.ma_region_ou_service(request.user) \
416 .order_by('-date_debut')
417
418 return [('', '----------')] + \
419 [('rh-%s' % p.id, label_poste_display(p)) for p in postes_rh]
420
421
422 class EmployeForm(forms.ModelForm):
423 """ Formulaire des employés. """
424 class Meta:
425 model = dae.Employe
426 fields = ('employe', 'nom', 'prenom', 'genre')
427
428 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
429 employe = forms.ChoiceField(choices=(), required=False)
430
431 def __init__(self, *args, **kwargs):
432 """ Mise à jour dynamique du contenu du menu des employés. """
433 request = kwargs.pop('request', None)
434 super(EmployeForm, self).__init__(*args, **kwargs)
435 self.fields['employe'].choices = _employe_choices(self, request)
436
437
438 class DossierForm(forms.ModelForm):
439 """ Formulaire des dossiers. """
440 class Meta:
441 exclude = ('etat', 'employe', 'poste', 'date_debut',)
442 model = dae.Dossier
443 widgets = dict(statut_residence=forms.RadioSelect(),
444 contrat_date_debut=admin_widgets.AdminDateWidget(),
445 contrat_date_fin=admin_widgets.AdminDateWidget(),
446 )
447
448 WF_HELP_TEXT = ""
449
450
451 class PosteWorkflowForm(WorkflowFormMixin):
452 bouton_libelles = POSTE_ETATS_BOUTONS
453
454 class Meta:
455 fields = ('etat', )
456 model = dae.Poste
457
458 def __init__(self, *args, **kwargs):
459 super(PosteWorkflowForm, self).__init__(*args, **kwargs)
460 self.fields['etat'].help_text = WF_HELP_TEXT
461
462
463 class DossierWorkflowForm(WorkflowFormMixin):
464 bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste...
465
466 class Meta:
467 fields = ('etat', )
468 model = dae.Dossier
469
470 def __init__(self, *args, **kwargs):
471 super(DossierWorkflowForm, self).__init__(*args, **kwargs)
472 self.fields['etat'].help_text = WF_HELP_TEXT
473 self._etat_initial = self.instance.etat
474
475 def save(self):
476 super(DossierWorkflowForm, self).save()
477 poste = self.instance.poste
478 if poste.etat == self._etat_initial:
479 poste.etat = self.instance.etat
480 poste.save()
481
482
483 class ContratForm(forms.ModelForm):
484
485 class Meta:
486 fields = ('type_contrat', 'fichier', )
487 model = dae.Contrat
488
489
490 class DAENumeriseeForm(forms.ModelForm):
491
492 class Meta:
493 model = dae.Dossier
494 fields = ('dae_numerisee',)
495
496
497 class DAEFinaliseesSearchForm(forms.Form):
498 q = forms.CharField(
499 label='Recherche', required=False,
500 widget=forms.TextInput(attrs={'size': 40})
501 )
502 importees = forms.ChoiceField(
503 label='Importation', required=False, choices=(
504 ('', ''),
505 ('oui', 'DAE importées seulement'),
506 ('non', 'DAE non-importées seulement'),
507 )
508 )