Merge branch 'hotfix/bug_etat_transition' into dev
[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(
288 filtered_archived_fields_form_factory(
289 'type',
290 ),
291 forms.ModelForm):
292 # Utilisé dans templats.
293 montant_mensuel = forms.DecimalField(required=False)
294 montant = forms.DecimalField(required=True, label='Montant annuel')
295
296 class Meta:
297 model = dae.Remuneration
298
299 def __init__(self, *a, **kw):
300 super(FlexibleRemunForm, self).__init__(*a, **kw)
301 # self.fields['type'].widget = ReadOnlyChoiceWidget(choices=self.fields['type'].choices)
302
303 def clean_devise(self):
304 devise = self.cleaned_data['devise']
305 if devise.code == 'EUR':
306 return devise
307 implantation = ref.Implantation.objects.get(
308 id=self.data['implantation']
309 )
310 liste_taux = devise.tauxchange_set.order_by('-annee')
311 if len(liste_taux) == 0:
312 raise forms.ValidationError(
313 u"La devise %s n'a pas de taux pour l'implantation %s" %
314 (devise, implantation)
315 )
316 else:
317 return devise
318
319 def has_changed(self):
320 """
321 Modification de has_changed pour qu'il ignore les montant a 0
322 et les 'types'.
323 """
324
325 changed_data = self.changed_data
326
327 # Type is set in hidden fields, it shouldn't be changed by the
328 # user; ignore when checking if data has changed.
329 if 'type' in changed_data:
330 changed_data.pop(changed_data.index('type'))
331
332 # Montant is set to 0 in javascript, ifnore 'montant' data if
333 # its value is 0.
334
335 # Generer le key tel qu'identifié dans self.data:
336 montant_key = '-'.join((self.prefix, 'montant'))
337
338 if ('montant' in changed_data and
339 self.data.get(montant_key, '0') == '0'):
340 changed_data.pop(changed_data.index('montant'))
341
342 return bool(changed_data)
343
344
345 class ReadOnlyRemunForm(FlexibleRemunForm):
346 # Utilisé dans templats.
347
348 def __init__(self, *a, **kw):
349 super (ReadOnlyRemunForm, self).__init__(*a, **kw)
350 for field in self.fields:
351 field = self.fields[field]
352 if not isinstance(field.widget, (
353 forms.widgets.HiddenInput,
354 forms.widgets.Select)):
355 field.widget = ReadOnlyWidget()
356 elif isinstance(field.widget, forms.widgets.Select):
357 field.widget = ReadOnlyChoiceWidget(choices=field.choices)
358
359
360 class GroupedInlineFormset(BaseInlineFormSet):
361
362 def set_groups(self,
363 groups,
364 group_accessor,
365 choice_overrides=[]):
366
367
368 # Create pre-defined groups.
369 self.groups = OrderedDict()
370 for group in groups:
371 self.groups[group[0]] = {
372 'name': group[1],
373 'key': group[0],
374 'forms': [],
375 }
376
377 # Assign each form to a group.
378 for form in self.forms:
379 if bool(form.initial):
380 grp = group_accessor(form)
381 if grp[0] not in self.groups:
382 self.groups[grp[0]] = {
383 'name': grp[1],
384 'key': grp[0],
385 'forms': [],
386 }
387 self.groups[grp[0]]['forms'].append(form)
388
389 # Add extra forms (n extra for each grop).
390 tmp_extras = []
391 for i in xrange(len(self.groups) * self.extra):
392 tmp_extras.insert(0,
393 self._construct_form(self.initial_form_count() + i))
394
395 for g in self.groups:
396 for i in xrange(self.extra):
397 tmp_form = tmp_extras.pop()
398 self.groups[g]['forms'].append(tmp_form)
399 self.forms.append(tmp_form)
400
401
402 # Override form choices with the data provided in
403 # choice_overrides
404 for key in choice_overrides:
405 for form in self.groups.get(key, {'forms': []})['forms']:
406 for field_key in choice_overrides[key]:
407 form.fields[field_key].choices = choice_overrides[
408 key][field_key]
409
410
411 # Create an iterable for easier access in template.
412 self.group_list = self.groups.values()
413
414 # def set_groups(self, group_accessor, groups, group_order=[]):
415 # """
416 # group_accessor: A function that will get the key and name from
417 # each form.
418 # group_order: list the group keys here in a list and
419 # GroupedInlineFormset.groups will be ordered (ordereddict) by
420 # the key sequence provided here. Any missing key from the
421 # sequence will
422 # """
423
424 # # Build group list.
425 # self.groups = OrderedDict()
426 # temp_groups = {}
427 # # self.groups_and_forms = []
428 # for form in self.forms:
429 # group_key, group_name = group_accessor(form)
430 # if not temp_groups.has_key(group_key):
431 # temp_groups[group_key] = {
432 # 'name': group_name,
433 # 'key': group_key,
434 # 'forms': [],
435 # }
436 # temp_groups[group_key]['forms'].append(form)
437
438 # for order_key in group_order:
439 # if temp_groups.has_key(order_key):
440 # self.groups[order_key] = temp_groups.pop(order_key)
441
442 # for key in temp_groups:
443 # self.groups[key] = temp_groups[key]
444
445 # del temp_groups
446
447 # self.group_list = self.groups.values()
448
449
450 def remun_formset_factory(parent_model,
451 model,
452 form=forms.ModelForm,
453 formset=GroupedInlineFormset,
454 fk_name=None,
455 fields=None,
456 exclude=None,
457 can_order=False,
458 can_delete=True,
459 read_only=False,
460 extra=2,
461 max_num=None,
462 formfield_callback=None,
463 groups=None,
464 choice_overrides=[]):
465 trs = rh.TypeRemuneration.objects.all()
466 # extra = max_num = trs.count()
467 fk = _get_foreign_key(parent_model, model, fk_name=fk_name)
468 # enforce a max_num=1 when the foreign key to the parent model is unique.
469 if fk.unique:
470 max_num = 1
471 kwargs = {
472 'form': form,
473 'formfield_callback': formfield_callback,
474 'formset': formset,
475 'extra': extra,
476 'can_delete': can_delete,
477 'can_order': can_order,
478 'fields': fields,
479 'exclude': exclude,
480 'max_num': max_num,
481 }
482 FormSet = modelformset_factory(model, **kwargs)
483 FormSet.fk = fk
484 FormSet.read_only = read_only
485
486 def grouper(form):
487 rtype = form.initial['type']
488 if not isinstance(rtype, rh.TypeRemuneration):
489 rtype = rh.TypeRemuneration.objects.get(id=rtype)
490 return (rtype.nature_remuneration,
491 rtype.nature_remuneration
492 )
493
494
495
496 # Monkey patch FormSet.
497 def __init__(inst, *a, **kw):
498 super(inst.__class__, inst).__init__(*a, **kw)
499 inst.set_groups(groups, grouper, choice_overrides)
500
501 FormSet.__init__ = __init__
502
503 return FormSet
504
505
506 def remun_formset_factory_factory(
507 read_only=False,
508 parent_model=dae.Dossier,
509 model=dae.Remuneration,
510 exclude_archived=False):
511 """
512 Don't we love factory factories?
513 """
514
515 null_choice = ('', '-' * 10)
516 extras = 2 if not read_only else 0
517 can_delete = False if read_only else True
518 form_class = ReadOnlyRemunForm if read_only else FlexibleRemunForm
519
520 choice_override_extra_q = {}
521
522 if exclude_archived:
523 choice_override_extra_q.update({
524 'archive': False
525 })
526
527 return remun_formset_factory(
528 parent_model,
529 model,
530 form=form_class,
531 extra=extras,
532 can_delete=can_delete,
533 read_only=read_only,
534 groups = rh.NATURE_REMUNERATION_CHOICES,
535 choice_overrides = {
536 u'Traitement': {
537 'type': [null_choice] + list(
538 rh.TypeRemuneration.objects.filter(
539 nature_remuneration=u'Traitement',
540 **choice_override_extra_q).values_list(
541 'id', 'nom')
542 )
543 },
544 u'Indemnité': {
545 'type': [null_choice] + list(
546 rh.TypeRemuneration.objects.filter(
547 nature_remuneration=u'Indemnité',
548 **choice_override_extra_q).values_list(
549 'id', 'nom')
550 )
551 },
552 u'Charges': {
553 'type': [null_choice] + list(
554 rh.TypeRemuneration.objects.filter(
555 nature_remuneration=u'Charges',
556 **choice_override_extra_q).values_list(
557 'id', 'nom')
558 )
559 },
560 u'Accessoire': {
561 'type': [null_choice] + list(
562 rh.TypeRemuneration.objects.filter(
563 nature_remuneration=u'Accessoire',
564 **choice_override_extra_q).values_list(
565 'id', 'nom')
566 )
567 },
568 u'RAS': {
569 'type': [null_choice] + list(
570 rh.TypeRemuneration.objects.filter(
571 nature_remuneration=u'RAS',
572 **choice_override_extra_q).values_list(
573 'id', 'nom')
574 )
575 },
576 },
577 )
578
579 RemunForm = remun_formset_factory_factory(
580 read_only=False,
581 parent_model=dae.Dossier,
582 model=dae.Remuneration,
583 exclude_archived=True,
584 )
585
586 ReadOnlyRemunFormSet = remun_formset_factory_factory(
587 read_only=True,
588 parent_model=dae.Dossier,
589 model=dae.Remuneration,
590 )
591
592 PosteCompReadOnlyRemunFormSet = remun_formset_factory_factory(
593 read_only=True,
594 parent_model=dae.PosteComparaison,
595 model=dae.PosteComparaisonRemuneration,
596 )
597
598 DossierCompReadOnlyRemunFormSet = remun_formset_factory_factory(
599 read_only=True,
600 parent_model=dae.DossierComparaison,
601 model=dae.DossierComparaisonRemuneration,
602 )
603 RHReadOnlyRemunFormSet = remun_formset_factory_factory(
604 read_only=True,
605 parent_model=rh.Dossier,
606 model=rh.Remuneration,
607 )
608
609 class PosteForm(filtered_archived_fields_form_factory(
610 'classement_min',
611 'classement_max',),
612 forms.ModelForm):
613 """ Formulaire des postes. """
614
615 # On ne propose que les services actifs
616 service = forms.ModelChoiceField(
617 queryset=rh.Service.objects.all(), required=True
618 )
619
620 responsable = AutoCompleteSelectField('responsables', required=True)
621 #responsable = forms.ModelChoiceField(
622 # queryset=rh.Poste.objects.select_related(depth=1))
623
624 # La liste des choix est laissée vide. Voir __init__ pour la raison.
625 poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste",
626 choices=(), required=False)
627
628 valeur_point_min = forms.ModelChoiceField(
629 queryset=rh.ValeurPoint.actuelles.all(), required=False
630 )
631 valeur_point_max = forms.ModelChoiceField(
632 queryset=rh.ValeurPoint.actuelles.all(), required=False
633 )
634
635 class Meta:
636 model = dae.Poste
637 fields = ('type_intervention',
638 'poste', 'implantation', 'type_poste', 'service', 'nom',
639 'responsable', 'local', 'expatrie', 'mise_a_disposition',
640 'appel', 'date_debut', 'date_fin',
641 'regime_travail', 'regime_travail_nb_heure_semaine',
642 'classement_min', 'classement_max',
643 'valeur_point_min', 'valeur_point_max',
644 'devise_min', 'devise_max',
645 'salaire_min', 'salaire_max',
646 'indemn_expat_min', 'indemn_expat_max',
647 'indemn_fct_min', 'indemn_fct_max',
648 'charges_patronales_min', 'charges_patronales_max',
649 'autre_min', 'autre_max', 'devise_comparaison',
650 'comp_locale_min', 'comp_locale_max',
651 'comp_universite_min', 'comp_universite_max',
652 'comp_fonctionpub_min', 'comp_fonctionpub_max',
653 'comp_ong_min', 'comp_ong_max',
654 'comp_autre_min', 'comp_autre_max',
655 'justification',
656 )
657 widgets = dict(type_intervention=forms.RadioSelect(),
658 appel=forms.RadioSelect(),
659 nom=forms.TextInput(attrs={'size': 60},),
660 date_debut=admin_widgets.AdminDateWidget(),
661 date_fin=admin_widgets.AdminDateWidget(),
662 justification=forms.Textarea(attrs={'cols': 80},),
663 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
664 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
665 )
666
667 def __init__(self, *args, **kwargs):
668 """ Mise à jour dynamique du contenu du menu des postes.
669
670 Si on ne met le menu à jour de cette façon, à chaque instantiation du
671 formulaire, son contenu est mis en cache par le système et il ne
672 reflète pas les changements apportés par les ajouts, modifications,
673 etc...
674
675 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
676 car le "id" de chaque choix est spécial (voir _poste_choices).
677
678 """
679 request = kwargs.pop('request')
680 super(PosteForm, self).__init__(*args, **kwargs)
681 self.fields['poste'].choices = self._poste_choices(request)
682
683 self.fields['implantation'].choices = \
684 _implantation_choices(self, request)
685
686 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
687 if self.instance and self.instance.id is None:
688 dossiers = self.instance.get_dossiers()
689 if len(dossiers) > 0:
690 self.initial['service'] = dossiers[0].poste.service
691
692 def _poste_choices(self, request):
693 """ Menu déroulant pour les postes.
694 Constitué des postes de RH
695 """
696 postes_rh = rh.Poste.objects.ma_region_ou_service(request.user).all()
697 postes_rh = postes_rh.select_related(depth=1)
698
699 return [('', 'Nouveau poste')] + \
700 sorted([('rh-%s' % p.id, label_poste_display(p)) for p in
701 postes_rh],
702 key=lambda t: t[1])
703
704 def clean(self):
705 """
706 Validation conditionnelles de certains champs.
707 """
708 cleaned_data = self.cleaned_data
709
710 if cleaned_data.get("local") is False \
711 and cleaned_data.get("expatrie") is False:
712 msg = "Le poste doit au moins être ouvert localement " \
713 "ou aux expatriés"
714 self._errors["local"] = self.error_class([msg])
715 self._errors["expatrie"] = ''
716 raise forms.ValidationError(msg)
717
718 return cleaned_data
719
720
721 class ChoosePosteForm(forms.Form):
722 class Meta:
723 fields = ('poste',)
724
725 # La liste des choix est laissée vide. Voir PosteForm.__init__.
726 postes_dae = forms.ChoiceField(choices=(), required=False)
727 postes_rh = forms.ChoiceField(choices=(), required=False)
728
729 def __init__(self, request=None, *args, **kwargs):
730 super(ChoosePosteForm, self).__init__(*args, **kwargs)
731 self.fields['postes_dae'].choices = self._poste_dae_choices(request)
732 self.fields['postes_rh'].choices = self._poste_rh_choices(request)
733
734 def _poste_dae_choices(self, request):
735 """ Menu déroulant pour les postes."""
736 postes_dae = dae.Poste.objects.ma_region_ou_service(request.user) \
737 .exclude(etat__in=(POSTE_ETAT_FINALISE, )) \
738 .annotate(num_dae=Count('dae_dossiers')) \
739 .filter(num_dae=0) \
740 .order_by('implantation', '-date_debut', )
741
742 return [('', '----------')] + \
743 [('dae-%s' % p.id, label_poste_display(p)) for p in postes_dae]
744
745 def _poste_rh_choices(self, request):
746 """ Menu déroulant pour les postes."""
747 postes_dae = dae.Poste.objects.exclude(etat__in=(POSTE_ETAT_FINALISE, ))
748 today = datetime.date.today()
749 id_poste_dae_commences = [p.id_rh_id for p in postes_dae if p.id_rh is not None]
750 postes_rh = rh.Poste.objects.ma_region_ou_service(request.user) \
751 .exclude(id__in=id_poste_dae_commences) \
752 .filter(Q(date_debut__lte=today) &
753 (Q(date_fin__gte=today) |
754 Q(date_fin__isnull=True))
755 ) \
756 .order_by('implantation', '-date_debut', )
757
758 return [('', '----------')] + \
759 [('rh-%s' % p.id, label_poste_display(p)) for p in postes_rh]
760
761 def clean(self):
762 cleaned_data = super(ChoosePosteForm, self).clean()
763 postes_dae = cleaned_data.get("postes_dae")
764 postes_rh = cleaned_data.get("postes_rh")
765 if (postes_dae is u"" and postes_rh is u"") or \
766 (postes_dae is not u"" and postes_rh is not u""):
767 raise forms.ValidationError("Choisissez un poste DAE ou un poste RH")
768 return cleaned_data
769
770 def redirect(self):
771 poste_dae_key = self.cleaned_data.get("postes_dae")
772 if poste_dae_key is not u"":
773 return redirect(reverse('embauche', args=(poste_dae_key,)))
774 poste_rh_key = self.cleaned_data.get("postes_rh")
775 if poste_rh_key is not u"":
776 return redirect("%s?creer_dossier_dae='M'" % reverse('poste', args=(poste_rh_key,)))
777
778 class EmployeForm(forms.ModelForm):
779 """ Formulaire des employés. """
780 class Meta:
781 model = dae.Employe
782 fields = ('employe', 'nom', 'prenom', 'genre')
783
784 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
785 employe = forms.ChoiceField(choices=(), required=False)
786
787 def __init__(self, *args, **kwargs):
788 """ Mise à jour dynamique du contenu du menu des employés. """
789 request = kwargs.pop('request', None)
790 super(EmployeForm, self).__init__(*args, **kwargs)
791 self.fields['employe'].choices = _employe_choices(self, request)
792
793
794 class DossierForm(
795 filtered_archived_fields_form_factory(
796 'classement',
797 'classement_anterieur',
798 'classement_titulaire_anterieur',
799 ),
800 forms.ModelForm):
801 """ Formulaire des dossiers. """
802 class Meta:
803 exclude = ('etat', 'employe', 'poste', 'date_debut',)
804 model = dae.Dossier
805 widgets = dict(statut_residence=forms.RadioSelect(),
806 contrat_date_debut=admin_widgets.AdminDateWidget(),
807 contrat_date_fin=admin_widgets.AdminDateWidget(),
808 )
809
810 WF_HELP_TEXT = ""
811
812
813 class PosteWorkflowForm(WorkflowFormMixin):
814 bouton_libelles = POSTE_ETATS_BOUTONS
815
816 class Meta:
817 fields = ('etat', )
818 model = dae.Poste
819
820 def __init__(self, *args, **kwargs):
821 super(PosteWorkflowForm, self).__init__(*args, **kwargs)
822 self.fields['etat'].help_text = WF_HELP_TEXT
823
824
825 class DossierWorkflowForm(WorkflowFormMixin):
826 bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste...
827
828 class Meta:
829 fields = ('etat', )
830 model = dae.Dossier
831
832 def __init__(self, *args, **kwargs):
833 super(DossierWorkflowForm, self).__init__(*args, **kwargs)
834 self.fields['etat'].help_text = WF_HELP_TEXT
835 self._etat_initial = self.instance.etat
836
837 def save(self):
838 super(DossierWorkflowForm, self).save()
839 poste = self.instance.poste
840
841 if poste.etat == self._etat_initial:
842 poste.etat = self.instance.etat
843 poste.save()
844
845 # créer le commentaire automatique pour le poste associé
846 commentaire = WorkflowCommentaire()
847 commentaire.content_object = poste
848 texte = u"Validation automatique à travers le dossier [%s] de %s\n%s" %(
849 self.instance.id,
850 self.instance,
851 self.data.get('commentaire', ''),
852 )
853 commentaire.texte = texte
854 commentaire.etat_initial = self.instance._etat_courant
855 commentaire.etat_final = self.instance.etat
856 commentaire.owner = self.request.user
857 commentaire.save()
858
859
860 class ContratForm(forms.ModelForm):
861
862 class Meta:
863 fields = ('type_contrat', 'fichier', )
864 model = dae.Contrat
865
866
867 class DAENumeriseeForm(forms.ModelForm):
868
869 class Meta:
870 model = dae.Dossier
871 fields = ('dae_numerisee',)
872
873
874 class DAEFinaliseesSearchForm(forms.Form):
875 q = forms.CharField(
876 label='Recherche', required=False,
877 widget=forms.TextInput(attrs={'size': 40})
878 )
879 importees = forms.ChoiceField(
880 label='Importation', required=False, choices=(
881 ('', ''),
882 ('oui', 'DAE importées seulement'),
883 ('non', 'DAE non-importées seulement'),
884 )
885 )