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