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