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