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