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