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