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