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