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