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