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