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