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