1 # -*- encoding: utf-8 -*-
3 from django
import forms
4 from django
.forms
.models
import BaseInlineFormSet
5 from django
.contrib
.admin
import widgets
as admin_widgets
6 from django
.db
.models
import Q
, Max
7 from django
.forms
.models
import inlineformset_factory
, modelformset_factory
9 from ajax_select
.fields
import AutoCompleteSelectField
11 from auf
.django
.references
import models
as ref
12 from auf
.django
.workflow
.forms
import WorkflowFormMixin
14 from project
import groups
15 from project
.rh
import models
as rh
16 from project
.dae
import models
as dae
17 from project
.dae
.workflow
import POSTE_ETATS_BOUTONS
20 class BaseInlineFormSetWithInitial(BaseInlineFormSet
):
22 Cette classe permet de fournir l'option initial aux inlineformsets.
23 Elle devient désuette en django 1.4.
25 def __init__(self
, data
=None, files
=None, instance
=None,
26 save_as_new
=False, prefix
=None, queryset
=None, **kwargs
):
28 self
.initial_extra
= kwargs
.pop('initial', None)
30 from django
.db
.models
.fields
.related
import RelatedObject
32 self
.instance
= self
.fk
.rel
.to()
34 self
.instance
= instance
35 self
.save_as_new
= save_as_new
36 # is there a better way to get the object descriptor?
37 self
.rel_name
= RelatedObject(self
.fk
.rel
.to
, self
.model
, self
.fk
).get_accessor_name()
39 queryset
= self
.model
._default_manager
40 qs
= queryset
.filter(**{self
.fk
.name
: self
.instance
})
41 super(BaseInlineFormSetWithInitial
, self
).__init__(data
, files
, prefix
=prefix
,
42 queryset
=qs
, **kwargs
)
44 def _construct_form(self
, i
, **kwargs
):
45 if self
.is_bound
and i
< self
.initial_form_count():
46 # Import goes here instead of module-level because importing
47 # django.db has side effects.
48 from django
.db
import connections
49 pk_key
= "%s-%s" % (self
.add_prefix(i
), self
.model
._meta
.pk
.name
)
50 pk
= self
.data
[pk_key
]
51 pk_field
= self
.model
._meta
.pk
52 pk
= pk_field
.get_db_prep_lookup('exact', pk
,
53 connection
=connections
[self
.get_queryset().db
])
54 if isinstance(pk
, list):
56 kwargs
['instance'] = self
._existing_object(pk
)
57 if i
< self
.initial_form_count() and not kwargs
.get('instance'):
58 kwargs
['instance'] = self
.get_queryset()[i
]
59 if i
>= self
.initial_form_count() and self
.initial_extra
:
60 # Set initial values for extra forms
62 kwargs
['initial'] = self
.initial_extra
[i
-self
.initial_form_count()]
66 defaults
= {'auto_id': self
.auto_id
, 'prefix': self
.add_prefix(i
)}
68 defaults
['data'] = self
.data
69 defaults
['files'] = self
.files
72 defaults
['initial'] = self
.initial
[i
]
75 # Allow extra forms to be empty.
76 if i
>= self
.initial_form_count():
77 defaults
['empty_permitted'] = True
78 defaults
.update(kwargs
)
79 form
= self
.form(**defaults
)
80 self
.add_fields(form
, i
)
84 def _implantation_choices(obj
, request
):
86 employe
= groups
.get_employe_from_user(request
.user
)
87 q
= Q(**{'zone_administrative': employe
.implantation
.zone_administrative
})
90 user_groupes
= [g
.name
for g
in request
.user
.groups
.all()]
91 if groups
.DRH_NIVEAU_1
in user_groupes
:
93 return [('', '----------')] + \
94 [(i
.id, unicode(i
), )for i
in ref
.Implantation
.objects
.filter(q
)]
97 def _employe_choices(obj
, request
):
99 employe
= groups
.get_employe_from_user(request
.user
)
100 q_dae_region_service
= Q(
101 poste__implantation__zone_administrative
=(
102 employe
.implantation
.zone_administrative
105 q_rh_region_service
= Q(
106 poste__implantation__zone_administrative
=(
107 employe
.implantation
.zone_administrative
111 user_groupes
= [g
.name
for g
in request
.user
.groups
.all()]
112 if groups
.DRH_NIVEAU_1
in user_groupes
:
113 q_dae_region_service
= Q()
114 q_rh_region_service
= Q()
116 # On filtre les employes avec les droits régionaux et on s'assure que
117 # c'est bien le dernier dossier en date pour sortir l'employe. On retient
118 # un employé qui travaille présentement dans la même région que le user
120 dossiers_regionaux_ids
= [
121 d
.id for d
in dae
.Dossier
.objects
.filter(q_dae_region_service
)
125 for d
in dae
.Dossier
.objects
127 .annotate(dernier_dossier
=Max('id'))
128 if d
['dernier_dossier'] in dossiers_regionaux_ids
130 dae_employe
= dae
.Employe
.objects
.filter(id__in
=employes_ids
)
131 dae_
= dae_employe
.filter(id_rh__isnull
=True)
132 copies
= dae_employe
.filter(Q(id_rh__isnull
=False))
133 id_copies
= [p
.id_rh_id
for p
in copies
.all()]
135 dossiers_regionaux_ids
= [
136 d
.id for d
in rh
.Dossier
.objects
.filter(q_rh_region_service
)
140 for d
in rh
.Dossier
.objects
142 .annotate(dernier_dossier
=Max('id'))
143 if d
['dernier_dossier'] in dossiers_regionaux_ids
145 rhv1
= rh
.Employe
.objects \
146 .filter(id__in
=employes_ids
) \
147 .exclude(id__in
=id_copies
)
149 # On ajoute les nouveaux Employés DAE qui ont été crées, mais qui n'ont
150 # pas de Dossier associés
151 employes_avec_dae
= [d
.employe_id
for d
in dae
.Dossier
.objects
.all()]
152 employes_orphelins
= dae
.Employe
.objects
.exclude(id__in
=employes_avec_dae
)
154 def option_label(employe
):
155 return "%s %s" % (employe
.nom
.upper(), employe
.prenom
.title())
157 return [('', 'Nouvel employé')] + \
159 [('dae-%s' % p
.id, option_label(p
))
160 for p
in dae_ | copies | employes_orphelins
] +
161 [('rh-%s' % p
.id, option_label(p
)) for p
in rhv1
],
166 def label_poste_display(poste
):
167 """Formate un visuel pour un poste dans une liste déroulante"""
170 annee
= poste
.date_debut
.year
175 label
= u
"%s %s - %s [%s]" % (
176 annee
, nom
, poste
.type_poste
.categorie_emploi
.nom
, poste
.id
179 label
= unicode(poste
)
183 PostePieceFormSet
= inlineformset_factory(dae
.Poste
, dae
.PostePiece
,)
184 DossierPieceForm
= inlineformset_factory(dae
.Dossier
, dae
.DossierPiece
)
186 # Ce formset est utilisé dans le cas de la création de poste prépopulé avec les
188 FinancementFormSetInitial
= inlineformset_factory(
190 dae
.PosteFinancement
,
191 formset
=BaseInlineFormSetWithInitial
,
194 FinancementFormSet
= inlineformset_factory(
196 dae
.PosteFinancement
,
201 class DossierComparaisonForm(forms
.ModelForm
):
203 recherche
= AutoCompleteSelectField('dossiers', required
=False)
204 poste
= forms
.CharField(
205 max_length
=255, widget
=forms
.TextInput(attrs
={'size': '60'})
209 model
= dae
.DossierComparaison
210 exclude
= ('dossier',)
212 DossierComparaisonFormSet
= modelformset_factory(
213 dae
.DossierComparaison
, extra
=3, max_num
=3, form
=DossierComparaisonForm
217 class PosteComparaisonForm(forms
.ModelForm
):
219 recherche
= AutoCompleteSelectField('dae_postes', required
=False)
222 model
= dae
.PosteComparaison
225 # Ce formset est utilisé dans le cas de la création de poste prépopulé avec les
227 PosteComparaisonFormSetInitial
= inlineformset_factory(
229 dae
.PosteComparaison
,
232 form
=PosteComparaisonForm
,
233 formset
=BaseInlineFormSetWithInitial
,
235 PosteComparaisonFormSet
= inlineformset_factory(
237 dae
.PosteComparaison
,
240 form
=PosteComparaisonForm
,
244 class FlexibleRemunForm(forms
.ModelForm
):
246 montant_mensuel
= forms
.DecimalField(required
=False)
247 montant
= forms
.DecimalField(required
=True, label
='Montant annuel')
250 model
= dae
.Remuneration
252 def clean_devise(self
):
253 devise
= self
.cleaned_data
['devise']
254 if devise
.code
== 'EUR':
256 implantation
= ref
.Implantation
.objects
.get(
257 id=self
.data
['implantation']
259 liste_taux
= devise
.tauxchange_set
.order_by('-annee')
260 if len(liste_taux
) == 0:
261 raise forms
.ValidationError(
262 u
"La devise %s n'a pas de taux pour l'implantation %s" %
263 (devise
, implantation
)
268 RemunForm
= inlineformset_factory(
269 dae
.Dossier
, dae
.Remuneration
, extra
=5, form
=FlexibleRemunForm
273 class PosteForm(forms
.ModelForm
):
274 """ Formulaire des postes. """
276 # On ne propose que les services actifs
277 service
= forms
.ModelChoiceField(
278 queryset
=rh
.Service
.objects
.all(), required
=True
281 responsable
= AutoCompleteSelectField('responsables', required
=True)
282 #responsable = forms.ModelChoiceField(
283 # queryset=rh.Poste.objects.select_related(depth=1))
285 # La liste des choix est laissée vide. Voir __init__ pour la raison.
286 poste
= forms
.ChoiceField(label
="Nouveau poste ou évolution du poste",
287 choices
=(), required
=False)
289 valeur_point_min
= forms
.ModelChoiceField(
290 queryset
=rh
.ValeurPoint
.actuelles
.all(), required
=False
292 valeur_point_max
= forms
.ModelChoiceField(
293 queryset
=rh
.ValeurPoint
.actuelles
.all(), required
=False
298 fields
= ('type_intervention',
299 'poste', 'implantation', 'type_poste', 'service', 'nom',
300 'responsable', 'local', 'expatrie', 'mise_a_disposition',
301 'appel', 'date_debut', 'date_fin',
302 'regime_travail', 'regime_travail_nb_heure_semaine',
303 'classement_min', 'classement_max',
304 'valeur_point_min', 'valeur_point_max',
305 'devise_min', 'devise_max',
306 'salaire_min', 'salaire_max',
307 'indemn_expat_min', 'indemn_expat_max',
308 'indemn_fct_min', 'indemn_fct_max',
309 'charges_patronales_min', 'charges_patronales_max',
310 'autre_min', 'autre_max', 'devise_comparaison',
311 'comp_locale_min', 'comp_locale_max',
312 'comp_universite_min', 'comp_universite_max',
313 'comp_fonctionpub_min', 'comp_fonctionpub_max',
314 'comp_ong_min', 'comp_ong_max',
315 'comp_autre_min', 'comp_autre_max',
318 widgets
= dict(type_intervention
=forms
.RadioSelect(),
319 appel
=forms
.RadioSelect(),
320 nom
=forms
.TextInput(attrs
={'size': 60},),
321 date_debut
=admin_widgets
.AdminDateWidget(),
322 date_fin
=admin_widgets
.AdminDateWidget(),
323 justification
=forms
.Textarea(attrs
={'cols': 80},),
324 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
325 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
328 def __init__(self
, *args
, **kwargs
):
329 """ Mise à jour dynamique du contenu du menu des postes.
331 Si on ne met le menu à jour de cette façon, à chaque instantiation du
332 formulaire, son contenu est mis en cache par le système et il ne
333 reflète pas les changements apportés par les ajouts, modifications,
336 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
337 car le "id" de chaque choix est spécial (voir _poste_choices).
340 request
= kwargs
.pop('request')
341 super(PosteForm
, self
).__init__(*args
, **kwargs
)
342 self
.fields
['poste'].choices
= self
._poste_choices(request
)
344 self
.fields
['implantation'].choices
= \
345 _implantation_choices(self
, request
)
347 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
348 if self
.instance
and self
.instance
.id is None:
349 dossiers
= self
.instance
.get_dossiers()
350 if len(dossiers
) > 0:
351 self
.initial
['service'] = dossiers
[0].poste
.service
353 def _poste_choices(self
, request
):
354 """ Menu déroulant pour les postes.
355 Constitué des postes de RH
357 postes_rh
= rh
.Poste
.objects
.ma_region_ou_service(request
.user
).all()
358 postes_rh
= postes_rh
.select_related(depth
=1)
360 return [('', 'Nouveau poste')] + \
361 sorted([('rh-%s' % p
.id, label_poste_display(p
)) for p
in
367 Validation conditionnelles de certains champs.
369 cleaned_data
= self
.cleaned_data
371 if cleaned_data
.get("local") is False \
372 and cleaned_data
.get("expatrie") is False:
373 msg
= "Le poste doit au moins être ouvert localement " \
375 self
._errors
["local"] = self
.error_class([msg
])
376 self
._errors
["expatrie"] = ''
377 raise forms
.ValidationError(msg
)
382 class ChoosePosteForm(forms
.ModelForm
):
387 # La liste des choix est laissée vide. Voir PosteForm.__init__.
388 poste
= forms
.ChoiceField(choices
=(), required
=False)
390 def __init__(self
, request
=None, *args
, **kwargs
):
391 super(ChoosePosteForm
, self
).__init__(*args
, **kwargs
)
392 self
.fields
['poste'].choices
= self
._poste_choices(request
)
394 def _poste_choices(self
, request
):
395 """ Menu déroulant pour les postes. """
396 dae_
= dae
.Poste
.objects
.ma_region_ou_service(request
.user
) \
397 .filter(id_rh__isnull
=True)
398 copies
= dae
.Poste
.objects
.ma_region_ou_service(request
.user
) \
399 .exclude(id_rh__isnull
=True)
401 return [('', '----------')] + \
402 sorted([('dae-%s' % p
.id, unicode(p
)) for p
in dae_ | copies
],
406 class EmployeForm(forms
.ModelForm
):
407 """ Formulaire des employés. """
410 fields
= ('employe', 'nom', 'prenom', 'genre')
412 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
413 employe
= forms
.ChoiceField(choices
=(), required
=False)
415 def __init__(self
, *args
, **kwargs
):
416 """ Mise à jour dynamique du contenu du menu des employés. """
417 request
= kwargs
.pop('request', None)
418 super(EmployeForm
, self
).__init__(*args
, **kwargs
)
419 self
.fields
['employe'].choices
= _employe_choices(self
, request
)
422 class DossierForm(forms
.ModelForm
):
423 """ Formulaire des dossiers. """
425 exclude
= ('etat', 'employe', 'poste', 'date_debut',)
427 widgets
= dict(statut_residence
=forms
.RadioSelect(),
428 contrat_date_debut
=admin_widgets
.AdminDateWidget(),
429 contrat_date_fin
=admin_widgets
.AdminDateWidget(),
435 class PosteWorkflowForm(WorkflowFormMixin
):
436 bouton_libelles
= POSTE_ETATS_BOUTONS
442 def __init__(self
, *args
, **kwargs
):
443 super(PosteWorkflowForm
, self
).__init__(*args
, **kwargs
)
444 self
.fields
['etat'].help_text
= WF_HELP_TEXT
447 class DossierWorkflowForm(WorkflowFormMixin
):
448 bouton_libelles
= POSTE_ETATS_BOUTONS
# meme workflow que poste...
454 def __init__(self
, *args
, **kwargs
):
455 super(DossierWorkflowForm
, self
).__init__(*args
, **kwargs
)
456 self
.fields
['etat'].help_text
= WF_HELP_TEXT
457 self
._etat_initial
= self
.instance
.etat
460 super(DossierWorkflowForm
, self
).save()
461 poste
= self
.instance
.poste
462 if poste
.etat
== self
._etat_initial
:
463 poste
.etat
= self
.instance
.etat
467 class ContratForm(forms
.ModelForm
):
470 fields
= ('type_contrat', 'fichier', )
474 class DAENumeriseeForm(forms
.ModelForm
):
478 fields
= ('dae_numerisee',)
481 class DAEFinaliseesSearchForm(forms
.Form
):
483 label
='Recherche', required
=False,
484 widget
=forms
.TextInput(attrs
={'size': 40})
486 importees
= forms
.ChoiceField(
487 label
='Importation', required
=False, choices
=(
489 ('oui', 'DAE importées seulement'),
490 ('non', 'DAE non-importées seulement'),