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