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