1 # -*- encoding: utf-8 -*-
5 from django
import forms
6 from django
.forms
.models
import BaseInlineFormSet
7 from django
.contrib
.admin
import widgets
as admin_widgets
8 from django
.db
.models
import Q
, Max
9 from django
.forms
.models
import inlineformset_factory
, modelformset_factory
11 from ajax_select
.fields
import AutoCompleteSelectField
13 from auf
.django
.references
import models
as ref
14 from auf
.django
.workflow
.forms
import WorkflowFormMixin
16 from project
.rh
import models
as rh
17 from project
.groups
import \
18 get_employe_from_user
, is_user_dans_services_centraux
20 from project
.dae
import models
as dae
21 from project
.dae
.workflow
import grp_drh
, POSTE_ETATS_BOUTONS
24 class BaseInlineFormSetWithInitial(BaseInlineFormSet
):
26 Cette classe permet de fournir l'option initial aux inlineformsets.
27 Elle devient désuette en django 1.4.
29 def __init__(self
, data
=None, files
=None, instance
=None,
30 save_as_new
=False, prefix
=None, queryset
=None, **kwargs
):
32 self
.initial_extra
= kwargs
.pop('initial', None)
34 from django
.db
.models
.fields
.related
import RelatedObject
36 self
.instance
= self
.fk
.rel
.to()
38 self
.instance
= instance
39 self
.save_as_new
= save_as_new
40 # is there a better way to get the object descriptor?
41 self
.rel_name
= RelatedObject(self
.fk
.rel
.to
, self
.model
, self
.fk
).get_accessor_name()
43 queryset
= self
.model
._default_manager
44 qs
= queryset
.filter(**{self
.fk
.name
: self
.instance
})
45 super(BaseInlineFormSetWithInitial
, self
).__init__(data
, files
, prefix
=prefix
,
46 queryset
=qs
, **kwargs
)
48 def _construct_form(self
, i
, **kwargs
):
49 if self
.is_bound
and i
< self
.initial_form_count():
50 # Import goes here instead of module-level because importing
51 # django.db has side effects.
52 from django
.db
import connections
53 pk_key
= "%s-%s" % (self
.add_prefix(i
), self
.model
._meta
.pk
.name
)
54 pk
= self
.data
[pk_key
]
55 pk_field
= self
.model
._meta
.pk
56 pk
= pk_field
.get_db_prep_lookup('exact', pk
,
57 connection
=connections
[self
.get_queryset().db
])
58 if isinstance(pk
, list):
60 kwargs
['instance'] = self
._existing_object(pk
)
61 if i
< self
.initial_form_count() and not kwargs
.get('instance'):
62 kwargs
['instance'] = self
.get_queryset()[i
]
63 if i
>= self
.initial_form_count() and self
.initial_extra
:
64 # Set initial values for extra forms
66 kwargs
['initial'] = self
.initial_extra
[i
-self
.initial_form_count()]
70 defaults
= {'auto_id': self
.auto_id
, 'prefix': self
.add_prefix(i
)}
72 defaults
['data'] = self
.data
73 defaults
['files'] = self
.files
76 defaults
['initial'] = self
.initial
[i
]
79 # Allow extra forms to be empty.
80 if i
>= self
.initial_form_count():
81 defaults
['empty_permitted'] = True
82 defaults
.update(kwargs
)
83 form
= self
.form(**defaults
)
84 self
.add_fields(form
, i
)
88 def _implantation_choices(obj
, request
):
90 employe
= get_employe_from_user(request
.user
)
92 if is_user_dans_services_centraux(request
.user
):
93 q
= Q(**{'id': employe
.implantation_id
})
96 q
= Q(**{'region': employe
.implantation
.region
})
99 if grp_drh
in request
.user
.groups
.all():
101 return [('', '----------')] + \
102 [(i
.id, unicode(i
), )for i
in ref
.Implantation
.objects
.filter(q
)]
105 def _employe_choices(obj
, request
):
107 employe
= get_employe_from_user(request
.user
)
109 if is_user_dans_services_centraux(request
.user
):
110 q_dae_region_service
= Q(poste__implantation
=employe
.implantation
)
111 q_rh_region_service
= Q(poste__implantation
=employe
.implantation
)
114 q_dae_region_service
= Q(
115 poste__implantation__region
=employe
.implantation
.region
117 q_rh_region_service
= Q(
118 poste__implantation__region
=employe
.implantation
.region
121 if grp_drh
in request
.user
.groups
.all():
122 q_dae_region_service
= Q()
123 q_rh_region_service
= Q()
125 # On filtre les employes avec les droits régionaux et on s'assure que
126 # c'est bien le dernier dossier en date pour sortir l'employe. On retient
127 # un employé qui travaille présentement dans la même région que le user
129 dossiers_regionaux_ids
= [
130 d
.id for d
in dae
.Dossier
.objects
.filter(q_dae_region_service
)
134 for d
in dae
.Dossier
.objects
136 .annotate(dernier_dossier
=Max('id'))
137 if d
['dernier_dossier'] in dossiers_regionaux_ids
139 dae_employe
= dae
.Employe
.objects
.filter(id__in
=employes_ids
)
140 dae_
= dae_employe
.filter(id_rh__isnull
=True)
141 copies
= dae_employe
.filter(Q(id_rh__isnull
=False))
142 id_copies
= [p
.id_rh_id
for p
in copies
.all()]
144 dossiers_regionaux_ids
= [
145 d
.id for d
in rh
.Dossier
.objects
.filter(q_rh_region_service
)
149 for d
in rh
.Dossier
.objects
151 .annotate(dernier_dossier
=Max('id'))
152 if d
['dernier_dossier'] in dossiers_regionaux_ids
154 rhv1
= rh
.Employe
.objects \
155 .filter(id__in
=employes_ids
) \
156 .exclude(id__in
=id_copies
)
158 # On ajoute les nouveaux Employés DAE qui ont été crées, mais qui n'ont
159 # pas de Dossier associés
160 employes_avec_dae
= [d
.employe_id
for d
in dae
.Dossier
.objects
.all()]
161 employes_orphelins
= dae
.Employe
.objects
.exclude(id__in
=employes_avec_dae
)
163 def option_label(employe
):
164 return "%s %s" % (employe
.nom
.upper(), employe
.prenom
.title())
166 return [('', 'Nouvel employé')] + \
168 [('dae-%s' % p
.id, option_label(p
))
169 for p
in dae_ | copies | employes_orphelins
] +
170 [('rh-%s' % p
.id, option_label(p
)) for p
in rhv1
],
175 def label_poste_display(poste
):
176 """Formate un visuel pour un poste dans une liste déroulante"""
179 annee
= poste
.date_debut
.year
183 label
= u
"%s %s - %s [%s]" % (
184 annee
, nom
, poste
.type_poste
.categorie_emploi
.nom
, poste
.id
189 PostePieceFormSet
= inlineformset_factory(dae
.Poste
, dae
.PostePiece
,)
190 DossierPieceForm
= inlineformset_factory(dae
.Dossier
, dae
.DossierPiece
)
192 # Ce formset est utilisé dans le cas de la création de poste prépopulé avec les
194 FinancementFormSetInitial
= inlineformset_factory(
196 dae
.PosteFinancement
,
197 formset
=BaseInlineFormSetWithInitial
,
200 FinancementFormSet
= inlineformset_factory(
202 dae
.PosteFinancement
,
207 class DossierComparaisonForm(forms
.ModelForm
):
209 recherche
= AutoCompleteSelectField('dossiers', required
=False)
210 poste
= forms
.CharField(
211 max_length
=255, widget
=forms
.TextInput(attrs
={'size': '60'})
215 model
= dae
.DossierComparaison
216 exclude
= ('dossier',)
218 DossierComparaisonFormSet
= modelformset_factory(
219 dae
.DossierComparaison
, extra
=3, max_num
=3, form
=DossierComparaisonForm
223 class PosteComparaisonForm(forms
.ModelForm
):
225 recherche
= AutoCompleteSelectField('dae_postes', required
=False)
228 model
= dae
.PosteComparaison
231 # Ce formset est utilisé dans le cas de la création de poste prépopulé avec les
233 PosteComparaisonFormSetInitial
= inlineformset_factory(
235 dae
.PosteComparaison
,
238 form
=PosteComparaisonForm
,
239 formset
=BaseInlineFormSetWithInitial
,
241 PosteComparaisonFormSet
= inlineformset_factory(
243 dae
.PosteComparaison
,
246 form
=PosteComparaisonForm
,
250 class FlexibleRemunForm(forms
.ModelForm
):
252 montant_mensuel
= forms
.DecimalField(required
=False)
253 montant
= forms
.DecimalField(required
=True, label
='Montant annuel')
256 model
= dae
.Remuneration
258 def clean_devise(self
):
259 devise
= self
.cleaned_data
['devise']
260 if devise
.code
== 'EUR':
262 implantation
= ref
.Implantation
.objects
.get(
263 id=self
.data
['implantation']
265 liste_taux
= devise
.tauxchange_set
.order_by('-annee')
266 if len(liste_taux
) == 0:
267 raise forms
.ValidationError(
268 u
"La devise %s n'a pas de taux pour l'implantation %s" %
269 (devise
, implantation
)
274 RemunForm
= inlineformset_factory(
275 dae
.Dossier
, dae
.Remuneration
, extra
=5, form
=FlexibleRemunForm
279 class PosteForm(forms
.ModelForm
):
280 """ Formulaire des postes. """
282 # On ne propose que les services actifs
283 service
= forms
.ModelChoiceField(
284 queryset
=rh
.Service
.objects
.all(), required
=True
287 responsable
= AutoCompleteSelectField('responsables', required
=True)
288 #responsable = forms.ModelChoiceField(
289 # queryset=rh.Poste.objects.select_related(depth=1))
291 # La liste des choix est laissée vide. Voir __init__ pour la raison.
292 poste
= forms
.ChoiceField(label
="Nouveau poste ou évolution du poste",
293 choices
=(), required
=False)
295 valeur_point_min
= forms
.ModelChoiceField(
296 queryset
=rh
.ValeurPoint
.actuelles
.all(), required
=False
298 valeur_point_max
= forms
.ModelChoiceField(
299 queryset
=rh
.ValeurPoint
.actuelles
.all(), required
=False
304 fields
= ('type_intervention',
305 'poste', 'implantation', 'type_poste', 'service', 'nom',
306 'responsable', 'local', 'expatrie', 'mise_a_disposition',
307 'appel', 'date_debut', 'date_fin',
308 'regime_travail', 'regime_travail_nb_heure_semaine',
309 'classement_min', 'classement_max',
310 'valeur_point_min', 'valeur_point_max',
311 'devise_min', 'devise_max',
312 'salaire_min', 'salaire_max',
313 'indemn_expat_min', 'indemn_expat_max',
314 'indemn_fct_min', 'indemn_fct_max',
315 'charges_patronales_min', 'charges_patronales_max',
316 'autre_min', 'autre_max', 'devise_comparaison',
317 'comp_locale_min', 'comp_locale_max',
318 'comp_universite_min', 'comp_universite_max',
319 'comp_fonctionpub_min', 'comp_fonctionpub_max',
320 'comp_ong_min', 'comp_ong_max',
321 'comp_autre_min', 'comp_autre_max',
324 widgets
= dict(type_intervention
=forms
.RadioSelect(),
325 appel
=forms
.RadioSelect(),
326 nom
=forms
.TextInput(attrs
={'size': 60},),
327 date_debut
=admin_widgets
.AdminDateWidget(),
328 date_fin
=admin_widgets
.AdminDateWidget(),
329 justification
=forms
.Textarea(attrs
={'cols': 80},),
330 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
331 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
334 def __init__(self
, *args
, **kwargs
):
335 """ Mise à jour dynamique du contenu du menu des postes.
337 Si on ne met le menu à jour de cette façon, à chaque instantiation du
338 formulaire, son contenu est mis en cache par le système et il ne
339 reflète pas les changements apportés par les ajouts, modifications,
342 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
343 car le "id" de chaque choix est spécial (voir _poste_choices).
346 request
= kwargs
.pop('request')
347 super(PosteForm
, self
).__init__(*args
, **kwargs
)
348 self
.fields
['poste'].choices
= self
._poste_choices(request
)
350 self
.fields
['implantation'].choices
= \
351 _implantation_choices(self
, request
)
353 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
354 if self
.instance
and self
.instance
.id is None:
355 dossiers
= self
.instance
.get_dossiers()
356 if len(dossiers
) > 0:
357 self
.initial
['service'] = dossiers
[0].poste
.service
359 def _poste_choices(self
, request
):
360 """ Menu déroulant pour les postes.
361 Constitué des postes de RH
363 postes_rh
= rh
.Poste
.objects
.ma_region_ou_service(request
.user
).all()
364 postes_rh
= postes_rh
.select_related(depth
=1)
366 return [('', 'Nouveau poste')] + \
367 sorted([('rh-%s' % p
.id, label_poste_display(p
)) for p
in
373 Validation conditionnelles de certains champs.
375 cleaned_data
= self
.cleaned_data
377 if cleaned_data
.get("local") is False \
378 and cleaned_data
.get("expatrie") is False:
379 msg
= "Le poste doit au moins être ouvert localement " \
381 self
._errors
["local"] = self
.error_class([msg
])
382 self
._errors
["expatrie"] = ''
383 raise forms
.ValidationError(msg
)
388 class ChoosePosteForm(forms
.ModelForm
):
393 # La liste des choix est laissée vide. Voir PosteForm.__init__.
394 poste
= forms
.ChoiceField(choices
=(), required
=False)
396 def __init__(self
, request
=None, *args
, **kwargs
):
397 super(ChoosePosteForm
, self
).__init__(*args
, **kwargs
)
398 self
.fields
['poste'].choices
= self
._poste_choices(request
)
400 def _poste_choices(self
, request
):
401 """ Menu déroulant pour les postes. """
402 dae_
= dae
.Poste
.objects
.ma_region_ou_service(request
.user
) \
403 .filter(id_rh__isnull
=True)
404 copies
= dae
.Poste
.objects
.ma_region_ou_service(request
.user
) \
405 .exclude(id_rh__isnull
=True)
407 return [('', '----------')] + \
408 sorted([('dae-%s' % p
.id, unicode(p
)) for p
in dae_ | copies
],
412 class EmployeForm(forms
.ModelForm
):
413 """ Formulaire des employés. """
416 fields
= ('employe', 'nom', 'prenom', 'genre')
418 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
419 employe
= forms
.ChoiceField(choices
=(), required
=False)
421 def __init__(self
, *args
, **kwargs
):
422 """ Mise à jour dynamique du contenu du menu des employés. """
423 request
= kwargs
.pop('request', None)
424 super(EmployeForm
, self
).__init__(*args
, **kwargs
)
425 self
.fields
['employe'].choices
= _employe_choices(self
, request
)
428 class DossierForm(forms
.ModelForm
):
429 """ Formulaire des dossiers. """
431 exclude
= ('etat', 'employe', 'poste', 'date_debut',)
433 widgets
= dict(statut_residence
=forms
.RadioSelect(),
434 contrat_date_debut
=admin_widgets
.AdminDateWidget(),
435 contrat_date_fin
=admin_widgets
.AdminDateWidget(),
441 class PosteWorkflowForm(WorkflowFormMixin
):
442 bouton_libelles
= POSTE_ETATS_BOUTONS
448 def __init__(self
, *args
, **kwargs
):
449 super(PosteWorkflowForm
, self
).__init__(*args
, **kwargs
)
450 self
.fields
['etat'].help_text
= WF_HELP_TEXT
453 class DossierWorkflowForm(WorkflowFormMixin
):
454 bouton_libelles
= POSTE_ETATS_BOUTONS
# meme workflow que poste...
460 def __init__(self
, *args
, **kwargs
):
461 super(DossierWorkflowForm
, self
).__init__(*args
, **kwargs
)
462 self
.fields
['etat'].help_text
= WF_HELP_TEXT
463 self
._etat_initial
= self
.instance
.etat
466 super(DossierWorkflowForm
, self
).save()
467 poste
= self
.instance
.poste
468 if poste
.etat
== self
._etat_initial
:
469 poste
.etat
= self
.instance
.etat
473 class ContratForm(forms
.ModelForm
):
476 fields
= ('type_contrat', 'fichier', )
480 class DAENumeriseeForm(forms
.ModelForm
):
484 fields
= ('dae_numerisee',)
487 class DAEFinaliseesSearchForm(forms
.Form
):
489 label
='Recherche', required
=False,
490 widget
=forms
.TextInput(attrs
={'size': 40})
492 importees
= forms
.ChoiceField(
493 label
='Importation', required
=False, choices
=(
495 ('oui', 'DAE importées seulement'),
496 ('non', 'DAE non-importées seulement'),