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