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