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