5834
[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
f40a4829
BS
32def 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
2e672700
OL
46class 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
f258e4e7
OL
110def _implantation_choices(obj, request):
111 # TRAITEMENT NORMAL
3383b2d1 112 employe = groups.get_employe_from_user(request.user)
01971ac9 113 q = Q(**{'zone_administrative__in': groups.get_zones_from_user(request.user)})
f258e4e7
OL
114
115 # TRAITEMENT DRH
3383b2d1 116 user_groupes = [g.name for g in request.user.groups.all()]
713b824a
OL
117 if groups.DRH_NIVEAU_1 in user_groupes or \
118 groups.DRH_NIVEAU_2 in user_groupes:
f258e4e7 119 q = Q()
5a1f75cb
EMS
120 return [('', '----------')] + \
121 [(i.id, unicode(i), )for i in ref.Implantation.objects.filter(q)]
122
f258e4e7
OL
123
124def _employe_choices(obj, request):
f258e4e7 125 # TRAITEMENT NORMAL
3383b2d1 126 employe = groups.get_employe_from_user(request.user)
b0cf30b8 127 q_dae_region_service = Q(
01971ac9
BS
128 poste__implantation__zone_administrative__in=(
129 groups.get_zones_from_user(request.user)
5a1f75cb 130 )
b0cf30b8
EMS
131 )
132 q_rh_region_service = Q(
01971ac9
BS
133 poste__implantation__zone_administrative__in=(
134 groups.get_zones_from_user(request.user)
5a1f75cb 135 )
b0cf30b8 136 )
f258e4e7 137 # TRAITEMENT DRH
3383b2d1 138 user_groupes = [g.name for g in request.user.groups.all()]
713b824a
OL
139 if groups.DRH_NIVEAU_1 in user_groupes or \
140 groups.DRH_NIVEAU_2 in user_groupes:
072820fc
OL
141 q_dae_region_service = Q()
142 q_rh_region_service = Q()
f258e4e7 143
5a1f75cb
EMS
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 ]
072820fc
OL
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))
f258e4e7 161 id_copies = [p.id_rh_id for p in copies.all()]
072820fc 162
5a1f75cb
EMS
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
67c15007
OL
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
0339920c
OL
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)
f258e4e7 186
0339920c
OL
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
5a1f75cb 192
f258e4e7 193
4bce4d24
OL
194def label_poste_display(poste):
195 """Formate un visuel pour un poste dans une liste déroulante"""
23294f7d
OL
196 annee = ""
197 if poste.date_debut:
198 annee = poste.date_debut.year
9c1ff333
OL
199
200 nom = poste.nom
67ae0181 201 label = u"%s (%s) %s [%s]" % (
34950f36
OL
202 annee,
203 poste.implantation.nom_court,
204 nom,
67ae0181 205 #poste.type_poste.categorie_emploi.nom,
34950f36 206 poste.id,
93817ef3 207 )
4bce4d24 208 return label
9cb4de55 209
2e672700 210
874949f3 211PostePieceFormSet = inlineformset_factory(dae.Poste, dae.PostePiece,)
25086dcf 212DossierPieceForm = inlineformset_factory(dae.Dossier, dae.DossierPiece)
2e672700 213
874949f3
OL
214# Ce formset est utilisé dans le cas de la création de poste prépopulé avec les
215# données de RH
216FinancementFormSetInitial = inlineformset_factory(
2e672700
OL
217 dae.Poste,
218 dae.PosteFinancement,
219 formset=BaseInlineFormSetWithInitial,
220 extra=2
5a1f75cb 221)
874949f3
OL
222FinancementFormSet = inlineformset_factory(
223 dae.Poste,
224 dae.PosteFinancement,
225 extra=2
226)
5a1f75cb 227
03b395db 228
f40a4829
BS
229class DossierComparaisonForm(
230 filtered_archived_fields_form_factory(
231 'classement',
232 ),
233 forms.ModelForm):
11f22317 234
03b395db 235 recherche = AutoCompleteSelectField('dossiers', required=False)
5a1f75cb
EMS
236 poste = forms.CharField(
237 max_length=255, widget=forms.TextInput(attrs={'size': '60'})
238 )
b9098c33
BS
239 cmp_dossier = forms.IntegerField(
240 widget=forms.widgets.HiddenInput,
241 required=False
242 )
03b395db 243
320d7584 244 class Meta:
03b395db 245 model = dae.DossierComparaison
320d7584 246 exclude = ('dossier',)
03b395db 247
320d7584
EMS
248DossierComparaisonFormSet = modelformset_factory(
249 dae.DossierComparaison, extra=3, max_num=3, form=DossierComparaisonForm
25086dcf 250)
03b395db 251
5a1f75cb 252
f40a4829
BS
253class PosteComparaisonForm(
254 filtered_archived_fields_form_factory('classement'),
255 forms.ModelForm):
11f22317 256
e503e64d 257 recherche = AutoCompleteSelectField('dae_postes', required=False)
068d1462 258
b9098c33
BS
259 cmp_poste = forms.IntegerField(
260 widget=forms.widgets.HiddenInput,
261 required=False,
262 )
263
320d7584 264 class Meta:
068d1462 265 model = dae.PosteComparaison
320d7584 266 exclude = ('poste',)
068d1462 267
874949f3
OL
268# Ce formset est utilisé dans le cas de la création de poste prépopulé avec les
269# données de RH
270PosteComparaisonFormSetInitial = inlineformset_factory(
2e672700
OL
271 dae.Poste,
272 dae.PosteComparaison,
273 extra=3,
274 max_num=3,
275 form=PosteComparaisonForm,
2e672700 276 formset=BaseInlineFormSetWithInitial,
25086dcf 277)
874949f3
OL
278PosteComparaisonFormSet = inlineformset_factory(
279 dae.Poste,
280 dae.PosteComparaison,
281 extra=3,
282 max_num=3,
283 form=PosteComparaisonForm,
284)
068d1462 285
5a1f75cb 286
156d6b4d
BS
287class FlexibleRemunForm(
288 filtered_archived_fields_form_factory(
289 'type',
290 ),
291 forms.ModelForm):
bc8dcc0e 292 # Utilisé dans templats.
0a085c42
OL
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
4718c21c
BS
299 def __init__(self, *a, **kw):
300 super(FlexibleRemunForm, self).__init__(*a, **kw)
661da766 301 # self.fields['type'].widget = ReadOnlyChoiceWidget(choices=self.fields['type'].choices)
4718c21c 302
dc4b78a7
OL
303 def clean_devise(self):
304 devise = self.cleaned_data['devise']
67173010
OL
305 if devise.code == 'EUR':
306 return devise
5a1f75cb
EMS
307 implantation = ref.Implantation.objects.get(
308 id=self.data['implantation']
309 )
2455f48d 310 liste_taux = devise.tauxchange_set.order_by('-annee')
dc4b78a7 311 if len(liste_taux) == 0:
5a1f75cb
EMS
312 raise forms.ValidationError(
313 u"La devise %s n'a pas de taux pour l'implantation %s" %
314 (devise, implantation)
315 )
dc4b78a7
OL
316 else:
317 return devise
318
6bec5651
BS
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
4718c21c 344
661da766 345class ReadOnlyRemunForm(FlexibleRemunForm):
bc8dcc0e
BS
346 # Utilisé dans templats.
347
661da766 348 def __init__(self, *a, **kw):
bc8dcc0e 349 super (ReadOnlyRemunForm, self).__init__(*a, **kw)
661da766
BS
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
4718c21c
BS
360class GroupedInlineFormset(BaseInlineFormSet):
361
661da766
BS
362 def set_groups(self,
363 groups,
364 group_accessor,
365 choice_overrides=[]):
366
4718c21c 367
661da766 368 # Create pre-defined groups.
6bec5651 369 self.groups = OrderedDict()
661da766
BS
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.
4718c21c 378 for form in self.forms:
661da766
BS
379 if bool(form.initial):
380 grp = group_accessor(form)
381 if grp[0] not in self.groups:
382 self.groups[grp[0]] = {
383 'name': grp[1],
384 'key': grp[0],
385 'forms': [],
386 }
387 self.groups[grp[0]]['forms'].append(form)
388
389 # Add extra forms (n extra for each grop).
390 tmp_extras = []
391 for i in xrange(len(self.groups) * self.extra):
bc8dcc0e
BS
392 tmp_extras.insert(0,
393 self._construct_form(self.initial_form_count() + i))
661da766
BS
394
395 for g in self.groups:
396 for i in xrange(self.extra):
56db8597
BS
397 tmp_form = tmp_extras.pop()
398 self.groups[g]['forms'].append(tmp_form)
399 self.forms.append(tmp_form)
6bec5651 400
661da766
BS
401
402 # Override form choices with the data provided in
403 # choice_overrides
404 for key in choice_overrides:
405 for form in self.groups.get(key, {'forms': []})['forms']:
406 for field_key in choice_overrides[key]:
407 form.fields[field_key].choices = choice_overrides[
408 key][field_key]
409
410
411 # Create an iterable for easier access in template.
4718c21c
BS
412 self.group_list = self.groups.values()
413
661da766
BS
414 # def set_groups(self, group_accessor, groups, group_order=[]):
415 # """
416 # group_accessor: A function that will get the key and name from
417 # each form.
418 # group_order: list the group keys here in a list and
419 # GroupedInlineFormset.groups will be ordered (ordereddict) by
420 # the key sequence provided here. Any missing key from the
421 # sequence will
422 # """
423
424 # # Build group list.
425 # self.groups = OrderedDict()
426 # temp_groups = {}
427 # # self.groups_and_forms = []
428 # for form in self.forms:
429 # group_key, group_name = group_accessor(form)
430 # if not temp_groups.has_key(group_key):
431 # temp_groups[group_key] = {
432 # 'name': group_name,
433 # 'key': group_key,
434 # 'forms': [],
435 # }
436 # temp_groups[group_key]['forms'].append(form)
437
438 # for order_key in group_order:
439 # if temp_groups.has_key(order_key):
440 # self.groups[order_key] = temp_groups.pop(order_key)
441
442 # for key in temp_groups:
443 # self.groups[key] = temp_groups[key]
444
445 # del temp_groups
446
447 # self.group_list = self.groups.values()
448
4718c21c
BS
449
450def remun_formset_factory(parent_model,
451 model,
452 form=forms.ModelForm,
453 formset=GroupedInlineFormset,
454 fk_name=None,
455 fields=None,
456 exclude=None,
457 can_order=False,
458 can_delete=True,
bc8dcc0e 459 read_only=False,
661da766 460 extra=2,
4718c21c 461 max_num=None,
6bec5651 462 formfield_callback=None,
661da766
BS
463 groups=None,
464 choice_overrides=[]):
4718c21c 465 trs = rh.TypeRemuneration.objects.all()
661da766 466 # extra = max_num = trs.count()
4718c21c
BS
467 fk = _get_foreign_key(parent_model, model, fk_name=fk_name)
468 # enforce a max_num=1 when the foreign key to the parent model is unique.
469 if fk.unique:
470 max_num = 1
471 kwargs = {
472 'form': form,
473 'formfield_callback': formfield_callback,
474 'formset': formset,
475 'extra': extra,
476 'can_delete': can_delete,
477 'can_order': can_order,
478 'fields': fields,
479 'exclude': exclude,
480 'max_num': max_num,
481 }
482 FormSet = modelformset_factory(model, **kwargs)
483 FormSet.fk = fk
bc8dcc0e 484 FormSet.read_only = read_only
4718c21c
BS
485
486 def grouper(form):
661da766
BS
487 rtype = form.initial['type']
488 if not isinstance(rtype, rh.TypeRemuneration):
489 rtype = rh.TypeRemuneration.objects.get(id=rtype)
490 return (rtype.nature_remuneration,
491 rtype.nature_remuneration
492 )
4718c21c 493
4718c21c 494
661da766 495
bc8dcc0e
BS
496 # Monkey patch FormSet.
497 def __init__(inst, *a, **kw):
498 super(inst.__class__, inst).__init__(*a, **kw)
661da766
BS
499 inst.set_groups(groups, grouper, choice_overrides)
500
4718c21c
BS
501 FormSet.__init__ = __init__
502
503 return FormSet
504
505
f5e9e6a4
BS
506def remun_formset_factory_factory(
507 read_only=False,
508 parent_model=dae.Dossier,
509 model=dae.Remuneration,
510 exclude_archived=False):
bc8dcc0e
BS
511 """
512 Don't we love factory factories?
513 """
514
515 null_choice = ('', '-' * 10)
516 extras = 2 if not read_only else 0
517 can_delete = False if read_only else True
518 form_class = ReadOnlyRemunForm if read_only else FlexibleRemunForm
519
f5e9e6a4
BS
520 choice_override_extra_q = {}
521
522 if exclude_archived:
523 choice_override_extra_q.update({
524 'archive': False
525 })
526
bc8dcc0e 527 return remun_formset_factory(
b9098c33
BS
528 parent_model,
529 model,
bc8dcc0e
BS
530 form=form_class,
531 extra=extras,
532 can_delete=can_delete,
533 read_only=read_only,
46dab81b 534 groups = rh.NATURE_REMUNERATION_CHOICES,
bc8dcc0e
BS
535 choice_overrides = {
536 u'Traitement': {
537 'type': [null_choice] + list(
538 rh.TypeRemuneration.objects.filter(
f5e9e6a4
BS
539 nature_remuneration=u'Traitement',
540 **choice_override_extra_q).values_list(
bc8dcc0e
BS
541 'id', 'nom')
542 )
543 },
544 u'Indemnité': {
545 'type': [null_choice] + list(
546 rh.TypeRemuneration.objects.filter(
f5e9e6a4
BS
547 nature_remuneration=u'Indemnité',
548 **choice_override_extra_q).values_list(
bc8dcc0e
BS
549 'id', 'nom')
550 )
551 },
552 u'Charges': {
553 'type': [null_choice] + list(
554 rh.TypeRemuneration.objects.filter(
f5e9e6a4
BS
555 nature_remuneration=u'Charges',
556 **choice_override_extra_q).values_list(
bc8dcc0e
BS
557 'id', 'nom')
558 )
559 },
560 u'Accessoire': {
561 'type': [null_choice] + list(
562 rh.TypeRemuneration.objects.filter(
f5e9e6a4
BS
563 nature_remuneration=u'Accessoire',
564 **choice_override_extra_q).values_list(
bc8dcc0e
BS
565 'id', 'nom')
566 )
567 },
568 u'RAS': {
569 'type': [null_choice] + list(
570 rh.TypeRemuneration.objects.filter(
f5e9e6a4
BS
571 nature_remuneration=u'RAS',
572 **choice_override_extra_q).values_list(
bc8dcc0e
BS
573 'id', 'nom')
574 )
575 },
f5e9e6a4 576 },
bc8dcc0e 577 )
0a085c42 578
b9098c33
BS
579RemunForm = remun_formset_factory_factory(
580 read_only=False,
581 parent_model=dae.Dossier,
582 model=dae.Remuneration,
f5e9e6a4 583 exclude_archived=True,
b9098c33
BS
584)
585
586ReadOnlyRemunFormSet = remun_formset_factory_factory(
587 read_only=True,
588 parent_model=dae.Dossier,
589 model=dae.Remuneration,
590 )
591
592PosteCompReadOnlyRemunFormSet = remun_formset_factory_factory(
593 read_only=True,
594 parent_model=dae.PosteComparaison,
595 model=dae.PosteComparaisonRemuneration,
596 )
597
598DossierCompReadOnlyRemunFormSet = remun_formset_factory_factory(
599 read_only=True,
600 parent_model=dae.DossierComparaison,
601 model=dae.DossierComparaisonRemuneration,
602 )
0316268d
BS
603RHReadOnlyRemunFormSet = remun_formset_factory_factory(
604 read_only=True,
605 parent_model=rh.Dossier,
606 model=rh.Remuneration,
607 )
5a1f75cb 608
f40a4829
BS
609class PosteForm(filtered_archived_fields_form_factory(
610 'classement_min',
611 'classement_max',),
612 forms.ModelForm):
5d680e84 613 """ Formulaire des postes. """
12c7f8a7 614
ea7adc69 615 # On ne propose que les services actifs
5a1f75cb
EMS
616 service = forms.ModelChoiceField(
617 queryset=rh.Service.objects.all(), required=True
618 )
ea7adc69 619
5a1f75cb 620 responsable = AutoCompleteSelectField('responsables', required=True)
12c7f8a7
OL
621 #responsable = forms.ModelChoiceField(
622 # queryset=rh.Poste.objects.select_related(depth=1))
623
624 # La liste des choix est laissée vide. Voir __init__ pour la raison.
625 poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste",
626 choices=(), required=False)
11f22317 627
5a1f75cb
EMS
628 valeur_point_min = forms.ModelChoiceField(
629 queryset=rh.ValeurPoint.actuelles.all(), required=False
630 )
631 valeur_point_max = forms.ModelChoiceField(
632 queryset=rh.ValeurPoint.actuelles.all(), required=False
633 )
11f22317 634
5d680e84
NC
635 class Meta:
636 model = dae.Poste
c3be904d
OL
637 fields = ('type_intervention',
638 'poste', 'implantation', 'type_poste', 'service', 'nom',
154677c3 639 'responsable', 'local', 'expatrie', 'mise_a_disposition',
b15bf543 640 'appel', 'date_debut', 'date_fin',
5d680e84
NC
641 'regime_travail', 'regime_travail_nb_heure_semaine',
642 'classement_min', 'classement_max',
643 'valeur_point_min', 'valeur_point_max',
3d627bfd 644 'devise_min', 'devise_max',
5f61bccb
OL
645 'salaire_min', 'salaire_max',
646 'indemn_expat_min', 'indemn_expat_max',
647 'indemn_fct_min', 'indemn_fct_max',
648 'charges_patronales_min', 'charges_patronales_max',
5d680e84
NC
649 'autre_min', 'autre_max', 'devise_comparaison',
650 'comp_locale_min', 'comp_locale_max',
651 'comp_universite_min', 'comp_universite_max',
652 'comp_fonctionpub_min', 'comp_fonctionpub_max',
653 'comp_ong_min', 'comp_ong_max',
8fa94e8b 654 'comp_autre_min', 'comp_autre_max',
2e092e0c 655 'justification',
8fa94e8b 656 )
c3be904d
OL
657 widgets = dict(type_intervention=forms.RadioSelect(),
658 appel=forms.RadioSelect(),
3d627bfd 659 nom=forms.TextInput(attrs={'size': 60},),
e88caaf0
OL
660 date_debut=admin_widgets.AdminDateWidget(),
661 date_fin=admin_widgets.AdminDateWidget(),
2e092e0c 662 justification=forms.Textarea(attrs={'cols': 80},),
3d627bfd 663 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
664 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
665 )
5d680e84 666
c2458db6 667 def __init__(self, *args, **kwargs):
5d680e84
NC
668 """ Mise à jour dynamique du contenu du menu des postes.
669
670 Si on ne met le menu à jour de cette façon, à chaque instantiation du
671 formulaire, son contenu est mis en cache par le système et il ne
672 reflète pas les changements apportés par les ajouts, modifications,
673 etc...
674
139686f2
NC
675 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
676 car le "id" de chaque choix est spécial (voir _poste_choices).
677
5d680e84 678 """
c2458db6 679 request = kwargs.pop('request')
5d680e84 680 super(PosteForm, self).__init__(*args, **kwargs)
f258e4e7 681 self.fields['poste'].choices = self._poste_choices(request)
9c1ff333 682
5a1f75cb
EMS
683 self.fields['implantation'].choices = \
684 _implantation_choices(self, request)
5d680e84 685
cc3098d0
OL
686 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
687 if self.instance and self.instance.id is None:
688 dossiers = self.instance.get_dossiers()
689 if len(dossiers) > 0:
09aa8374 690 self.initial['service'] = dossiers[0].poste.service
9508a5b8 691
f258e4e7 692 def _poste_choices(self, request):
5d680e84 693 """ Menu déroulant pour les postes.
9c1ff333 694 Constitué des postes de RH
5d680e84 695 """
9c1ff333
OL
696 postes_rh = rh.Poste.objects.ma_region_ou_service(request.user).all()
697 postes_rh = postes_rh.select_related(depth=1)
5d680e84 698
98d51b59 699 return [('', 'Nouveau poste')] + \
9c1ff333
OL
700 sorted([('rh-%s' % p.id, label_poste_display(p)) for p in
701 postes_rh],
5d680e84 702 key=lambda t: t[1])
3ed49093 703
4dd75e7b
OL
704 def clean(self):
705 """
706 Validation conditionnelles de certains champs.
707 """
5a1f75cb 708 cleaned_data = self.cleaned_data
4dd75e7b 709
5a1f75cb
EMS
710 if cleaned_data.get("local") is False \
711 and cleaned_data.get("expatrie") is False:
712 msg = "Le poste doit au moins être ouvert localement " \
713 "ou aux expatriés"
f42c6e20
OL
714 self._errors["local"] = self.error_class([msg])
715 self._errors["expatrie"] = ''
716 raise forms.ValidationError(msg)
f42c6e20 717
4dd75e7b
OL
718 return cleaned_data
719
3ed49093 720
34950f36 721class ChoosePosteForm(forms.Form):
139686f2 722 class Meta:
139686f2
NC
723 fields = ('poste',)
724
725 # La liste des choix est laissée vide. Voir PosteForm.__init__.
34950f36
OL
726 postes_dae = forms.ChoiceField(choices=(), required=False)
727 postes_rh = forms.ChoiceField(choices=(), required=False)
139686f2 728
4ee6d70a 729 def __init__(self, request=None, *args, **kwargs):
139686f2 730 super(ChoosePosteForm, self).__init__(*args, **kwargs)
34950f36
OL
731 self.fields['postes_dae'].choices = self._poste_dae_choices(request)
732 self.fields['postes_rh'].choices = self._poste_rh_choices(request)
139686f2 733
34950f36
OL
734 def _poste_dae_choices(self, request):
735 """ Menu déroulant pour les postes."""
736 postes_dae = dae.Poste.objects.ma_region_ou_service(request.user) \
737 .exclude(etat__in=(POSTE_ETAT_FINALISE, )) \
738 .annotate(num_dae=Count('dae_dossiers')) \
739 .filter(num_dae=0) \
67ae0181 740 .order_by('implantation', '-date_debut', )
139686f2 741
98d51b59 742 return [('', '----------')] + \
34950f36
OL
743 [('dae-%s' % p.id, label_poste_display(p)) for p in postes_dae]
744
745 def _poste_rh_choices(self, request):
746 """ Menu déroulant pour les postes."""
80be36aa 747 postes_dae = dae.Poste.objects.exclude(etat__in=(POSTE_ETAT_FINALISE, ))
bed0c4c9 748 today = datetime.date.today()
80be36aa 749 id_poste_dae_commences = [p.id_rh_id for p in postes_dae if p.id_rh is not None]
34950f36 750 postes_rh = rh.Poste.objects.ma_region_ou_service(request.user) \
80be36aa 751 .exclude(id__in=id_poste_dae_commences) \
bed0c4c9
BS
752 .filter(Q(date_debut__lte=today) &
753 (Q(date_fin__gte=today) |
754 Q(date_fin__isnull=True))
755 ) \
67ae0181 756 .order_by('implantation', '-date_debut', )
34950f36
OL
757
758 return [('', '----------')] + \
759 [('rh-%s' % p.id, label_poste_display(p)) for p in postes_rh]
139686f2 760
80be36aa
OL
761 def clean(self):
762 cleaned_data = super(ChoosePosteForm, self).clean()
763 postes_dae = cleaned_data.get("postes_dae")
764 postes_rh = cleaned_data.get("postes_rh")
765 if (postes_dae is u"" and postes_rh is u"") or \
766 (postes_dae is not u"" and postes_rh is not u""):
767 raise forms.ValidationError("Choisissez un poste DAE ou un poste RH")
768 return cleaned_data
769
770 def redirect(self):
771 poste_dae_key = self.cleaned_data.get("postes_dae")
772 if poste_dae_key is not u"":
773 return redirect(reverse('embauche', args=(poste_dae_key,)))
774 poste_rh_key = self.cleaned_data.get("postes_rh")
775 if poste_rh_key is not u"":
67ae0181 776 return redirect("%s?creer_dossier_dae='M'" % reverse('poste', args=(poste_rh_key,)))
139686f2 777
139686f2
NC
778class EmployeForm(forms.ModelForm):
779 """ Formulaire des employés. """
780 class Meta:
781 model = dae.Employe
782 fields = ('employe', 'nom', 'prenom', 'genre')
783
784 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
785 employe = forms.ChoiceField(choices=(), required=False)
786
ac6235f6 787 def __init__(self, *args, **kwargs):
139686f2 788 """ Mise à jour dynamique du contenu du menu des employés. """
ac6235f6 789 request = kwargs.pop('request', None)
139686f2 790 super(EmployeForm, self).__init__(*args, **kwargs)
f258e4e7 791 self.fields['employe'].choices = _employe_choices(self, request)
139686f2 792
139686f2 793
f40a4829
BS
794class DossierForm(
795 filtered_archived_fields_form_factory(
796 'classement',
797 'classement_anterieur',
798 'classement_titulaire_anterieur',
799 ),
800 forms.ModelForm):
139686f2
NC
801 """ Formulaire des dossiers. """
802 class Meta:
5a1f75cb 803 exclude = ('etat', 'employe', 'poste', 'date_debut',)
139686f2 804 model = dae.Dossier
4d25e2ba 805 widgets = dict(statut_residence=forms.RadioSelect(),
0e0aeb7e
OL
806 contrat_date_debut=admin_widgets.AdminDateWidget(),
807 contrat_date_fin=admin_widgets.AdminDateWidget(),
4d25e2ba 808 )
e6f52402 809
3799cafc 810WF_HELP_TEXT = ""
e0b93e3a 811
5a1f75cb 812
e6f52402 813class PosteWorkflowForm(WorkflowFormMixin):
56589624 814 bouton_libelles = POSTE_ETATS_BOUTONS
5a1f75cb 815
e6f52402
OL
816 class Meta:
817 fields = ('etat', )
818 model = dae.Poste
9536ea21 819
e0b93e3a 820 def __init__(self, *args, **kwargs):
e54b7d5d 821 super(PosteWorkflowForm, self).__init__(*args, **kwargs)
e0b93e3a
OL
822 self.fields['etat'].help_text = WF_HELP_TEXT
823
824
e6f52402 825class DossierWorkflowForm(WorkflowFormMixin):
5a1f75cb
EMS
826 bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste...
827
e6f52402 828 class Meta:
9e40cfbe 829 fields = ('etat', )
e6f52402 830 model = dae.Dossier
e0b93e3a
OL
831
832 def __init__(self, *args, **kwargs):
e54b7d5d 833 super(DossierWorkflowForm, self).__init__(*args, **kwargs)
e0b93e3a 834 self.fields['etat'].help_text = WF_HELP_TEXT
e54b7d5d 835 self._etat_initial = self.instance.etat
e0b93e3a 836
e54b7d5d
EMS
837 def save(self):
838 super(DossierWorkflowForm, self).save()
839 poste = self.instance.poste
66fefd2f
OL
840
841 # créer le commentaire automatique pour le poste associé
842 commentaire = WorkflowCommentaire()
843 commentaire.content_object = poste
844 texte = u"Validation automatique à travers le dossier [%s] de %s\n%s" %(
845 self.instance.id,
846 self.instance,
847 self.data.get('commentaire', ''),
848 )
849 commentaire.texte = texte
850 commentaire.etat_initial = self.instance._etat_courant
851 commentaire.etat_final = self.instance.etat
852 commentaire.owner = self.request.user
853 commentaire.save()
854
855 # force l'état du poste
856 poste.etat = self.instance.etat
857 poste.save()
9536ea21 858
5a1f75cb 859
9536ea21
EMS
860class ContratForm(forms.ModelForm):
861
862 class Meta:
9dfa4296 863 fields = ('type_contrat', 'fichier', )
9536ea21
EMS
864 model = dae.Contrat
865
5a1f75cb 866
c3f0b49f
EMS
867class DAENumeriseeForm(forms.ModelForm):
868
869 class Meta:
870 model = dae.Dossier
871 fields = ('dae_numerisee',)
cbfd7bd4
EMS
872
873
874class DAEFinaliseesSearchForm(forms.Form):
875 q = forms.CharField(
876 label='Recherche', required=False,
877 widget=forms.TextInput(attrs={'size': 40})
878 )
879 importees = forms.ChoiceField(
880 label='Importation', required=False, choices=(
881 ('', ''),
882 ('oui', 'DAE importées seulement'),
883 ('non', 'DAE non-importées seulement'),
884 )
885 )