[#2194] Faire suivre le poste lorsqu'un dossier change d'état.
[auf_rh_dae.git] / project / dae / forms.py
CommitLineData
5d680e84 1# -*- encoding: utf-8 -*-
ce110fb9 2
072820fc 3from django.db.models import Q, Max
5d680e84 4from django import forms
36341125 5from django.forms.models import inlineformset_factory
e88caaf0 6from django.contrib.admin import widgets as admin_widgets
afc204bf 7from ajax_select.fields import AutoCompleteSelectField
8fa94e8b 8from auf.django.workflow.forms import WorkflowFormMixin
5d680e84 9from datamaster_modeles import models as ref
5d680e84 10from dae import models as dae
d8cfc3d5 11from utils import get_employe_from_user, is_user_dans_services_centraux
5d680e84 12from rh_v1 import models as rh
56589624 13from workflow import grp_drh, POSTE_ETATS_BOUTONS
f258e4e7
OL
14
15def _implantation_choices(obj, request):
16 # TRAITEMENT NORMAL
17 employe = get_employe_from_user(request.user)
18 # SERVICE
d8cfc3d5 19 if is_user_dans_services_centraux(request.user):
f258e4e7
OL
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
30def _employe_choices(obj, request):
f258e4e7
OL
31 # TRAITEMENT NORMAL
32 employe = get_employe_from_user(request.user)
33 # SERVICE
d8cfc3d5 34 if is_user_dans_services_centraux(request.user):
072820fc
OL
35 q_dae_region_service = Q(poste__implantation=employe.implantation)
36 q_rh_region_service = Q(implantation1=employe.implantation) | Q(implantation2=employe.implantation)
f258e4e7
OL
37 # REGION
38 else:
072820fc
OL
39 q_dae_region_service = Q(poste__implantation__region=employe.implantation.region)
40 q_rh_region_service = Q(implantation1__region=employe.implantation.region) | Q(implantation2__region=employe.implantation.region)
f258e4e7
OL
41 # TRAITEMENT DRH
42 if grp_drh in request.user.groups.all():
072820fc
OL
43 q_dae_region_service = Q()
44 q_rh_region_service = Q()
f258e4e7 45
072820fc
OL
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))
f258e4e7 53 id_copies = [p.id_rh_id for p in copies.all()]
072820fc
OL
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]
f258e4e7
OL
57 rhv1 = rh.Employe.objects.filter(id__in=employes_ids).exclude(id__in=id_copies)
58
67c15007
OL
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
f258e4e7
OL
64 def option_label(employe):
65 return "%s %s" % (employe.nom.upper(), employe.prenom.title())
66
67 return [('', 'Nouvel employé')] + \
67c15007 68 sorted([('dae-%s' % p.id, option_label(p)) for p in dae_ | copies | employes_orphelins] +
f258e4e7
OL
69 [('rh-%s' % p.id, option_label(p)) for p in rhv1],
70 key=lambda t: t[1])
71
5d680e84 72
4bce4d24
OL
73def label_poste_display(poste):
74 """Formate un visuel pour un poste dans une liste déroulante"""
75 label = u"%s - %s [%s]" %(poste.type_poste, poste.type_poste.famille_emploi.nom, poste.id)
76 return label
9cb4de55
OL
77
78class PostePieceForm(inlineformset_factory(dae.Poste, dae.PostePiece)):
79 pass
80
81class DossierPieceForm(inlineformset_factory(dae.Dossier, dae.DossierPiece)):
82 pass
83
e2185d45 84class FinancementForm(inlineformset_factory(dae.Poste, dae.PosteFinancement, extra=2)):
151e7bd0
OL
85 pass
86
03b395db
OL
87
88class DossierComparaisonForm(forms.ModelForm):
89
90 recherche = AutoCompleteSelectField('dossiers', required=False)
91 poste = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'size':'60'}))
92
93 class Model:
94 model = dae.DossierComparaison
95
03b395db
OL
96class DossierComparaisonForm(inlineformset_factory(dae.Dossier, dae.DossierComparaison, extra=3, max_num=3, form=DossierComparaisonForm)):
97 pass
98
068d1462
OL
99class PosteComparaisonForm(forms.ModelForm):
100
101 recherche = AutoCompleteSelectField('postes', required=False)
102
103 class Model:
104 model = dae.PosteComparaison
105
106class PosteComparaisonForm(inlineformset_factory(dae.Poste, dae.PosteComparaison, extra=3, max_num=3, form=PosteComparaisonForm)):
107 pass
108
0a085c42
OL
109class 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
dc4b78a7
OL
117 def clean_devise(self):
118 devise = self.cleaned_data['devise']
67173010
OL
119 if devise.code == 'EUR':
120 return devise
a7186cbb
OL
121 implantation = ref.Implantation.objects.get(id=self.data['implantation'])
122 liste_taux = devise.tauxchange_set.order_by('-annee').filter(implantation=implantation)
dc4b78a7 123 if len(liste_taux) == 0:
a7186cbb 124 raise forms.ValidationError(u"La devise %s n'a pas de taux pour l'implantation %s" % (devise, implantation))
dc4b78a7
OL
125 else:
126 return devise
127
128
0a085c42
OL
129class RemunForm(inlineformset_factory(dae.Dossier, dae.Remuneration, extra=5, form=FlexibleRemunForm)):
130 pass
131
1b217058 132class PosteForm(forms.ModelForm):
5d680e84 133 """ Formulaire des postes. """
12c7f8a7 134
ea7adc69 135 # On ne propose que les services actifs
8899db43 136 service = forms.ModelChoiceField(queryset=rh.Service.objects.filter(actif=True), required=True)
ea7adc69 137
12c7f8a7
OL
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)
ea7adc69 145
12c7f8a7
OL
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)
3121c13c
OL
148
149
5d680e84
NC
150 class Meta:
151 model = dae.Poste
b15bf543 152 exclude = ('actif', )
c3be904d
OL
153 fields = ('type_intervention',
154 'poste', 'implantation', 'type_poste', 'service', 'nom',
154677c3 155 'responsable', 'local', 'expatrie', 'mise_a_disposition',
b15bf543 156 'appel', 'date_debut', 'date_fin',
5d680e84
NC
157 'regime_travail', 'regime_travail_nb_heure_semaine',
158 'classement_min', 'classement_max',
159 'valeur_point_min', 'valeur_point_max',
3d627bfd 160 'devise_min', 'devise_max',
5f61bccb
OL
161 'salaire_min', 'salaire_max',
162 'indemn_expat_min', 'indemn_expat_max',
163 'indemn_fct_min', 'indemn_fct_max',
164 'charges_patronales_min', 'charges_patronales_max',
5d680e84
NC
165 'autre_min', 'autre_max', 'devise_comparaison',
166 'comp_locale_min', 'comp_locale_max',
167 'comp_universite_min', 'comp_universite_max',
168 'comp_fonctionpub_min', 'comp_fonctionpub_max',
169 'comp_ong_min', 'comp_ong_max',
8fa94e8b 170 'comp_autre_min', 'comp_autre_max',
2e092e0c 171 'justification',
8fa94e8b 172 )
c3be904d
OL
173 widgets = dict(type_intervention=forms.RadioSelect(),
174 appel=forms.RadioSelect(),
3d627bfd 175 nom=forms.TextInput(attrs={'size': 60},),
e88caaf0
OL
176 date_debut=admin_widgets.AdminDateWidget(),
177 date_fin=admin_widgets.AdminDateWidget(),
2e092e0c 178 justification=forms.Textarea(attrs={'cols': 80},),
3d627bfd 179 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
180 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
181 )
5d680e84 182
c2458db6 183 def __init__(self, *args, **kwargs):
5d680e84
NC
184 """ Mise à jour dynamique du contenu du menu des postes.
185
186 Si on ne met le menu à jour de cette façon, à chaque instantiation du
187 formulaire, son contenu est mis en cache par le système et il ne
188 reflète pas les changements apportés par les ajouts, modifications,
189 etc...
190
139686f2
NC
191 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
192 car le "id" de chaque choix est spécial (voir _poste_choices).
193
5d680e84 194 """
c2458db6 195 request = kwargs.pop('request')
5d680e84 196 super(PosteForm, self).__init__(*args, **kwargs)
f258e4e7
OL
197 self.fields['poste'].choices = self._poste_choices(request)
198 self.fields['implantation'].choices = _implantation_choices(self, request)
5d680e84 199
cc3098d0
OL
200 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
201 if self.instance and self.instance.id is None:
202 dossiers = self.instance.get_dossiers()
203 if len(dossiers) > 0:
204 self.initial['service'] = dossiers[0].service_id
9508a5b8
OL
205 self.initial['nom'] = "%s %s" % (self.initial['nom'], self.instance.get_complement_nom())
206
cc3098d0 207
f258e4e7 208 def _poste_choices(self, request):
5d680e84
NC
209 """ Menu déroulant pour les postes.
210
211 Constitué des postes de dae et des postes de rh_v1 qui n'ont pas
212 d'équivalent dans dae.
213
214 """
f258e4e7
OL
215 dae_ = dae.Poste.objects.ma_region_ou_service(request.user).filter(actif=True, id_rh__isnull=True)
216 copies = dae.Poste.objects.ma_region_ou_service(request.user).exclude(id_rh__isnull=True)
5d680e84 217 id_copies = [p.id_rh_id for p in copies.all()]
f258e4e7 218 rhv1 = rh.Poste.objects.ma_region_ou_service(request.user).filter(actif=True).exclude(id__in=id_copies)
139686f2
NC
219 # Optimisation de la requête
220 rhv1 = rhv1.select_related(depth=1)
5d680e84 221
98d51b59 222 return [('', 'Nouveau poste')] + \
4bce4d24
OL
223 sorted([('dae-%s' % p.id, label_poste_display(p)) for p in dae_ | copies] +
224 [('rh-%s' % p.id, label_poste_display(p)) for p in rhv1],
5d680e84 225 key=lambda t: t[1])
3ed49093 226
4dd75e7b
OL
227 def clean(self):
228 """
229 Validation conditionnelles de certains champs.
230 """
231 cleaned_data = self.cleaned_data
232
233 # Gestion de la mise à disposition
234 mise_a_disposition = cleaned_data.get("mise_a_disposition")
235 valeur_point_min = cleaned_data.get("valeur_point_min")
236 valeur_point_max = cleaned_data.get("valeur_point_max")
237 if mise_a_disposition is False and (valeur_point_min is None or valeur_point_max is None):
238 msg = u"Ce champ est obligatoire."
239 self._errors["valeur_point_min"] = self.error_class([msg])
240 self._errors["valeur_point_max"] = self.error_class([msg])
241 raise forms.ValidationError("Les valeurs de point sont vides")
f42c6e20 242
f42c6e20
OL
243 if cleaned_data.get("local") is False and cleaned_data.get("expatrie") is False:
244 msg = "Le poste doit au moins être ouvert localement ou aux expatriés"
245 self._errors["local"] = self.error_class([msg])
246 self._errors["expatrie"] = ''
247 raise forms.ValidationError(msg)
248
249
4dd75e7b
OL
250 return cleaned_data
251
252
253
494ff2be
NC
254 def save(self, *args, **kwargs):
255 kwargs2 = kwargs.copy()
256 kwargs2['commit'] = False
257 poste = super(PosteForm, self).save(*args, **kwargs2)
258 # id_rh
259 if 'commit' not in kwargs or kwargs['commit']:
260 poste.save()
261 return poste
262
3ed49093 263
139686f2
NC
264class ChoosePosteForm(forms.ModelForm):
265 class Meta:
266 model = dae.Poste
267 fields = ('poste',)
268
269 # La liste des choix est laissée vide. Voir PosteForm.__init__.
270 poste = forms.ChoiceField(choices=(), required=False)
271
4ee6d70a 272 def __init__(self, request=None, *args, **kwargs):
139686f2 273 super(ChoosePosteForm, self).__init__(*args, **kwargs)
4ee6d70a 274 self.fields['poste'].choices = self._poste_choices(request)
139686f2 275
4ee6d70a 276 def _poste_choices(self, request):
139686f2 277 """ Menu déroulant pour les postes. """
4ee6d70a
OL
278 dae_ = dae.Poste.objects.ma_region_ou_service(request.user).filter(id_rh__isnull=True)
279 copies = dae.Poste.objects.ma_region_ou_service(request.user).exclude(id_rh__isnull=True)
139686f2
NC
280 id_copies = [p.id_rh_id for p in copies.all()]
281
98d51b59 282 return [('', '----------')] + \
139686f2
NC
283 sorted([('dae-%s' % p.id, unicode(p)) for p in dae_ | copies],
284 key=lambda t: t[1])
285
286
139686f2
NC
287class EmployeForm(forms.ModelForm):
288 """ Formulaire des employés. """
289 class Meta:
290 model = dae.Employe
291 fields = ('employe', 'nom', 'prenom', 'genre')
292
293 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
294 employe = forms.ChoiceField(choices=(), required=False)
295
ac6235f6 296 def __init__(self, *args, **kwargs):
139686f2 297 """ Mise à jour dynamique du contenu du menu des employés. """
ac6235f6 298 request = kwargs.pop('request', None)
139686f2 299 super(EmployeForm, self).__init__(*args, **kwargs)
f258e4e7 300 self.fields['employe'].choices = _employe_choices(self, request)
139686f2 301
139686f2
NC
302
303
304class DossierForm(forms.ModelForm):
305 """ Formulaire des dossiers. """
306 class Meta:
b1baa306 307 exclude= ('etat', )
139686f2 308 model = dae.Dossier
4d25e2ba 309 widgets = dict(statut_residence=forms.RadioSelect(),
0e0aeb7e
OL
310 contrat_date_debut=admin_widgets.AdminDateWidget(),
311 contrat_date_fin=admin_widgets.AdminDateWidget(),
4d25e2ba 312 )
e6f52402 313
3799cafc 314WF_HELP_TEXT = ""
e0b93e3a 315
e6f52402 316class PosteWorkflowForm(WorkflowFormMixin):
56589624 317 bouton_libelles = POSTE_ETATS_BOUTONS
e6f52402
OL
318 class Meta:
319 fields = ('etat', )
320 model = dae.Poste
56589624 321
e0b93e3a 322 def __init__(self, *args, **kwargs):
e54b7d5d 323 super(PosteWorkflowForm, self).__init__(*args, **kwargs)
e0b93e3a
OL
324 self.fields['etat'].help_text = WF_HELP_TEXT
325
326
e6f52402 327class DossierWorkflowForm(WorkflowFormMixin):
56589624 328 bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste...
e6f52402
OL
329 class Meta:
330 fields = ('etat', )
331 model = dae.Dossier
e0b93e3a
OL
332
333 def __init__(self, *args, **kwargs):
e54b7d5d 334 super(DossierWorkflowForm, self).__init__(*args, **kwargs)
e0b93e3a 335 self.fields['etat'].help_text = WF_HELP_TEXT
e54b7d5d 336 self._etat_initial = self.instance.etat
e0b93e3a 337
e54b7d5d
EMS
338 def save(self):
339 super(DossierWorkflowForm, self).save()
340 poste = self.instance.poste
341 if poste.etat == self._etat_initial:
342 poste.etat = self.instance.etat
343 poste.save()