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