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