fix prepopulation DAE from RH
[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 PostePieceForm = inlineformset_factory(
188 dae.Poste,
189 dae.PostePiece,
190 #formset=BaseInlineFormSetWithInitial,
191 )
192
193
194 DossierPieceForm = inlineformset_factory(dae.Dossier, dae.DossierPiece)
195
196
197 FinancementForm = inlineformset_factory(
198 dae.Poste,
199 dae.PosteFinancement,
200 formset=BaseInlineFormSetWithInitial,
201 extra=2
202 )
203
204
205 class DossierComparaisonForm(forms.ModelForm):
206
207 recherche = AutoCompleteSelectField('dossiers', required=False)
208 poste = forms.CharField(
209 max_length=255, widget=forms.TextInput(attrs={'size': '60'})
210 )
211
212 class Meta:
213 model = dae.DossierComparaison
214 exclude = ('dossier',)
215
216 DossierComparaisonFormSet = modelformset_factory(
217 dae.DossierComparaison, extra=3, max_num=3, form=DossierComparaisonForm
218 )
219
220
221 class PosteComparaisonForm(forms.ModelForm):
222
223 recherche = AutoCompleteSelectField('dae_postes', required=False)
224
225 class Meta:
226 model = dae.PosteComparaison
227 exclude = ('poste',)
228
229 PosteComparaisonFormSet = inlineformset_factory(
230 dae.Poste,
231 dae.PosteComparaison,
232 extra=3,
233 max_num=3,
234 form=PosteComparaisonForm,
235 #formset=BaseModelFormSetWithInitial,
236 formset=BaseInlineFormSetWithInitial,
237 )
238
239
240 class FlexibleRemunForm(forms.ModelForm):
241
242 montant_mensuel = forms.DecimalField(required=False)
243 montant = forms.DecimalField(required=True, label='Montant annuel')
244
245 class Meta:
246 model = dae.Remuneration
247
248 def clean_devise(self):
249 devise = self.cleaned_data['devise']
250 if devise.code == 'EUR':
251 return devise
252 implantation = ref.Implantation.objects.get(
253 id=self.data['implantation']
254 )
255 liste_taux = devise.tauxchange_set.order_by('-annee')
256 if len(liste_taux) == 0:
257 raise forms.ValidationError(
258 u"La devise %s n'a pas de taux pour l'implantation %s" %
259 (devise, implantation)
260 )
261 else:
262 return devise
263
264 RemunForm = inlineformset_factory(
265 dae.Dossier, dae.Remuneration, extra=5, form=FlexibleRemunForm
266 )
267
268
269 class PosteForm(forms.ModelForm):
270 """ Formulaire des postes. """
271
272 # On ne propose que les services actifs
273 service = forms.ModelChoiceField(
274 queryset=rh.Service.objects.all(), required=True
275 )
276
277 responsable = AutoCompleteSelectField('responsables', required=True)
278 #responsable = forms.ModelChoiceField(
279 # queryset=rh.Poste.objects.select_related(depth=1))
280
281 # La liste des choix est laissée vide. Voir __init__ pour la raison.
282 poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste",
283 choices=(), required=False)
284
285 valeur_point_min = forms.ModelChoiceField(
286 queryset=rh.ValeurPoint.actuelles.all(), required=False
287 )
288 valeur_point_max = forms.ModelChoiceField(
289 queryset=rh.ValeurPoint.actuelles.all(), required=False
290 )
291
292 class Meta:
293 model = dae.Poste
294 fields = ('type_intervention',
295 'poste', 'implantation', 'type_poste', 'service', 'nom',
296 'responsable', 'local', 'expatrie', 'mise_a_disposition',
297 'appel', 'date_debut', 'date_fin',
298 'regime_travail', 'regime_travail_nb_heure_semaine',
299 'classement_min', 'classement_max',
300 'valeur_point_min', 'valeur_point_max',
301 'devise_min', 'devise_max',
302 'salaire_min', 'salaire_max',
303 'indemn_expat_min', 'indemn_expat_max',
304 'indemn_fct_min', 'indemn_fct_max',
305 'charges_patronales_min', 'charges_patronales_max',
306 'autre_min', 'autre_max', 'devise_comparaison',
307 'comp_locale_min', 'comp_locale_max',
308 'comp_universite_min', 'comp_universite_max',
309 'comp_fonctionpub_min', 'comp_fonctionpub_max',
310 'comp_ong_min', 'comp_ong_max',
311 'comp_autre_min', 'comp_autre_max',
312 'justification',
313 )
314 widgets = dict(type_intervention=forms.RadioSelect(),
315 appel=forms.RadioSelect(),
316 nom=forms.TextInput(attrs={'size': 60},),
317 date_debut=admin_widgets.AdminDateWidget(),
318 date_fin=admin_widgets.AdminDateWidget(),
319 justification=forms.Textarea(attrs={'cols': 80},),
320 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
321 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
322 )
323
324 def __init__(self, *args, **kwargs):
325 """ Mise à jour dynamique du contenu du menu des postes.
326
327 Si on ne met le menu à jour de cette façon, à chaque instantiation du
328 formulaire, son contenu est mis en cache par le système et il ne
329 reflète pas les changements apportés par les ajouts, modifications,
330 etc...
331
332 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
333 car le "id" de chaque choix est spécial (voir _poste_choices).
334
335 """
336 request = kwargs.pop('request')
337 super(PosteForm, self).__init__(*args, **kwargs)
338 self.fields['poste'].choices = self._poste_choices(request)
339 self.fields['implantation'].choices = \
340 _implantation_choices(self, request)
341
342 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
343 if self.instance and self.instance.id is None:
344 dossiers = self.instance.get_dossiers()
345 if len(dossiers) > 0:
346 self.initial['service'] = dossiers[0].poste.service
347
348 def _poste_choices(self, request):
349 """ Menu déroulant pour les postes.
350
351 Constitué des postes de dae et des postes de rh_v1 qui n'ont pas
352 d'équivalent dans dae.
353
354 """
355 copies = dae.Poste.objects \
356 .ma_region_ou_service(request.user) \
357 .exclude(id_rh__isnull=True) \
358 .filter(etat=POSTE_ETAT_FINALISE)
359 id_copies = [p.id_rh_id for p in copies.all()]
360 rhv1 = rh.Poste.objects.ma_region_ou_service(request.user) \
361 .exclude(id__in=id_copies)
362 # Optimisation de la requête
363 rhv1 = rhv1.select_related(depth=1)
364
365 return [('', 'Nouveau poste')] + \
366 sorted([('rh-%s' % p.id, label_poste_display(p)) for p in rhv1],
367 key=lambda t: t[1])
368
369 def clean(self):
370 """
371 Validation conditionnelles de certains champs.
372 """
373 cleaned_data = self.cleaned_data
374
375 if cleaned_data.get("local") is False \
376 and cleaned_data.get("expatrie") is False:
377 msg = "Le poste doit au moins être ouvert localement " \
378 "ou aux expatriés"
379 self._errors["local"] = self.error_class([msg])
380 self._errors["expatrie"] = ''
381 raise forms.ValidationError(msg)
382
383 return cleaned_data
384
385 def save(self, *args, **kwargs):
386 kwargs2 = kwargs.copy()
387 kwargs2['commit'] = False
388 poste = super(PosteForm, self).save(*args, **kwargs2)
389 # id_rh
390 if 'commit' not in kwargs or kwargs['commit']:
391 if poste.id is None:
392 poste.date_creation = datetime.datetime.now()
393 poste.save()
394 return poste
395
396
397 class ChoosePosteForm(forms.ModelForm):
398 class Meta:
399 model = dae.Poste
400 fields = ('poste',)
401
402 # La liste des choix est laissée vide. Voir PosteForm.__init__.
403 poste = forms.ChoiceField(choices=(), required=False)
404
405 def __init__(self, request=None, *args, **kwargs):
406 super(ChoosePosteForm, self).__init__(*args, **kwargs)
407 self.fields['poste'].choices = self._poste_choices(request)
408
409 def _poste_choices(self, request):
410 """ Menu déroulant pour les postes. """
411 dae_ = dae.Poste.objects.ma_region_ou_service(request.user) \
412 .filter(id_rh__isnull=True)
413 copies = dae.Poste.objects.ma_region_ou_service(request.user) \
414 .exclude(id_rh__isnull=True)
415
416 return [('', '----------')] + \
417 sorted([('dae-%s' % p.id, unicode(p)) for p in dae_ | copies],
418 key=lambda t: t[1])
419
420
421 class EmployeForm(forms.ModelForm):
422 """ Formulaire des employés. """
423 class Meta:
424 model = dae.Employe
425 fields = ('employe', 'nom', 'prenom', 'genre')
426
427 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
428 employe = forms.ChoiceField(choices=(), required=False)
429
430 def __init__(self, *args, **kwargs):
431 """ Mise à jour dynamique du contenu du menu des employés. """
432 request = kwargs.pop('request', None)
433 super(EmployeForm, self).__init__(*args, **kwargs)
434 self.fields['employe'].choices = _employe_choices(self, request)
435
436
437 class DossierForm(forms.ModelForm):
438 """ Formulaire des dossiers. """
439 class Meta:
440 exclude = ('etat', 'employe', 'poste', 'date_debut',)
441 model = dae.Dossier
442 widgets = dict(statut_residence=forms.RadioSelect(),
443 contrat_date_debut=admin_widgets.AdminDateWidget(),
444 contrat_date_fin=admin_widgets.AdminDateWidget(),
445 )
446
447 def save(self, *args, **kwargs):
448 dossier = super(DossierForm, self).save(*args, **kwargs)
449 if dossier.id is None:
450 dossier.date_creation = datetime.datetime.now()
451 dossier.save()
452 return dossier
453
454 WF_HELP_TEXT = ""
455
456
457 class PosteWorkflowForm(WorkflowFormMixin):
458 bouton_libelles = POSTE_ETATS_BOUTONS
459
460 class Meta:
461 fields = ('etat', )
462 model = dae.Poste
463
464 def __init__(self, *args, **kwargs):
465 super(PosteWorkflowForm, self).__init__(*args, **kwargs)
466 self.fields['etat'].help_text = WF_HELP_TEXT
467
468
469 class DossierWorkflowForm(WorkflowFormMixin):
470 bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste...
471
472 class Meta:
473 fields = ('etat', )
474 model = dae.Dossier
475
476 def __init__(self, *args, **kwargs):
477 super(DossierWorkflowForm, self).__init__(*args, **kwargs)
478 self.fields['etat'].help_text = WF_HELP_TEXT
479 self._etat_initial = self.instance.etat
480
481 def save(self):
482 super(DossierWorkflowForm, self).save()
483 poste = self.instance.poste
484 if poste.etat == self._etat_initial:
485 poste.etat = self.instance.etat
486 poste.save()
487
488
489 class ContratForm(forms.ModelForm):
490
491 class Meta:
492 fields = ('type_contrat', 'fichier', )
493 model = dae.Contrat
494
495
496 class DAENumeriseeForm(forms.ModelForm):
497
498 class Meta:
499 model = dae.Dossier
500 fields = ('dae_numerisee',)
501
502
503 class DAEFinaliseesSearchForm(forms.Form):
504 q = forms.CharField(
505 label='Recherche', required=False,
506 widget=forms.TextInput(attrs={'size': 40})
507 )
508 importees = forms.ChoiceField(
509 label='Importation', required=False, choices=(
510 ('', ''),
511 ('oui', 'DAE importées seulement'),
512 ('non', 'DAE non-importées seulement'),
513 )
514 )