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