Fixes classement issue
[auf_rh_dae.git] / project / dae / forms.py
1 # -*- encoding: utf-8 -*-
2
3 import datetime
4 from ordereddict import OrderedDict
5 from dateutil.relativedelta import relativedelta
6 from django import forms
7 from django.core.urlresolvers import reverse
8 from django.core.exceptions import MultipleObjectsReturned
9 from django.forms.models import BaseInlineFormSet
10 from django.forms.models import (
11 inlineformset_factory,
12 modelformset_factory,
13 _get_foreign_key,
14 )
15 from django.db.models import Q, Max, Count
16 from django.shortcuts import redirect
17 from django.contrib.admin import widgets as admin_widgets
18
19 from ajax_select.fields import AutoCompleteSelectField
20
21 from auf.django.references import models as ref
22 from auf.django.workflow.forms import WorkflowFormMixin
23 from auf.django.workflow.models import WorkflowCommentaire
24
25 from project import groups
26 from project.rh import models as rh
27 from project.dae import models as dae
28 from .widgets import ReadOnlyChoiceWidget, ReadOnlyWidget
29 from project.dae.workflow import POSTE_ETATS_BOUTONS, POSTE_ETAT_FINALISE
30
31
32 def filtered_archived_fields_form_factory(*fields):
33 """
34 Retourne un model form
35 """
36 class FilterArchivedFields(object):
37 def __init__(self, *a, **kw):
38 super(FilterArchivedFields, self).__init__(*a, **kw)
39 for f in fields:
40 self.fields[f].queryset = (
41 self.fields[f]._queryset.filter(archive=False)
42 )
43 return FilterArchivedFields
44
45
46 class BaseInlineFormSetWithInitial(BaseInlineFormSet):
47 """
48 Cette classe permet de fournir l'option initial aux inlineformsets.
49 Elle devient désuette en django 1.4.
50 """
51 def __init__(self, data=None, files=None, instance=None,
52 save_as_new=False, prefix=None, queryset=None, **kwargs):
53
54 self.initial_extra = kwargs.pop('initial', None)
55
56 from django.db.models.fields.related import RelatedObject
57 if instance is None:
58 self.instance = self.fk.rel.to()
59 else:
60 self.instance = instance
61 self.save_as_new = save_as_new
62 # is there a better way to get the object descriptor?
63 self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()
64 if queryset is None:
65 queryset = self.model._default_manager
66 qs = queryset.filter(**{self.fk.name: self.instance})
67 super(BaseInlineFormSetWithInitial, self).__init__(data, files, prefix=prefix,
68 queryset=qs, **kwargs)
69
70 def _construct_form(self, i, **kwargs):
71 if self.is_bound and i < self.initial_form_count():
72 # Import goes here instead of module-level because importing
73 # django.db has side effects.
74 from django.db import connections
75 pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name)
76 pk = self.data[pk_key]
77 pk_field = self.model._meta.pk
78 pk = pk_field.get_db_prep_lookup('exact', pk,
79 connection=connections[self.get_queryset().db])
80 if isinstance(pk, list):
81 pk = pk[0]
82 kwargs['instance'] = self._existing_object(pk)
83 if i < self.initial_form_count() and not kwargs.get('instance'):
84 kwargs['instance'] = self.get_queryset()[i]
85 if i >= self.initial_form_count() and self.initial_extra:
86 # Set initial values for extra forms
87 try:
88 kwargs['initial'] = self.initial_extra[i-self.initial_form_count()]
89 except IndexError:
90 pass
91
92 defaults = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)}
93 if self.is_bound:
94 defaults['data'] = self.data
95 defaults['files'] = self.files
96 if self.initial:
97 try:
98 defaults['initial'] = self.initial[i]
99 except IndexError:
100 pass
101 # Allow extra forms to be empty.
102 if i >= self.initial_form_count():
103 defaults['empty_permitted'] = True
104 defaults.update(kwargs)
105 form = self.form(**defaults)
106 self.add_fields(form, i)
107 return form
108
109
110 def _implantation_choices(obj, request):
111 # TRAITEMENT NORMAL
112 employe = groups.get_employe_from_user(request.user)
113 q = Q(**{'zone_administrative__in': groups.get_zones_from_user(request.user)})
114
115 # TRAITEMENT DRH
116 user_groupes = [g.name for g in request.user.groups.all()]
117 if groups.DRH_NIVEAU_1 in user_groupes or \
118 groups.DRH_NIVEAU_2 in user_groupes:
119 q = Q()
120 return [('', '----------')] + \
121 [(i.id, unicode(i), )for i in ref.Implantation.objects.filter(q)]
122
123
124 def _employe_choices(obj, request):
125 # TRAITEMENT NORMAL
126 employe = groups.get_employe_from_user(request.user)
127 q_dae_region_service = Q(
128 poste__implantation__zone_administrative__in=(
129 groups.get_zones_from_user(request.user)
130 )
131 )
132 q_rh_region_service = Q(
133 poste__implantation__zone_administrative__in=(
134 groups.get_zones_from_user(request.user)
135 )
136 )
137 # TRAITEMENT DRH
138 user_groupes = [g.name for g in request.user.groups.all()]
139 if groups.DRH_NIVEAU_1 in user_groupes or \
140 groups.DRH_NIVEAU_2 in user_groupes:
141 q_dae_region_service = Q()
142 q_rh_region_service = Q()
143
144 # On filtre les employes avec les droits régionaux et on s'assure que
145 # c'est bien le dernier dossier en date pour sortir l'employe. On retient
146 # un employé qui travaille présentement dans la même région que le user
147 # connecté.
148 dossiers_regionaux_ids = [
149 d.id for d in dae.Dossier.objects.filter(q_dae_region_service)
150 ]
151 employes_ids = [
152 d['employe']
153 for d in dae.Dossier.objects
154 .values('employe')
155 .annotate(dernier_dossier=Max('id'))
156 if d['dernier_dossier'] in dossiers_regionaux_ids
157 ]
158 dae_employe = dae.Employe.objects.filter(id__in=employes_ids)
159 dae_ = dae_employe.filter(id_rh__isnull=True)
160 copies = dae_employe.filter(Q(id_rh__isnull=False))
161 id_copies = [p.id_rh_id for p in copies.all()]
162
163 dossiers_regionaux_ids = [
164 d.id for d in rh.Dossier.objects.filter(q_rh_region_service)
165 ]
166 employes_ids = [
167 d['employe']
168 for d in rh.Dossier.objects
169 .values('employe')
170 .annotate(dernier_dossier=Max('id'))
171 if d['dernier_dossier'] in dossiers_regionaux_ids
172 ]
173 rhv1 = rh.Employe.objects \
174 .filter(id__in=employes_ids) \
175 .exclude(id__in=id_copies)
176
177 # On ajoute les nouveaux Employés DAE qui ont été crées, mais qui n'ont
178 # pas de Dossier associés
179 employes_avec_dae = [d.employe_id for d in dae.Dossier.objects.all()]
180 employes_orphelins = dae.Employe.objects.exclude(id__in=employes_avec_dae)
181
182 def option_label(employe, extra=""):
183 if extra:
184 extra = " [%s]" % extra
185 return "%s %s %s" % (employe.nom.upper(), employe.prenom.title(), extra)
186
187 lbl_rh = sorted([('rh-%s' % p.id, option_label(p, "existant dans rh")) for p in rhv1],
188 key=lambda t: t[1])
189 lbl_dae = sorted([('dae-%s' % p.id, option_label(p)) for p in dae_ | copies | employes_orphelins],
190 key=lambda t: t[1])
191 return [('', 'Nouvel employé')] + lbl_rh + lbl_dae
192
193
194 def label_poste_display(poste):
195 """Formate un visuel pour un poste dans une liste déroulante"""
196 annee = ""
197 if poste.date_debut:
198 annee = poste.date_debut.year
199
200 nom = poste.nom
201 label = u"%s (%s) %s [%s]" % (
202 annee,
203 poste.implantation.nom_court,
204 nom,
205 #poste.type_poste.categorie_emploi.nom,
206 poste.id,
207 )
208 return label
209
210
211 PostePieceFormSet = inlineformset_factory(dae.Poste, dae.PostePiece,)
212 DossierPieceForm = inlineformset_factory(dae.Dossier, dae.DossierPiece)
213
214 # Ce formset est utilisé dans le cas de la création de poste prépopulé avec les
215 # données de RH
216 FinancementFormSetInitial = inlineformset_factory(
217 dae.Poste,
218 dae.PosteFinancement,
219 formset=BaseInlineFormSetWithInitial,
220 extra=2
221 )
222 FinancementFormSet = inlineformset_factory(
223 dae.Poste,
224 dae.PosteFinancement,
225 extra=2
226 )
227
228
229 class DossierComparaisonForm(
230 filtered_archived_fields_form_factory(
231 'classement',
232 ),
233 forms.ModelForm):
234
235 recherche = AutoCompleteSelectField('dossiers', required=False)
236 poste = forms.CharField(
237 max_length=255, widget=forms.TextInput(attrs={'size': '60'})
238 )
239 cmp_dossier = forms.IntegerField(
240 widget=forms.widgets.HiddenInput,
241 required=False
242 )
243
244 class Meta:
245 model = dae.DossierComparaison
246 exclude = ('dossier',)
247
248 DossierComparaisonFormSet = modelformset_factory(
249 dae.DossierComparaison, extra=3, max_num=3, form=DossierComparaisonForm
250 )
251
252
253 class PosteComparaisonForm(
254 filtered_archived_fields_form_factory('classement'),
255 forms.ModelForm):
256
257 recherche = AutoCompleteSelectField('dae_postes', required=False)
258
259 cmp_poste = forms.IntegerField(
260 widget=forms.widgets.HiddenInput,
261 required=False,
262 )
263
264 class Meta:
265 model = dae.PosteComparaison
266 exclude = ('poste',)
267
268 # Ce formset est utilisé dans le cas de la création de poste prépopulé avec les
269 # données de RH
270 PosteComparaisonFormSetInitial = inlineformset_factory(
271 dae.Poste,
272 dae.PosteComparaison,
273 extra=3,
274 max_num=3,
275 form=PosteComparaisonForm,
276 formset=BaseInlineFormSetWithInitial,
277 )
278 PosteComparaisonFormSet = inlineformset_factory(
279 dae.Poste,
280 dae.PosteComparaison,
281 extra=3,
282 max_num=3,
283 form=PosteComparaisonForm,
284 )
285
286
287 class FlexibleRemunForm(forms.ModelForm):
288 # Utilisé dans templats.
289 montant_mensuel = forms.DecimalField(required=False)
290 montant = forms.DecimalField(required=True, label='Montant annuel')
291
292 class Meta:
293 model = dae.Remuneration
294
295 def __init__(self, *a, **kw):
296 super(FlexibleRemunForm, self).__init__(*a, **kw)
297 # self.fields['type'].widget = ReadOnlyChoiceWidget(choices=self.fields['type'].choices)
298
299 def clean_devise(self):
300 devise = self.cleaned_data['devise']
301 if devise.code == 'EUR':
302 return devise
303 implantation = ref.Implantation.objects.get(
304 id=self.data['implantation']
305 )
306 liste_taux = devise.tauxchange_set.order_by('-annee')
307 if len(liste_taux) == 0:
308 raise forms.ValidationError(
309 u"La devise %s n'a pas de taux pour l'implantation %s" %
310 (devise, implantation)
311 )
312 else:
313 return devise
314
315 def has_changed(self):
316 """
317 Modification de has_changed pour qu'il ignore les montant a 0
318 et les 'types'.
319 """
320
321 changed_data = self.changed_data
322
323 # Type is set in hidden fields, it shouldn't be changed by the
324 # user; ignore when checking if data has changed.
325 if 'type' in changed_data:
326 changed_data.pop(changed_data.index('type'))
327
328 # Montant is set to 0 in javascript, ifnore 'montant' data if
329 # its value is 0.
330
331 # Generer le key tel qu'identifié dans self.data:
332 montant_key = '-'.join((self.prefix, 'montant'))
333
334 if ('montant' in changed_data and
335 self.data.get(montant_key, '0') == '0'):
336 changed_data.pop(changed_data.index('montant'))
337
338 return bool(changed_data)
339
340
341 class ReadOnlyRemunForm(FlexibleRemunForm):
342 # Utilisé dans templats.
343
344 def __init__(self, *a, **kw):
345 super (ReadOnlyRemunForm, self).__init__(*a, **kw)
346 for field in self.fields:
347 field = self.fields[field]
348 if not isinstance(field.widget, (
349 forms.widgets.HiddenInput,
350 forms.widgets.Select)):
351 field.widget = ReadOnlyWidget()
352 elif isinstance(field.widget, forms.widgets.Select):
353 field.widget = ReadOnlyChoiceWidget(choices=field.choices)
354
355
356 class GroupedInlineFormset(BaseInlineFormSet):
357
358 def set_groups(self,
359 groups,
360 group_accessor,
361 choice_overrides=[]):
362
363
364 # Create pre-defined groups.
365 self.groups = OrderedDict()
366 for group in groups:
367 self.groups[group[0]] = {
368 'name': group[1],
369 'key': group[0],
370 'forms': [],
371 }
372
373 # Assign each form to a group.
374 for form in self.forms:
375 if bool(form.initial):
376 grp = group_accessor(form)
377 if grp[0] not in self.groups:
378 self.groups[grp[0]] = {
379 'name': grp[1],
380 'key': grp[0],
381 'forms': [],
382 }
383 self.groups[grp[0]]['forms'].append(form)
384
385 # Add extra forms (n extra for each grop).
386 tmp_extras = []
387 for i in xrange(len(self.groups) * self.extra):
388 tmp_extras.insert(0,
389 self._construct_form(self.initial_form_count() + i))
390
391 for g in self.groups:
392 for i in xrange(self.extra):
393 tmp_form = tmp_extras.pop()
394 self.groups[g]['forms'].append(tmp_form)
395 self.forms.append(tmp_form)
396
397
398 # Override form choices with the data provided in
399 # choice_overrides
400 for key in choice_overrides:
401 for form in self.groups.get(key, {'forms': []})['forms']:
402 for field_key in choice_overrides[key]:
403 form.fields[field_key].choices = choice_overrides[
404 key][field_key]
405
406
407 # Create an iterable for easier access in template.
408 self.group_list = self.groups.values()
409
410 # def set_groups(self, group_accessor, groups, group_order=[]):
411 # """
412 # group_accessor: A function that will get the key and name from
413 # each form.
414 # group_order: list the group keys here in a list and
415 # GroupedInlineFormset.groups will be ordered (ordereddict) by
416 # the key sequence provided here. Any missing key from the
417 # sequence will
418 # """
419
420 # # Build group list.
421 # self.groups = OrderedDict()
422 # temp_groups = {}
423 # # self.groups_and_forms = []
424 # for form in self.forms:
425 # group_key, group_name = group_accessor(form)
426 # if not temp_groups.has_key(group_key):
427 # temp_groups[group_key] = {
428 # 'name': group_name,
429 # 'key': group_key,
430 # 'forms': [],
431 # }
432 # temp_groups[group_key]['forms'].append(form)
433
434 # for order_key in group_order:
435 # if temp_groups.has_key(order_key):
436 # self.groups[order_key] = temp_groups.pop(order_key)
437
438 # for key in temp_groups:
439 # self.groups[key] = temp_groups[key]
440
441 # del temp_groups
442
443 # self.group_list = self.groups.values()
444
445
446 def remun_formset_factory(parent_model,
447 model,
448 form=forms.ModelForm,
449 formset=GroupedInlineFormset,
450 fk_name=None,
451 fields=None,
452 exclude=None,
453 can_order=False,
454 can_delete=True,
455 read_only=False,
456 extra=2,
457 max_num=None,
458 formfield_callback=None,
459 groups=None,
460 choice_overrides=[]):
461 trs = rh.TypeRemuneration.objects.all()
462 # extra = max_num = trs.count()
463 fk = _get_foreign_key(parent_model, model, fk_name=fk_name)
464 # enforce a max_num=1 when the foreign key to the parent model is unique.
465 if fk.unique:
466 max_num = 1
467 kwargs = {
468 'form': form,
469 'formfield_callback': formfield_callback,
470 'formset': formset,
471 'extra': extra,
472 'can_delete': can_delete,
473 'can_order': can_order,
474 'fields': fields,
475 'exclude': exclude,
476 'max_num': max_num,
477 }
478 FormSet = modelformset_factory(model, **kwargs)
479 FormSet.fk = fk
480 FormSet.read_only = read_only
481
482 def grouper(form):
483 rtype = form.initial['type']
484 if not isinstance(rtype, rh.TypeRemuneration):
485 rtype = rh.TypeRemuneration.objects.get(id=rtype)
486 return (rtype.nature_remuneration,
487 rtype.nature_remuneration
488 )
489
490
491
492 # Monkey patch FormSet.
493 def __init__(inst, *a, **kw):
494 super(inst.__class__, inst).__init__(*a, **kw)
495 inst.set_groups(groups, grouper, choice_overrides)
496
497 FormSet.__init__ = __init__
498
499 return FormSet
500
501
502 def remun_formset_factory_factory(
503 read_only=False,
504 parent_model=dae.Dossier,
505 model=dae.Remuneration,
506 exclude_archived=False):
507 """
508 Don't we love factory factories?
509 """
510
511 null_choice = ('', '-' * 10)
512 extras = 2 if not read_only else 0
513 can_delete = False if read_only else True
514 form_class = ReadOnlyRemunForm if read_only else FlexibleRemunForm
515
516 choice_override_extra_q = {}
517
518 if exclude_archived:
519 choice_override_extra_q.update({
520 'archive': False
521 })
522
523 return remun_formset_factory(
524 parent_model,
525 model,
526 form=form_class,
527 extra=extras,
528 can_delete=can_delete,
529 read_only=read_only,
530 groups = rh.NATURE_REMUNERATION_CHOICES,
531 choice_overrides = {
532 u'Traitement': {
533 'type': [null_choice] + list(
534 rh.TypeRemuneration.objects.filter(
535 nature_remuneration=u'Traitement',
536 **choice_override_extra_q).values_list(
537 'id', 'nom')
538 )
539 },
540 u'Indemnité': {
541 'type': [null_choice] + list(
542 rh.TypeRemuneration.objects.filter(
543 nature_remuneration=u'Indemnité',
544 **choice_override_extra_q).values_list(
545 'id', 'nom')
546 )
547 },
548 u'Charges': {
549 'type': [null_choice] + list(
550 rh.TypeRemuneration.objects.filter(
551 nature_remuneration=u'Charges',
552 **choice_override_extra_q).values_list(
553 'id', 'nom')
554 )
555 },
556 u'Accessoire': {
557 'type': [null_choice] + list(
558 rh.TypeRemuneration.objects.filter(
559 nature_remuneration=u'Accessoire',
560 **choice_override_extra_q).values_list(
561 'id', 'nom')
562 )
563 },
564 u'RAS': {
565 'type': [null_choice] + list(
566 rh.TypeRemuneration.objects.filter(
567 nature_remuneration=u'RAS',
568 **choice_override_extra_q).values_list(
569 'id', 'nom')
570 )
571 },
572 },
573 )
574
575 RemunForm = remun_formset_factory_factory(
576 read_only=False,
577 parent_model=dae.Dossier,
578 model=dae.Remuneration,
579 exclude_archived=True,
580 )
581
582 ReadOnlyRemunFormSet = remun_formset_factory_factory(
583 read_only=True,
584 parent_model=dae.Dossier,
585 model=dae.Remuneration,
586 )
587
588 PosteCompReadOnlyRemunFormSet = remun_formset_factory_factory(
589 read_only=True,
590 parent_model=dae.PosteComparaison,
591 model=dae.PosteComparaisonRemuneration,
592 )
593
594 DossierCompReadOnlyRemunFormSet = remun_formset_factory_factory(
595 read_only=True,
596 parent_model=dae.DossierComparaison,
597 model=dae.DossierComparaisonRemuneration,
598 )
599
600
601 class PosteForm(filtered_archived_fields_form_factory(
602 'classement_min',
603 'classement_max',),
604 forms.ModelForm):
605 """ Formulaire des postes. """
606
607 # On ne propose que les services actifs
608 service = forms.ModelChoiceField(
609 queryset=rh.Service.objects.all(), required=True
610 )
611
612 responsable = AutoCompleteSelectField('responsables', required=True)
613 #responsable = forms.ModelChoiceField(
614 # queryset=rh.Poste.objects.select_related(depth=1))
615
616 # La liste des choix est laissée vide. Voir __init__ pour la raison.
617 poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste",
618 choices=(), required=False)
619
620 valeur_point_min = forms.ModelChoiceField(
621 queryset=rh.ValeurPoint.actuelles.all(), required=False
622 )
623 valeur_point_max = forms.ModelChoiceField(
624 queryset=rh.ValeurPoint.actuelles.all(), required=False
625 )
626
627 class Meta:
628 model = dae.Poste
629 fields = ('type_intervention',
630 'poste', 'implantation', 'type_poste', 'service', 'nom',
631 'responsable', 'local', 'expatrie', 'mise_a_disposition',
632 'appel', 'date_debut', 'date_fin',
633 'regime_travail', 'regime_travail_nb_heure_semaine',
634 'classement_min', 'classement_max',
635 'valeur_point_min', 'valeur_point_max',
636 'devise_min', 'devise_max',
637 'salaire_min', 'salaire_max',
638 'indemn_expat_min', 'indemn_expat_max',
639 'indemn_fct_min', 'indemn_fct_max',
640 'charges_patronales_min', 'charges_patronales_max',
641 'autre_min', 'autre_max', 'devise_comparaison',
642 'comp_locale_min', 'comp_locale_max',
643 'comp_universite_min', 'comp_universite_max',
644 'comp_fonctionpub_min', 'comp_fonctionpub_max',
645 'comp_ong_min', 'comp_ong_max',
646 'comp_autre_min', 'comp_autre_max',
647 'justification',
648 )
649 widgets = dict(type_intervention=forms.RadioSelect(),
650 appel=forms.RadioSelect(),
651 nom=forms.TextInput(attrs={'size': 60},),
652 date_debut=admin_widgets.AdminDateWidget(),
653 date_fin=admin_widgets.AdminDateWidget(),
654 justification=forms.Textarea(attrs={'cols': 80},),
655 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
656 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
657 )
658
659 def __init__(self, *args, **kwargs):
660 """ Mise à jour dynamique du contenu du menu des postes.
661
662 Si on ne met le menu à jour de cette façon, à chaque instantiation du
663 formulaire, son contenu est mis en cache par le système et il ne
664 reflète pas les changements apportés par les ajouts, modifications,
665 etc...
666
667 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
668 car le "id" de chaque choix est spécial (voir _poste_choices).
669
670 """
671 request = kwargs.pop('request')
672 super(PosteForm, self).__init__(*args, **kwargs)
673 self.fields['poste'].choices = self._poste_choices(request)
674
675 self.fields['implantation'].choices = \
676 _implantation_choices(self, request)
677
678 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
679 if self.instance and self.instance.id is None:
680 dossiers = self.instance.get_dossiers()
681 if len(dossiers) > 0:
682 self.initial['service'] = dossiers[0].poste.service
683
684 def _poste_choices(self, request):
685 """ Menu déroulant pour les postes.
686 Constitué des postes de RH
687 """
688 postes_rh = rh.Poste.objects.ma_region_ou_service(request.user).all()
689 postes_rh = postes_rh.select_related(depth=1)
690
691 return [('', 'Nouveau poste')] + \
692 sorted([('rh-%s' % p.id, label_poste_display(p)) for p in
693 postes_rh],
694 key=lambda t: t[1])
695
696 def clean(self):
697 """
698 Validation conditionnelles de certains champs.
699 """
700 cleaned_data = self.cleaned_data
701
702 if cleaned_data.get("local") is False \
703 and cleaned_data.get("expatrie") is False:
704 msg = "Le poste doit au moins être ouvert localement " \
705 "ou aux expatriés"
706 self._errors["local"] = self.error_class([msg])
707 self._errors["expatrie"] = ''
708 raise forms.ValidationError(msg)
709
710 return cleaned_data
711
712
713 class ChoosePosteForm(forms.Form):
714 class Meta:
715 fields = ('poste',)
716
717 # La liste des choix est laissée vide. Voir PosteForm.__init__.
718 postes_dae = forms.ChoiceField(choices=(), required=False)
719 postes_rh = forms.ChoiceField(choices=(), required=False)
720
721 def __init__(self, request=None, *args, **kwargs):
722 super(ChoosePosteForm, self).__init__(*args, **kwargs)
723 self.fields['postes_dae'].choices = self._poste_dae_choices(request)
724 self.fields['postes_rh'].choices = self._poste_rh_choices(request)
725
726 def _poste_dae_choices(self, request):
727 """ Menu déroulant pour les postes."""
728 postes_dae = dae.Poste.objects.ma_region_ou_service(request.user) \
729 .exclude(etat__in=(POSTE_ETAT_FINALISE, )) \
730 .annotate(num_dae=Count('dae_dossiers')) \
731 .filter(num_dae=0) \
732 .order_by('implantation', '-date_debut', )
733
734 return [('', '----------')] + \
735 [('dae-%s' % p.id, label_poste_display(p)) for p in postes_dae]
736
737 def _poste_rh_choices(self, request):
738 """ Menu déroulant pour les postes."""
739 postes_dae = dae.Poste.objects.exclude(etat__in=(POSTE_ETAT_FINALISE, ))
740 today = datetime.date.today()
741 id_poste_dae_commences = [p.id_rh_id for p in postes_dae if p.id_rh is not None]
742 postes_rh = rh.Poste.objects.ma_region_ou_service(request.user) \
743 .exclude(id__in=id_poste_dae_commences) \
744 .filter(Q(date_debut__lte=today) &
745 (Q(date_fin__gte=today) |
746 Q(date_fin__isnull=True))
747 ) \
748 .order_by('implantation', '-date_debut', )
749
750 return [('', '----------')] + \
751 [('rh-%s' % p.id, label_poste_display(p)) for p in postes_rh]
752
753 def clean(self):
754 cleaned_data = super(ChoosePosteForm, self).clean()
755 postes_dae = cleaned_data.get("postes_dae")
756 postes_rh = cleaned_data.get("postes_rh")
757 if (postes_dae is u"" and postes_rh is u"") or \
758 (postes_dae is not u"" and postes_rh is not u""):
759 raise forms.ValidationError("Choisissez un poste DAE ou un poste RH")
760 return cleaned_data
761
762 def redirect(self):
763 poste_dae_key = self.cleaned_data.get("postes_dae")
764 if poste_dae_key is not u"":
765 return redirect(reverse('embauche', args=(poste_dae_key,)))
766 poste_rh_key = self.cleaned_data.get("postes_rh")
767 if poste_rh_key is not u"":
768 return redirect("%s?creer_dossier_dae='M'" % reverse('poste', args=(poste_rh_key,)))
769
770 class EmployeForm(forms.ModelForm):
771 """ Formulaire des employés. """
772 class Meta:
773 model = dae.Employe
774 fields = ('employe', 'nom', 'prenom', 'genre')
775
776 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
777 employe = forms.ChoiceField(choices=(), required=False)
778
779 def __init__(self, *args, **kwargs):
780 """ Mise à jour dynamique du contenu du menu des employés. """
781 request = kwargs.pop('request', None)
782 super(EmployeForm, self).__init__(*args, **kwargs)
783 self.fields['employe'].choices = _employe_choices(self, request)
784
785
786 class DossierForm(
787 filtered_archived_fields_form_factory(
788 'classement',
789 'classement_anterieur',
790 'classement_titulaire_anterieur',
791 ),
792 forms.ModelForm):
793 """ Formulaire des dossiers. """
794 class Meta:
795 exclude = ('etat', 'employe', 'poste', 'date_debut',)
796 model = dae.Dossier
797 widgets = dict(statut_residence=forms.RadioSelect(),
798 contrat_date_debut=admin_widgets.AdminDateWidget(),
799 contrat_date_fin=admin_widgets.AdminDateWidget(),
800 )
801
802 WF_HELP_TEXT = ""
803
804
805 class PosteWorkflowForm(WorkflowFormMixin):
806 bouton_libelles = POSTE_ETATS_BOUTONS
807
808 class Meta:
809 fields = ('etat', )
810 model = dae.Poste
811
812 def __init__(self, *args, **kwargs):
813 super(PosteWorkflowForm, self).__init__(*args, **kwargs)
814 self.fields['etat'].help_text = WF_HELP_TEXT
815
816
817 class DossierWorkflowForm(WorkflowFormMixin):
818 bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste...
819
820 class Meta:
821 fields = ('etat', )
822 model = dae.Dossier
823
824 def __init__(self, *args, **kwargs):
825 super(DossierWorkflowForm, self).__init__(*args, **kwargs)
826 self.fields['etat'].help_text = WF_HELP_TEXT
827 self._etat_initial = self.instance.etat
828
829 def save(self):
830 super(DossierWorkflowForm, self).save()
831 poste = self.instance.poste
832
833 # créer le commentaire automatique pour le poste associé
834 commentaire = WorkflowCommentaire()
835 commentaire.content_object = poste
836 texte = u"Validation automatique à travers le dossier [%s] de %s\n%s" %(
837 self.instance.id,
838 self.instance,
839 self.data.get('commentaire', ''),
840 )
841 commentaire.texte = texte
842 commentaire.etat_initial = self.instance._etat_courant
843 commentaire.etat_final = self.instance.etat
844 commentaire.owner = self.request.user
845 commentaire.save()
846
847 # force l'état du poste
848 poste.etat = self.instance.etat
849 poste.save()
850
851
852 class ContratForm(forms.ModelForm):
853
854 class Meta:
855 fields = ('type_contrat', 'fichier', )
856 model = dae.Contrat
857
858
859 class DAENumeriseeForm(forms.ModelForm):
860
861 class Meta:
862 model = dae.Dossier
863 fields = ('dae_numerisee',)
864
865
866 class DAEFinaliseesSearchForm(forms.Form):
867 q = forms.CharField(
868 label='Recherche', required=False,
869 widget=forms.TextInput(attrs={'size': 40})
870 )
871 importees = forms.ChoiceField(
872 label='Importation', required=False, choices=(
873 ('', ''),
874 ('oui', 'DAE importées seulement'),
875 ('non', 'DAE non-importées seulement'),
876 )
877 )