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