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