fix prepopulation DAE from RH
[auf_rh_dae.git] / project / dae / forms.py
CommitLineData
5d680e84 1# -*- encoding: utf-8 -*-
ce110fb9 2
aa512122 3import datetime
5a1f75cb 4
5a1f75cb 5from django import forms
2e672700 6from django.forms.models import BaseInlineFormSet
5a1f75cb
EMS
7from django.contrib.admin import widgets as admin_widgets
8from django.db.models import Q, Max
9from django.forms.models import inlineformset_factory, modelformset_factory
10
75f0e87b
DB
11from ajax_select.fields import AutoCompleteSelectField
12
13from auf.django.references import models as ref
14from auf.django.workflow.forms import WorkflowFormMixin
15
17c90428 16from project.rh import models as rh
afd3be54 17from project.groups import \
5a1f75cb 18 get_employe_from_user, is_user_dans_services_centraux
17c90428
DB
19
20from project.dae import models as dae
5a1f75cb 21from project.dae.workflow import \
1b31de9f
EMS
22 grp_drh, POSTE_ETATS_BOUTONS, POSTE_ETAT_FINALISE
23
f258e4e7 24
2e672700
OL
25class BaseInlineFormSetWithInitial(BaseInlineFormSet):
26 """
27 Cette classe permet de fournir l'option initial aux inlineformsets.
28 Elle devient désuette en django 1.4.
29 """
30 def __init__(self, data=None, files=None, instance=None,
31 save_as_new=False, prefix=None, queryset=None, **kwargs):
32
33 self.initial_extra = kwargs.pop('initial', None)
34
35 from django.db.models.fields.related import RelatedObject
36 if instance is None:
37 self.instance = self.fk.rel.to()
38 else:
39 self.instance = instance
40 self.save_as_new = save_as_new
41 # is there a better way to get the object descriptor?
42 self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()
43 if queryset is None:
44 queryset = self.model._default_manager
45 qs = queryset.filter(**{self.fk.name: self.instance})
46 super(BaseInlineFormSetWithInitial, self).__init__(data, files, prefix=prefix,
47 queryset=qs, **kwargs)
48
49 def _construct_form(self, i, **kwargs):
50 if self.is_bound and i < self.initial_form_count():
51 # Import goes here instead of module-level because importing
52 # django.db has side effects.
53 from django.db import connections
54 pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name)
55 pk = self.data[pk_key]
56 pk_field = self.model._meta.pk
57 pk = pk_field.get_db_prep_lookup('exact', pk,
58 connection=connections[self.get_queryset().db])
59 if isinstance(pk, list):
60 pk = pk[0]
61 kwargs['instance'] = self._existing_object(pk)
62 if i < self.initial_form_count() and not kwargs.get('instance'):
63 kwargs['instance'] = self.get_queryset()[i]
64 if i >= self.initial_form_count() and self.initial_extra:
65 # Set initial values for extra forms
66 try:
67 kwargs['initial'] = self.initial_extra[i-self.initial_form_count()]
68 except IndexError:
69 pass
70
71 defaults = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)}
72 if self.is_bound:
73 defaults['data'] = self.data
74 defaults['files'] = self.files
75 if self.initial:
76 try:
77 defaults['initial'] = self.initial[i]
78 except IndexError:
79 pass
80 # Allow extra forms to be empty.
81 if i >= self.initial_form_count():
82 defaults['empty_permitted'] = True
83 defaults.update(kwargs)
84 form = self.form(**defaults)
85 self.add_fields(form, i)
86 return form
87
88
f258e4e7
OL
89def _implantation_choices(obj, request):
90 # TRAITEMENT NORMAL
91 employe = get_employe_from_user(request.user)
92 # SERVICE
d8cfc3d5 93 if is_user_dans_services_centraux(request.user):
5a1f75cb 94 q = Q(**{'id': employe.implantation_id})
f258e4e7
OL
95 # REGION
96 else:
5a1f75cb 97 q = Q(**{'region': employe.implantation.region})
f258e4e7
OL
98
99 # TRAITEMENT DRH
100 if grp_drh in request.user.groups.all():
101 q = Q()
5a1f75cb
EMS
102 return [('', '----------')] + \
103 [(i.id, unicode(i), )for i in ref.Implantation.objects.filter(q)]
104
f258e4e7
OL
105
106def _employe_choices(obj, request):
f258e4e7
OL
107 # TRAITEMENT NORMAL
108 employe = get_employe_from_user(request.user)
109 # SERVICE
d8cfc3d5 110 if is_user_dans_services_centraux(request.user):
072820fc 111 q_dae_region_service = Q(poste__implantation=employe.implantation)
09aa8374 112 q_rh_region_service = Q(poste__implantation=employe.implantation)
f258e4e7
OL
113 # REGION
114 else:
5a1f75cb
EMS
115 q_dae_region_service = Q(
116 poste__implantation__region=employe.implantation.region
117 )
118 q_rh_region_service = Q(
119 poste__implantation__region=employe.implantation.region
120 )
f258e4e7
OL
121 # TRAITEMENT DRH
122 if grp_drh in request.user.groups.all():
072820fc
OL
123 q_dae_region_service = Q()
124 q_rh_region_service = Q()
f258e4e7 125
5a1f75cb
EMS
126 # On filtre les employes avec les droits régionaux et on s'assure que
127 # c'est bien le dernier dossier en date pour sortir l'employe. On retient
128 # un employé qui travaille présentement dans la même région que le user
129 # connecté.
130 dossiers_regionaux_ids = [
131 d.id for d in dae.Dossier.objects.filter(q_dae_region_service)
132 ]
133 employes_ids = [
134 d['employe']
135 for d in dae.Dossier.objects
136 .values('employe')
137 .annotate(dernier_dossier=Max('id'))
138 if d['dernier_dossier'] in dossiers_regionaux_ids
139 ]
072820fc
OL
140 dae_employe = dae.Employe.objects.filter(id__in=employes_ids)
141 dae_ = dae_employe.filter(id_rh__isnull=True)
142 copies = dae_employe.filter(Q(id_rh__isnull=False))
f258e4e7 143 id_copies = [p.id_rh_id for p in copies.all()]
072820fc 144
5a1f75cb
EMS
145 dossiers_regionaux_ids = [
146 d.id for d in rh.Dossier.objects.filter(q_rh_region_service)
147 ]
148 employes_ids = [
149 d['employe']
150 for d in rh.Dossier.objects
151 .values('employe')
152 .annotate(dernier_dossier=Max('id'))
153 if d['dernier_dossier'] in dossiers_regionaux_ids
154 ]
155 rhv1 = rh.Employe.objects \
156 .filter(id__in=employes_ids) \
157 .exclude(id__in=id_copies)
158
159 # On ajoute les nouveaux Employés DAE qui ont été crées, mais qui n'ont
160 # pas de Dossier associés
67c15007
OL
161 employes_avec_dae = [d.employe_id for d in dae.Dossier.objects.all()]
162 employes_orphelins = dae.Employe.objects.exclude(id__in=employes_avec_dae)
163
f258e4e7
OL
164 def option_label(employe):
165 return "%s %s" % (employe.nom.upper(), employe.prenom.title())
166
167 return [('', 'Nouvel employé')] + \
5a1f75cb
EMS
168 sorted(
169 [('dae-%s' % p.id, option_label(p))
170 for p in dae_ | copies | employes_orphelins] +
171 [('rh-%s' % p.id, option_label(p)) for p in rhv1],
172 key=lambda t: t[1]
173 )
174
f258e4e7 175
4bce4d24
OL
176def label_poste_display(poste):
177 """Formate un visuel pour un poste dans une liste déroulante"""
23294f7d
OL
178 annee = ""
179 if poste.date_debut:
180 annee = poste.date_debut.year
5a1f75cb 181 label = u"%s %s - %s [%s]" % (
7bf28694 182 annee, poste.type_poste, poste.type_poste.categorie_emploi.nom,
5a1f75cb
EMS
183 poste.id
184 )
4bce4d24 185 return label
9cb4de55 186
2e672700
OL
187PostePieceForm = inlineformset_factory(
188 dae.Poste,
189 dae.PostePiece,
190 #formset=BaseInlineFormSetWithInitial,
191 )
192
193
25086dcf 194DossierPieceForm = inlineformset_factory(dae.Dossier, dae.DossierPiece)
2e672700
OL
195
196
5a1f75cb 197FinancementForm = inlineformset_factory(
2e672700
OL
198 dae.Poste,
199 dae.PosteFinancement,
200 formset=BaseInlineFormSetWithInitial,
201 extra=2
5a1f75cb
EMS
202)
203
03b395db
OL
204
205class DossierComparaisonForm(forms.ModelForm):
11f22317 206
03b395db 207 recherche = AutoCompleteSelectField('dossiers', required=False)
5a1f75cb
EMS
208 poste = forms.CharField(
209 max_length=255, widget=forms.TextInput(attrs={'size': '60'})
210 )
03b395db 211
320d7584 212 class Meta:
03b395db 213 model = dae.DossierComparaison
320d7584 214 exclude = ('dossier',)
03b395db 215
320d7584
EMS
216DossierComparaisonFormSet = modelformset_factory(
217 dae.DossierComparaison, extra=3, max_num=3, form=DossierComparaisonForm
25086dcf 218)
03b395db 219
5a1f75cb 220
068d1462 221class PosteComparaisonForm(forms.ModelForm):
11f22317 222
e503e64d 223 recherche = AutoCompleteSelectField('dae_postes', required=False)
068d1462 224
320d7584 225 class Meta:
068d1462 226 model = dae.PosteComparaison
320d7584 227 exclude = ('poste',)
068d1462 228
2e672700
OL
229PosteComparaisonFormSet = inlineformset_factory(
230 dae.Poste,
231 dae.PosteComparaison,
232 extra=3,
233 max_num=3,
234 form=PosteComparaisonForm,
235 #formset=BaseModelFormSetWithInitial,
236 formset=BaseInlineFormSetWithInitial,
25086dcf 237)
068d1462 238
5a1f75cb 239
0a085c42
OL
240class FlexibleRemunForm(forms.ModelForm):
241
242 montant_mensuel = forms.DecimalField(required=False)
243 montant = forms.DecimalField(required=True, label='Montant annuel')
244
245 class Meta:
246 model = dae.Remuneration
247
dc4b78a7
OL
248 def clean_devise(self):
249 devise = self.cleaned_data['devise']
67173010
OL
250 if devise.code == 'EUR':
251 return devise
5a1f75cb
EMS
252 implantation = ref.Implantation.objects.get(
253 id=self.data['implantation']
254 )
2455f48d 255 liste_taux = devise.tauxchange_set.order_by('-annee')
dc4b78a7 256 if len(liste_taux) == 0:
5a1f75cb
EMS
257 raise forms.ValidationError(
258 u"La devise %s n'a pas de taux pour l'implantation %s" %
259 (devise, implantation)
260 )
dc4b78a7
OL
261 else:
262 return devise
263
25086dcf
EMS
264RemunForm = inlineformset_factory(
265 dae.Dossier, dae.Remuneration, extra=5, form=FlexibleRemunForm
266)
0a085c42 267
5a1f75cb 268
1b217058 269class PosteForm(forms.ModelForm):
5d680e84 270 """ Formulaire des postes. """
12c7f8a7 271
ea7adc69 272 # On ne propose que les services actifs
5a1f75cb
EMS
273 service = forms.ModelChoiceField(
274 queryset=rh.Service.objects.all(), required=True
275 )
ea7adc69 276
5a1f75cb 277 responsable = AutoCompleteSelectField('responsables', required=True)
12c7f8a7
OL
278 #responsable = forms.ModelChoiceField(
279 # queryset=rh.Poste.objects.select_related(depth=1))
280
281 # La liste des choix est laissée vide. Voir __init__ pour la raison.
282 poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste",
283 choices=(), required=False)
11f22317 284
5a1f75cb
EMS
285 valeur_point_min = forms.ModelChoiceField(
286 queryset=rh.ValeurPoint.actuelles.all(), required=False
287 )
288 valeur_point_max = forms.ModelChoiceField(
289 queryset=rh.ValeurPoint.actuelles.all(), required=False
290 )
11f22317 291
5d680e84
NC
292 class Meta:
293 model = dae.Poste
c3be904d
OL
294 fields = ('type_intervention',
295 'poste', 'implantation', 'type_poste', 'service', 'nom',
154677c3 296 'responsable', 'local', 'expatrie', 'mise_a_disposition',
b15bf543 297 'appel', 'date_debut', 'date_fin',
5d680e84
NC
298 'regime_travail', 'regime_travail_nb_heure_semaine',
299 'classement_min', 'classement_max',
300 'valeur_point_min', 'valeur_point_max',
3d627bfd 301 'devise_min', 'devise_max',
5f61bccb
OL
302 'salaire_min', 'salaire_max',
303 'indemn_expat_min', 'indemn_expat_max',
304 'indemn_fct_min', 'indemn_fct_max',
305 'charges_patronales_min', 'charges_patronales_max',
5d680e84
NC
306 'autre_min', 'autre_max', 'devise_comparaison',
307 'comp_locale_min', 'comp_locale_max',
308 'comp_universite_min', 'comp_universite_max',
309 'comp_fonctionpub_min', 'comp_fonctionpub_max',
310 'comp_ong_min', 'comp_ong_max',
8fa94e8b 311 'comp_autre_min', 'comp_autre_max',
2e092e0c 312 'justification',
8fa94e8b 313 )
c3be904d
OL
314 widgets = dict(type_intervention=forms.RadioSelect(),
315 appel=forms.RadioSelect(),
3d627bfd 316 nom=forms.TextInput(attrs={'size': 60},),
e88caaf0
OL
317 date_debut=admin_widgets.AdminDateWidget(),
318 date_fin=admin_widgets.AdminDateWidget(),
2e092e0c 319 justification=forms.Textarea(attrs={'cols': 80},),
3d627bfd 320 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
321 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
322 )
5d680e84 323
c2458db6 324 def __init__(self, *args, **kwargs):
5d680e84
NC
325 """ Mise à jour dynamique du contenu du menu des postes.
326
327 Si on ne met le menu à jour de cette façon, à chaque instantiation du
328 formulaire, son contenu est mis en cache par le système et il ne
329 reflète pas les changements apportés par les ajouts, modifications,
330 etc...
331
139686f2
NC
332 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
333 car le "id" de chaque choix est spécial (voir _poste_choices).
334
5d680e84 335 """
c2458db6 336 request = kwargs.pop('request')
5d680e84 337 super(PosteForm, self).__init__(*args, **kwargs)
f258e4e7 338 self.fields['poste'].choices = self._poste_choices(request)
5a1f75cb
EMS
339 self.fields['implantation'].choices = \
340 _implantation_choices(self, request)
5d680e84 341
cc3098d0
OL
342 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
343 if self.instance and self.instance.id is None:
344 dossiers = self.instance.get_dossiers()
345 if len(dossiers) > 0:
09aa8374 346 self.initial['service'] = dossiers[0].poste.service
9508a5b8 347
f258e4e7 348 def _poste_choices(self, request):
5d680e84
NC
349 """ Menu déroulant pour les postes.
350
351 Constitué des postes de dae et des postes de rh_v1 qui n'ont pas
352 d'équivalent dans dae.
353
354 """
5a1f75cb
EMS
355 copies = dae.Poste.objects \
356 .ma_region_ou_service(request.user) \
357 .exclude(id_rh__isnull=True) \
358 .filter(etat=POSTE_ETAT_FINALISE)
5d680e84 359 id_copies = [p.id_rh_id for p in copies.all()]
5a1f75cb
EMS
360 rhv1 = rh.Poste.objects.ma_region_ou_service(request.user) \
361 .exclude(id__in=id_copies)
139686f2
NC
362 # Optimisation de la requête
363 rhv1 = rhv1.select_related(depth=1)
5d680e84 364
98d51b59 365 return [('', 'Nouveau poste')] + \
aa512122 366 sorted([('rh-%s' % p.id, label_poste_display(p)) for p in rhv1],
5d680e84 367 key=lambda t: t[1])
3ed49093 368
4dd75e7b
OL
369 def clean(self):
370 """
371 Validation conditionnelles de certains champs.
372 """
5a1f75cb 373 cleaned_data = self.cleaned_data
4dd75e7b 374
5a1f75cb
EMS
375 if cleaned_data.get("local") is False \
376 and cleaned_data.get("expatrie") is False:
377 msg = "Le poste doit au moins être ouvert localement " \
378 "ou aux expatriés"
f42c6e20
OL
379 self._errors["local"] = self.error_class([msg])
380 self._errors["expatrie"] = ''
381 raise forms.ValidationError(msg)
f42c6e20 382
4dd75e7b
OL
383 return cleaned_data
384
494ff2be
NC
385 def save(self, *args, **kwargs):
386 kwargs2 = kwargs.copy()
387 kwargs2['commit'] = False
388 poste = super(PosteForm, self).save(*args, **kwargs2)
389 # id_rh
390 if 'commit' not in kwargs or kwargs['commit']:
aa512122
OL
391 if poste.id is None:
392 poste.date_creation = datetime.datetime.now()
494ff2be
NC
393 poste.save()
394 return poste
395
3ed49093 396
139686f2
NC
397class ChoosePosteForm(forms.ModelForm):
398 class Meta:
399 model = dae.Poste
400 fields = ('poste',)
401
402 # La liste des choix est laissée vide. Voir PosteForm.__init__.
403 poste = forms.ChoiceField(choices=(), required=False)
404
4ee6d70a 405 def __init__(self, request=None, *args, **kwargs):
139686f2 406 super(ChoosePosteForm, self).__init__(*args, **kwargs)
4ee6d70a 407 self.fields['poste'].choices = self._poste_choices(request)
139686f2 408
4ee6d70a 409 def _poste_choices(self, request):
139686f2 410 """ Menu déroulant pour les postes. """
5a1f75cb
EMS
411 dae_ = dae.Poste.objects.ma_region_ou_service(request.user) \
412 .filter(id_rh__isnull=True)
413 copies = dae.Poste.objects.ma_region_ou_service(request.user) \
414 .exclude(id_rh__isnull=True)
139686f2 415
98d51b59 416 return [('', '----------')] + \
139686f2
NC
417 sorted([('dae-%s' % p.id, unicode(p)) for p in dae_ | copies],
418 key=lambda t: t[1])
419
420
139686f2
NC
421class EmployeForm(forms.ModelForm):
422 """ Formulaire des employés. """
423 class Meta:
424 model = dae.Employe
425 fields = ('employe', 'nom', 'prenom', 'genre')
426
427 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
428 employe = forms.ChoiceField(choices=(), required=False)
429
ac6235f6 430 def __init__(self, *args, **kwargs):
139686f2 431 """ Mise à jour dynamique du contenu du menu des employés. """
ac6235f6 432 request = kwargs.pop('request', None)
139686f2 433 super(EmployeForm, self).__init__(*args, **kwargs)
f258e4e7 434 self.fields['employe'].choices = _employe_choices(self, request)
139686f2 435
139686f2 436
139686f2
NC
437class DossierForm(forms.ModelForm):
438 """ Formulaire des dossiers. """
439 class Meta:
5a1f75cb 440 exclude = ('etat', 'employe', 'poste', 'date_debut',)
139686f2 441 model = dae.Dossier
4d25e2ba 442 widgets = dict(statut_residence=forms.RadioSelect(),
0e0aeb7e
OL
443 contrat_date_debut=admin_widgets.AdminDateWidget(),
444 contrat_date_fin=admin_widgets.AdminDateWidget(),
4d25e2ba 445 )
e6f52402 446
aa512122 447 def save(self, *args, **kwargs):
38112342 448 dossier = super(DossierForm, self).save(*args, **kwargs)
aa512122
OL
449 if dossier.id is None:
450 dossier.date_creation = datetime.datetime.now()
451 dossier.save()
452 return dossier
453
3799cafc 454WF_HELP_TEXT = ""
e0b93e3a 455
5a1f75cb 456
e6f52402 457class PosteWorkflowForm(WorkflowFormMixin):
56589624 458 bouton_libelles = POSTE_ETATS_BOUTONS
5a1f75cb 459
e6f52402
OL
460 class Meta:
461 fields = ('etat', )
462 model = dae.Poste
9536ea21 463
e0b93e3a 464 def __init__(self, *args, **kwargs):
e54b7d5d 465 super(PosteWorkflowForm, self).__init__(*args, **kwargs)
e0b93e3a
OL
466 self.fields['etat'].help_text = WF_HELP_TEXT
467
468
e6f52402 469class DossierWorkflowForm(WorkflowFormMixin):
5a1f75cb
EMS
470 bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste...
471
e6f52402 472 class Meta:
9e40cfbe 473 fields = ('etat', )
e6f52402 474 model = dae.Dossier
e0b93e3a
OL
475
476 def __init__(self, *args, **kwargs):
e54b7d5d 477 super(DossierWorkflowForm, self).__init__(*args, **kwargs)
e0b93e3a 478 self.fields['etat'].help_text = WF_HELP_TEXT
e54b7d5d 479 self._etat_initial = self.instance.etat
e0b93e3a 480
e54b7d5d
EMS
481 def save(self):
482 super(DossierWorkflowForm, self).save()
483 poste = self.instance.poste
484 if poste.etat == self._etat_initial:
485 poste.etat = self.instance.etat
486 poste.save()
9536ea21 487
5a1f75cb 488
9536ea21
EMS
489class ContratForm(forms.ModelForm):
490
491 class Meta:
9dfa4296 492 fields = ('type_contrat', 'fichier', )
9536ea21
EMS
493 model = dae.Contrat
494
5a1f75cb 495
c3f0b49f
EMS
496class DAENumeriseeForm(forms.ModelForm):
497
498 class Meta:
499 model = dae.Dossier
500 fields = ('dae_numerisee',)
cbfd7bd4
EMS
501
502
503class DAEFinaliseesSearchForm(forms.Form):
504 q = forms.CharField(
505 label='Recherche', required=False,
506 widget=forms.TextInput(attrs={'size': 40})
507 )
508 importees = forms.ChoiceField(
509 label='Importation', required=False, choices=(
510 ('', ''),
511 ('oui', 'DAE importées seulement'),
512 ('non', 'DAE non-importées seulement'),
513 )
514 )