Fix for titulaire antérieur
[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 )
0316268d
BS
585RHReadOnlyRemunFormSet = remun_formset_factory_factory(
586 read_only=True,
587 parent_model=rh.Dossier,
588 model=rh.Remuneration,
589 )
5a1f75cb 590
f40a4829
BS
591class PosteForm(filtered_archived_fields_form_factory(
592 'classement_min',
593 'classement_max',),
594 forms.ModelForm):
5d680e84 595 """ Formulaire des postes. """
12c7f8a7 596
ea7adc69 597 # On ne propose que les services actifs
5a1f75cb
EMS
598 service = forms.ModelChoiceField(
599 queryset=rh.Service.objects.all(), required=True
600 )
ea7adc69 601
5a1f75cb 602 responsable = AutoCompleteSelectField('responsables', required=True)
12c7f8a7
OL
603 #responsable = forms.ModelChoiceField(
604 # queryset=rh.Poste.objects.select_related(depth=1))
605
606 # La liste des choix est laissée vide. Voir __init__ pour la raison.
607 poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste",
608 choices=(), required=False)
11f22317 609
5a1f75cb
EMS
610 valeur_point_min = forms.ModelChoiceField(
611 queryset=rh.ValeurPoint.actuelles.all(), required=False
612 )
613 valeur_point_max = forms.ModelChoiceField(
614 queryset=rh.ValeurPoint.actuelles.all(), required=False
615 )
11f22317 616
5d680e84
NC
617 class Meta:
618 model = dae.Poste
c3be904d
OL
619 fields = ('type_intervention',
620 'poste', 'implantation', 'type_poste', 'service', 'nom',
154677c3 621 'responsable', 'local', 'expatrie', 'mise_a_disposition',
b15bf543 622 'appel', 'date_debut', 'date_fin',
5d680e84
NC
623 'regime_travail', 'regime_travail_nb_heure_semaine',
624 'classement_min', 'classement_max',
625 'valeur_point_min', 'valeur_point_max',
3d627bfd 626 'devise_min', 'devise_max',
5f61bccb
OL
627 'salaire_min', 'salaire_max',
628 'indemn_expat_min', 'indemn_expat_max',
629 'indemn_fct_min', 'indemn_fct_max',
630 'charges_patronales_min', 'charges_patronales_max',
5d680e84
NC
631 'autre_min', 'autre_max', 'devise_comparaison',
632 'comp_locale_min', 'comp_locale_max',
633 'comp_universite_min', 'comp_universite_max',
634 'comp_fonctionpub_min', 'comp_fonctionpub_max',
635 'comp_ong_min', 'comp_ong_max',
8fa94e8b 636 'comp_autre_min', 'comp_autre_max',
2e092e0c 637 'justification',
8fa94e8b 638 )
c3be904d
OL
639 widgets = dict(type_intervention=forms.RadioSelect(),
640 appel=forms.RadioSelect(),
3d627bfd 641 nom=forms.TextInput(attrs={'size': 60},),
e88caaf0
OL
642 date_debut=admin_widgets.AdminDateWidget(),
643 date_fin=admin_widgets.AdminDateWidget(),
2e092e0c 644 justification=forms.Textarea(attrs={'cols': 80},),
3d627bfd 645 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
646 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
647 )
5d680e84 648
c2458db6 649 def __init__(self, *args, **kwargs):
5d680e84
NC
650 """ Mise à jour dynamique du contenu du menu des postes.
651
652 Si on ne met le menu à jour de cette façon, à chaque instantiation du
653 formulaire, son contenu est mis en cache par le système et il ne
654 reflète pas les changements apportés par les ajouts, modifications,
655 etc...
656
139686f2
NC
657 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
658 car le "id" de chaque choix est spécial (voir _poste_choices).
659
5d680e84 660 """
c2458db6 661 request = kwargs.pop('request')
5d680e84 662 super(PosteForm, self).__init__(*args, **kwargs)
f258e4e7 663 self.fields['poste'].choices = self._poste_choices(request)
9c1ff333 664
5a1f75cb
EMS
665 self.fields['implantation'].choices = \
666 _implantation_choices(self, request)
5d680e84 667
cc3098d0
OL
668 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
669 if self.instance and self.instance.id is None:
670 dossiers = self.instance.get_dossiers()
671 if len(dossiers) > 0:
09aa8374 672 self.initial['service'] = dossiers[0].poste.service
9508a5b8 673
f258e4e7 674 def _poste_choices(self, request):
5d680e84 675 """ Menu déroulant pour les postes.
9c1ff333 676 Constitué des postes de RH
5d680e84 677 """
9c1ff333
OL
678 postes_rh = rh.Poste.objects.ma_region_ou_service(request.user).all()
679 postes_rh = postes_rh.select_related(depth=1)
5d680e84 680
98d51b59 681 return [('', 'Nouveau poste')] + \
9c1ff333
OL
682 sorted([('rh-%s' % p.id, label_poste_display(p)) for p in
683 postes_rh],
5d680e84 684 key=lambda t: t[1])
3ed49093 685
4dd75e7b
OL
686 def clean(self):
687 """
688 Validation conditionnelles de certains champs.
689 """
5a1f75cb 690 cleaned_data = self.cleaned_data
4dd75e7b 691
5a1f75cb
EMS
692 if cleaned_data.get("local") is False \
693 and cleaned_data.get("expatrie") is False:
694 msg = "Le poste doit au moins être ouvert localement " \
695 "ou aux expatriés"
f42c6e20
OL
696 self._errors["local"] = self.error_class([msg])
697 self._errors["expatrie"] = ''
698 raise forms.ValidationError(msg)
f42c6e20 699
4dd75e7b
OL
700 return cleaned_data
701
3ed49093 702
34950f36 703class ChoosePosteForm(forms.Form):
139686f2 704 class Meta:
139686f2
NC
705 fields = ('poste',)
706
707 # La liste des choix est laissée vide. Voir PosteForm.__init__.
34950f36
OL
708 postes_dae = forms.ChoiceField(choices=(), required=False)
709 postes_rh = forms.ChoiceField(choices=(), required=False)
139686f2 710
4ee6d70a 711 def __init__(self, request=None, *args, **kwargs):
139686f2 712 super(ChoosePosteForm, self).__init__(*args, **kwargs)
34950f36
OL
713 self.fields['postes_dae'].choices = self._poste_dae_choices(request)
714 self.fields['postes_rh'].choices = self._poste_rh_choices(request)
139686f2 715
34950f36
OL
716 def _poste_dae_choices(self, request):
717 """ Menu déroulant pour les postes."""
718 postes_dae = dae.Poste.objects.ma_region_ou_service(request.user) \
719 .exclude(etat__in=(POSTE_ETAT_FINALISE, )) \
720 .annotate(num_dae=Count('dae_dossiers')) \
721 .filter(num_dae=0) \
67ae0181 722 .order_by('implantation', '-date_debut', )
139686f2 723
98d51b59 724 return [('', '----------')] + \
34950f36
OL
725 [('dae-%s' % p.id, label_poste_display(p)) for p in postes_dae]
726
727 def _poste_rh_choices(self, request):
728 """ Menu déroulant pour les postes."""
80be36aa 729 postes_dae = dae.Poste.objects.exclude(etat__in=(POSTE_ETAT_FINALISE, ))
bed0c4c9 730 today = datetime.date.today()
80be36aa 731 id_poste_dae_commences = [p.id_rh_id for p in postes_dae if p.id_rh is not None]
34950f36 732 postes_rh = rh.Poste.objects.ma_region_ou_service(request.user) \
80be36aa 733 .exclude(id__in=id_poste_dae_commences) \
bed0c4c9
BS
734 .filter(Q(date_debut__lte=today) &
735 (Q(date_fin__gte=today) |
736 Q(date_fin__isnull=True))
737 ) \
67ae0181 738 .order_by('implantation', '-date_debut', )
34950f36
OL
739
740 return [('', '----------')] + \
741 [('rh-%s' % p.id, label_poste_display(p)) for p in postes_rh]
139686f2 742
80be36aa
OL
743 def clean(self):
744 cleaned_data = super(ChoosePosteForm, self).clean()
745 postes_dae = cleaned_data.get("postes_dae")
746 postes_rh = cleaned_data.get("postes_rh")
747 if (postes_dae is u"" and postes_rh is u"") or \
748 (postes_dae is not u"" and postes_rh is not u""):
749 raise forms.ValidationError("Choisissez un poste DAE ou un poste RH")
750 return cleaned_data
751
752 def redirect(self):
753 poste_dae_key = self.cleaned_data.get("postes_dae")
754 if poste_dae_key is not u"":
755 return redirect(reverse('embauche', args=(poste_dae_key,)))
756 poste_rh_key = self.cleaned_data.get("postes_rh")
757 if poste_rh_key is not u"":
67ae0181 758 return redirect("%s?creer_dossier_dae='M'" % reverse('poste', args=(poste_rh_key,)))
139686f2 759
139686f2
NC
760class EmployeForm(forms.ModelForm):
761 """ Formulaire des employés. """
762 class Meta:
763 model = dae.Employe
764 fields = ('employe', 'nom', 'prenom', 'genre')
765
766 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
767 employe = forms.ChoiceField(choices=(), required=False)
768
ac6235f6 769 def __init__(self, *args, **kwargs):
139686f2 770 """ Mise à jour dynamique du contenu du menu des employés. """
ac6235f6 771 request = kwargs.pop('request', None)
139686f2 772 super(EmployeForm, self).__init__(*args, **kwargs)
f8261271
BS
773 self.fields['employe'].choices = _employe_choices(
774 self,
775 request,
776 )
139686f2 777
139686f2 778
f40a4829
BS
779class DossierForm(
780 filtered_archived_fields_form_factory(
781 'classement',
782 'classement_anterieur',
783 'classement_titulaire_anterieur',
784 ),
785 forms.ModelForm):
139686f2 786 """ Formulaire des dossiers. """
4ef85bce 787
139686f2 788 class Meta:
5a1f75cb 789 exclude = ('etat', 'employe', 'poste', 'date_debut',)
139686f2 790 model = dae.Dossier
4d25e2ba 791 widgets = dict(statut_residence=forms.RadioSelect(),
0e0aeb7e
OL
792 contrat_date_debut=admin_widgets.AdminDateWidget(),
793 contrat_date_fin=admin_widgets.AdminDateWidget(),
4d25e2ba 794 )
e6f52402 795
3799cafc 796WF_HELP_TEXT = ""
e0b93e3a 797
5a1f75cb 798
e6f52402 799class PosteWorkflowForm(WorkflowFormMixin):
56589624 800 bouton_libelles = POSTE_ETATS_BOUTONS
5a1f75cb 801
e6f52402
OL
802 class Meta:
803 fields = ('etat', )
804 model = dae.Poste
9536ea21 805
e0b93e3a 806 def __init__(self, *args, **kwargs):
e54b7d5d 807 super(PosteWorkflowForm, self).__init__(*args, **kwargs)
e0b93e3a
OL
808 self.fields['etat'].help_text = WF_HELP_TEXT
809
810
e6f52402 811class DossierWorkflowForm(WorkflowFormMixin):
5a1f75cb
EMS
812 bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste...
813
e6f52402 814 class Meta:
9e40cfbe 815 fields = ('etat', )
e6f52402 816 model = dae.Dossier
e0b93e3a
OL
817
818 def __init__(self, *args, **kwargs):
e54b7d5d 819 super(DossierWorkflowForm, self).__init__(*args, **kwargs)
e0b93e3a 820 self.fields['etat'].help_text = WF_HELP_TEXT
e54b7d5d 821 self._etat_initial = self.instance.etat
e0b93e3a 822
e54b7d5d
EMS
823 def save(self):
824 super(DossierWorkflowForm, self).save()
825 poste = self.instance.poste
66fefd2f 826
72068c80
BS
827 if poste.etat == self._etat_initial:
828 poste.etat = self.instance.etat
829 poste.save()
830
831 # créer le commentaire automatique pour le poste associé
832 commentaire = WorkflowCommentaire()
833 commentaire.content_object = poste
834 texte = u"Validation automatique à travers le dossier [%s] de %s\n%s" %(
66fefd2f
OL
835 self.instance.id,
836 self.instance,
837 self.data.get('commentaire', ''),
838 )
72068c80
BS
839 commentaire.texte = texte
840 commentaire.etat_initial = self.instance._etat_courant
841 commentaire.etat_final = self.instance.etat
842 commentaire.owner = self.request.user
843 commentaire.save()
9536ea21 844
5a1f75cb 845
9536ea21
EMS
846class ContratForm(forms.ModelForm):
847
848 class Meta:
9dfa4296 849 fields = ('type_contrat', 'fichier', )
9536ea21
EMS
850 model = dae.Contrat
851
5a1f75cb 852
c3f0b49f
EMS
853class DAENumeriseeForm(forms.ModelForm):
854
855 class Meta:
856 model = dae.Dossier
857 fields = ('dae_numerisee',)
cbfd7bd4
EMS
858
859
860class DAEFinaliseesSearchForm(forms.Form):
861 q = forms.CharField(
862 label='Recherche', required=False,
863 widget=forms.TextInput(attrs={'size': 40})
864 )
865 importees = forms.ChoiceField(
866 label='Importation', required=False, choices=(
867 ('', ''),
868 ('oui', 'DAE importées seulement'),
869 ('non', 'DAE non-importées seulement'),
870 )
871 )