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