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