batch historique
[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)
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
180
93817ef3
OL
181 try:
182 label = u"%s %s - %s [%s]" % (
183 annee, nom, poste.type_poste.categorie_emploi.nom, poste.id
184 )
185 except:
186 label = unicode(poste)
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
139686f2
NC
389class ChoosePosteForm(forms.ModelForm):
390 class Meta:
391 model = dae.Poste
392 fields = ('poste',)
393
394 # La liste des choix est laissée vide. Voir PosteForm.__init__.
395 poste = forms.ChoiceField(choices=(), required=False)
396
4ee6d70a 397 def __init__(self, request=None, *args, **kwargs):
139686f2 398 super(ChoosePosteForm, self).__init__(*args, **kwargs)
4ee6d70a 399 self.fields['poste'].choices = self._poste_choices(request)
139686f2 400
4ee6d70a 401 def _poste_choices(self, request):
139686f2 402 """ Menu déroulant pour les postes. """
5a1f75cb
EMS
403 dae_ = dae.Poste.objects.ma_region_ou_service(request.user) \
404 .filter(id_rh__isnull=True)
405 copies = dae.Poste.objects.ma_region_ou_service(request.user) \
406 .exclude(id_rh__isnull=True)
139686f2 407
98d51b59 408 return [('', '----------')] + \
139686f2
NC
409 sorted([('dae-%s' % p.id, unicode(p)) for p in dae_ | copies],
410 key=lambda t: t[1])
411
412
139686f2
NC
413class EmployeForm(forms.ModelForm):
414 """ Formulaire des employés. """
415 class Meta:
416 model = dae.Employe
417 fields = ('employe', 'nom', 'prenom', 'genre')
418
419 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
420 employe = forms.ChoiceField(choices=(), required=False)
421
ac6235f6 422 def __init__(self, *args, **kwargs):
139686f2 423 """ Mise à jour dynamique du contenu du menu des employés. """
ac6235f6 424 request = kwargs.pop('request', None)
139686f2 425 super(EmployeForm, self).__init__(*args, **kwargs)
f258e4e7 426 self.fields['employe'].choices = _employe_choices(self, request)
139686f2 427
139686f2 428
139686f2
NC
429class DossierForm(forms.ModelForm):
430 """ Formulaire des dossiers. """
431 class Meta:
5a1f75cb 432 exclude = ('etat', 'employe', 'poste', 'date_debut',)
139686f2 433 model = dae.Dossier
4d25e2ba 434 widgets = dict(statut_residence=forms.RadioSelect(),
0e0aeb7e
OL
435 contrat_date_debut=admin_widgets.AdminDateWidget(),
436 contrat_date_fin=admin_widgets.AdminDateWidget(),
4d25e2ba 437 )
e6f52402 438
3799cafc 439WF_HELP_TEXT = ""
e0b93e3a 440
5a1f75cb 441
e6f52402 442class PosteWorkflowForm(WorkflowFormMixin):
56589624 443 bouton_libelles = POSTE_ETATS_BOUTONS
5a1f75cb 444
e6f52402
OL
445 class Meta:
446 fields = ('etat', )
447 model = dae.Poste
9536ea21 448
e0b93e3a 449 def __init__(self, *args, **kwargs):
e54b7d5d 450 super(PosteWorkflowForm, self).__init__(*args, **kwargs)
e0b93e3a
OL
451 self.fields['etat'].help_text = WF_HELP_TEXT
452
453
e6f52402 454class DossierWorkflowForm(WorkflowFormMixin):
5a1f75cb
EMS
455 bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste...
456
e6f52402 457 class Meta:
9e40cfbe 458 fields = ('etat', )
e6f52402 459 model = dae.Dossier
e0b93e3a
OL
460
461 def __init__(self, *args, **kwargs):
e54b7d5d 462 super(DossierWorkflowForm, self).__init__(*args, **kwargs)
e0b93e3a 463 self.fields['etat'].help_text = WF_HELP_TEXT
e54b7d5d 464 self._etat_initial = self.instance.etat
e0b93e3a 465
e54b7d5d
EMS
466 def save(self):
467 super(DossierWorkflowForm, self).save()
468 poste = self.instance.poste
469 if poste.etat == self._etat_initial:
470 poste.etat = self.instance.etat
471 poste.save()
9536ea21 472
5a1f75cb 473
9536ea21
EMS
474class ContratForm(forms.ModelForm):
475
476 class Meta:
9dfa4296 477 fields = ('type_contrat', 'fichier', )
9536ea21
EMS
478 model = dae.Contrat
479
5a1f75cb 480
c3f0b49f
EMS
481class DAENumeriseeForm(forms.ModelForm):
482
483 class Meta:
484 model = dae.Dossier
485 fields = ('dae_numerisee',)
cbfd7bd4
EMS
486
487
488class DAEFinaliseesSearchForm(forms.Form):
489 q = forms.CharField(
490 label='Recherche', required=False,
491 widget=forms.TextInput(attrs={'size': 40})
492 )
493 importees = forms.ChoiceField(
494 label='Importation', required=False, choices=(
495 ('', ''),
496 ('oui', 'DAE importées seulement'),
497 ('non', 'DAE non-importées seulement'),
498 )
499 )