tests DAE poste
[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 try:
182 label = u"%s %s - %s [%s]" % (
183 annee, nom, poste.type_poste.categorie_emploi.nom, poste.id
184 )
185 except:
186 label = unicode(poste)
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.ModelForm):
390 class Meta:
391 model = dae.Poste
392 fields = ('poste',)
393
394 # La liste des choix est laissée vide. Voir PosteForm.__init__.
395 poste = forms.ChoiceField(choices=(), required=False)
396
397 def __init__(self, request=None, *args, **kwargs):
398 super(ChoosePosteForm, self).__init__(*args, **kwargs)
399 self.fields['poste'].choices = self._poste_choices(request)
400
401 def _poste_choices(self, request):
402 """ Menu déroulant pour les postes. """
403 dae_ = dae.Poste.objects.ma_region_ou_service(request.user) \
404 .filter(id_rh__isnull=True)
405 copies = dae.Poste.objects.ma_region_ou_service(request.user) \
406 .exclude(id_rh__isnull=True)
407
408 return [('', '----------')] + \
409 sorted([('dae-%s' % p.id, unicode(p)) for p in dae_ | copies],
410 key=lambda t: t[1])
411
412
413 class EmployeForm(forms.ModelForm):
414 """ Formulaire des employés. """
415 class Meta:
416 model = dae.Employe
417 fields = ('employe', 'nom', 'prenom', 'genre')
418
419 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
420 employe = forms.ChoiceField(choices=(), required=False)
421
422 def __init__(self, *args, **kwargs):
423 """ Mise à jour dynamique du contenu du menu des employés. """
424 request = kwargs.pop('request', None)
425 super(EmployeForm, self).__init__(*args, **kwargs)
426 self.fields['employe'].choices = _employe_choices(self, request)
427
428
429 class DossierForm(forms.ModelForm):
430 """ Formulaire des dossiers. """
431 class Meta:
432 exclude = ('etat', 'employe', 'poste', 'date_debut',)
433 model = dae.Dossier
434 widgets = dict(statut_residence=forms.RadioSelect(),
435 contrat_date_debut=admin_widgets.AdminDateWidget(),
436 contrat_date_fin=admin_widgets.AdminDateWidget(),
437 )
438
439 WF_HELP_TEXT = ""
440
441
442 class PosteWorkflowForm(WorkflowFormMixin):
443 bouton_libelles = POSTE_ETATS_BOUTONS
444
445 class Meta:
446 fields = ('etat', )
447 model = dae.Poste
448
449 def __init__(self, *args, **kwargs):
450 super(PosteWorkflowForm, self).__init__(*args, **kwargs)
451 self.fields['etat'].help_text = WF_HELP_TEXT
452
453
454 class DossierWorkflowForm(WorkflowFormMixin):
455 bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste...
456
457 class Meta:
458 fields = ('etat', )
459 model = dae.Dossier
460
461 def __init__(self, *args, **kwargs):
462 super(DossierWorkflowForm, self).__init__(*args, **kwargs)
463 self.fields['etat'].help_text = WF_HELP_TEXT
464 self._etat_initial = self.instance.etat
465
466 def save(self):
467 super(DossierWorkflowForm, self).save()
468 poste = self.instance.poste
469 if poste.etat == self._etat_initial:
470 poste.etat = self.instance.etat
471 poste.save()
472
473
474 class ContratForm(forms.ModelForm):
475
476 class Meta:
477 fields = ('type_contrat', 'fichier', )
478 model = dae.Contrat
479
480
481 class DAENumeriseeForm(forms.ModelForm):
482
483 class Meta:
484 model = dae.Dossier
485 fields = ('dae_numerisee',)
486
487
488 class DAEFinaliseesSearchForm(forms.Form):
489 q = forms.CharField(
490 label='Recherche', required=False,
491 widget=forms.TextInput(attrs={'size': 40})
492 )
493 importees = forms.ChoiceField(
494 label='Importation', required=False, choices=(
495 ('', ''),
496 ('oui', 'DAE importées seulement'),
497 ('non', 'DAE non-importées seulement'),
498 )
499 )