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
or \
92 groups
.DRH_NIVEAU_2
in user_groupes
:
94 return [('', '----------')] + \
95 [(i
.id, unicode(i
), )for i
in ref
.Implantation
.objects
.filter(q
)]
98 def _employe_choices(obj
, request
):
100 employe
= groups
.get_employe_from_user(request
.user
)
101 q_dae_region_service
= Q(
102 poste__implantation__zone_administrative
=(
103 employe
.implantation
.zone_administrative
106 q_rh_region_service
= Q(
107 poste__implantation__zone_administrative
=(
108 employe
.implantation
.zone_administrative
112 user_groupes
= [g
.name
for g
in request
.user
.groups
.all()]
113 if groups
.DRH_NIVEAU_1
in user_groupes
or \
114 groups
.DRH_NIVEAU_2
in user_groupes
:
115 q_dae_region_service
= Q()
116 q_rh_region_service
= Q()
118 # On filtre les employes avec les droits régionaux et on s'assure que
119 # c'est bien le dernier dossier en date pour sortir l'employe. On retient
120 # un employé qui travaille présentement dans la même région que le user
122 dossiers_regionaux_ids
= [
123 d
.id for d
in dae
.Dossier
.objects
.filter(q_dae_region_service
)
127 for d
in dae
.Dossier
.objects
129 .annotate(dernier_dossier
=Max('id'))
130 if d
['dernier_dossier'] in dossiers_regionaux_ids
132 dae_employe
= dae
.Employe
.objects
.filter(id__in
=employes_ids
)
133 dae_
= dae_employe
.filter(id_rh__isnull
=True)
134 copies
= dae_employe
.filter(Q(id_rh__isnull
=False))
135 id_copies
= [p
.id_rh_id
for p
in copies
.all()]
137 dossiers_regionaux_ids
= [
138 d
.id for d
in rh
.Dossier
.objects
.filter(q_rh_region_service
)
142 for d
in rh
.Dossier
.objects
144 .annotate(dernier_dossier
=Max('id'))
145 if d
['dernier_dossier'] in dossiers_regionaux_ids
147 rhv1
= rh
.Employe
.objects \
148 .filter(id__in
=employes_ids
) \
149 .exclude(id__in
=id_copies
)
151 # On ajoute les nouveaux Employés DAE qui ont été crées, mais qui n'ont
152 # pas de Dossier associés
153 employes_avec_dae
= [d
.employe_id
for d
in dae
.Dossier
.objects
.all()]
154 employes_orphelins
= dae
.Employe
.objects
.exclude(id__in
=employes_avec_dae
)
156 def option_label(employe
, extra
=""):
158 extra
= " [%s]" % extra
159 return "%s %s %s" % (employe
.nom
.upper(), employe
.prenom
.title(), extra
)
161 lbl_rh
= sorted([('rh-%s' % p
.id, option_label(p
, "existant dans rh")) for p
in rhv1
],
163 lbl_dae
= sorted([('dae-%s' % p
.id, option_label(p
)) for p
in dae_ | copies | employes_orphelins
],
165 return [('', 'Nouvel employé')] + lbl_rh
+ lbl_dae
168 def label_poste_display(poste
):
169 """Formate un visuel pour un poste dans une liste déroulante"""
172 annee
= poste
.date_debut
.year
177 label
= u
"%s %s - %s [%s]" % (
178 annee
, nom
, poste
.type_poste
.categorie_emploi
.nom
, poste
.id
181 label
= unicode(poste
)
185 PostePieceFormSet
= inlineformset_factory(dae
.Poste
, dae
.PostePiece
,)
186 DossierPieceForm
= inlineformset_factory(dae
.Dossier
, dae
.DossierPiece
)
188 # Ce formset est utilisé dans le cas de la création de poste prépopulé avec les
190 FinancementFormSetInitial
= inlineformset_factory(
192 dae
.PosteFinancement
,
193 formset
=BaseInlineFormSetWithInitial
,
196 FinancementFormSet
= inlineformset_factory(
198 dae
.PosteFinancement
,
203 class DossierComparaisonForm(forms
.ModelForm
):
205 recherche
= AutoCompleteSelectField('dossiers', required
=False)
206 poste
= forms
.CharField(
207 max_length
=255, widget
=forms
.TextInput(attrs
={'size': '60'})
211 model
= dae
.DossierComparaison
212 exclude
= ('dossier',)
214 DossierComparaisonFormSet
= modelformset_factory(
215 dae
.DossierComparaison
, extra
=3, max_num
=3, form
=DossierComparaisonForm
219 class PosteComparaisonForm(forms
.ModelForm
):
221 recherche
= AutoCompleteSelectField('dae_postes', required
=False)
224 model
= dae
.PosteComparaison
227 # Ce formset est utilisé dans le cas de la création de poste prépopulé avec les
229 PosteComparaisonFormSetInitial
= inlineformset_factory(
231 dae
.PosteComparaison
,
234 form
=PosteComparaisonForm
,
235 formset
=BaseInlineFormSetWithInitial
,
237 PosteComparaisonFormSet
= inlineformset_factory(
239 dae
.PosteComparaison
,
242 form
=PosteComparaisonForm
,
246 class FlexibleRemunForm(forms
.ModelForm
):
248 montant_mensuel
= forms
.DecimalField(required
=False)
249 montant
= forms
.DecimalField(required
=True, label
='Montant annuel')
252 model
= dae
.Remuneration
254 def clean_devise(self
):
255 devise
= self
.cleaned_data
['devise']
256 if devise
.code
== 'EUR':
258 implantation
= ref
.Implantation
.objects
.get(
259 id=self
.data
['implantation']
261 liste_taux
= devise
.tauxchange_set
.order_by('-annee')
262 if len(liste_taux
) == 0:
263 raise forms
.ValidationError(
264 u
"La devise %s n'a pas de taux pour l'implantation %s" %
265 (devise
, implantation
)
270 RemunForm
= inlineformset_factory(
271 dae
.Dossier
, dae
.Remuneration
, extra
=5, form
=FlexibleRemunForm
275 class PosteForm(forms
.ModelForm
):
276 """ Formulaire des postes. """
278 # On ne propose que les services actifs
279 service
= forms
.ModelChoiceField(
280 queryset
=rh
.Service
.objects
.all(), required
=True
283 responsable
= AutoCompleteSelectField('responsables', required
=True)
284 #responsable = forms.ModelChoiceField(
285 # queryset=rh.Poste.objects.select_related(depth=1))
287 # La liste des choix est laissée vide. Voir __init__ pour la raison.
288 poste
= forms
.ChoiceField(label
="Nouveau poste ou évolution du poste",
289 choices
=(), required
=False)
291 valeur_point_min
= forms
.ModelChoiceField(
292 queryset
=rh
.ValeurPoint
.actuelles
.all(), required
=False
294 valeur_point_max
= forms
.ModelChoiceField(
295 queryset
=rh
.ValeurPoint
.actuelles
.all(), required
=False
300 fields
= ('type_intervention',
301 'poste', 'implantation', 'type_poste', 'service', 'nom',
302 'responsable', 'local', 'expatrie', 'mise_a_disposition',
303 'appel', 'date_debut', 'date_fin',
304 'regime_travail', 'regime_travail_nb_heure_semaine',
305 'classement_min', 'classement_max',
306 'valeur_point_min', 'valeur_point_max',
307 'devise_min', 'devise_max',
308 'salaire_min', 'salaire_max',
309 'indemn_expat_min', 'indemn_expat_max',
310 'indemn_fct_min', 'indemn_fct_max',
311 'charges_patronales_min', 'charges_patronales_max',
312 'autre_min', 'autre_max', 'devise_comparaison',
313 'comp_locale_min', 'comp_locale_max',
314 'comp_universite_min', 'comp_universite_max',
315 'comp_fonctionpub_min', 'comp_fonctionpub_max',
316 'comp_ong_min', 'comp_ong_max',
317 'comp_autre_min', 'comp_autre_max',
320 widgets
= dict(type_intervention
=forms
.RadioSelect(),
321 appel
=forms
.RadioSelect(),
322 nom
=forms
.TextInput(attrs
={'size': 60},),
323 date_debut
=admin_widgets
.AdminDateWidget(),
324 date_fin
=admin_widgets
.AdminDateWidget(),
325 justification
=forms
.Textarea(attrs
={'cols': 80},),
326 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
327 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
330 def __init__(self
, *args
, **kwargs
):
331 """ Mise à jour dynamique du contenu du menu des postes.
333 Si on ne met le menu à jour de cette façon, à chaque instantiation du
334 formulaire, son contenu est mis en cache par le système et il ne
335 reflète pas les changements apportés par les ajouts, modifications,
338 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
339 car le "id" de chaque choix est spécial (voir _poste_choices).
342 request
= kwargs
.pop('request')
343 super(PosteForm
, self
).__init__(*args
, **kwargs
)
344 self
.fields
['poste'].choices
= self
._poste_choices(request
)
346 self
.fields
['implantation'].choices
= \
347 _implantation_choices(self
, request
)
349 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
350 if self
.instance
and self
.instance
.id is None:
351 dossiers
= self
.instance
.get_dossiers()
352 if len(dossiers
) > 0:
353 self
.initial
['service'] = dossiers
[0].poste
.service
355 def _poste_choices(self
, request
):
356 """ Menu déroulant pour les postes.
357 Constitué des postes de RH
359 postes_rh
= rh
.Poste
.objects
.ma_region_ou_service(request
.user
).all()
360 postes_rh
= postes_rh
.select_related(depth
=1)
362 return [('', 'Nouveau poste')] + \
363 sorted([('rh-%s' % p
.id, label_poste_display(p
)) for p
in
369 Validation conditionnelles de certains champs.
371 cleaned_data
= self
.cleaned_data
373 if cleaned_data
.get("local") is False \
374 and cleaned_data
.get("expatrie") is False:
375 msg
= "Le poste doit au moins être ouvert localement " \
377 self
._errors
["local"] = self
.error_class([msg
])
378 self
._errors
["expatrie"] = ''
379 raise forms
.ValidationError(msg
)
384 class ChoosePosteForm(forms
.ModelForm
):
389 # La liste des choix est laissée vide. Voir PosteForm.__init__.
390 poste
= forms
.ChoiceField(choices
=(), required
=False)
392 def __init__(self
, request
=None, *args
, **kwargs
):
393 super(ChoosePosteForm
, self
).__init__(*args
, **kwargs
)
394 self
.fields
['poste'].choices
= self
._poste_choices(request
)
396 def _poste_choices(self
, request
):
397 """ Menu déroulant pour les postes. """
398 dae_
= dae
.Poste
.objects
.ma_region_ou_service(request
.user
) \
399 .filter(id_rh__isnull
=True)
400 copies
= dae
.Poste
.objects
.ma_region_ou_service(request
.user
) \
401 .exclude(id_rh__isnull
=True)
403 return [('', '----------')] + \
404 sorted([('dae-%s' % p
.id, unicode(p
)) for p
in dae_ | copies
],
408 class EmployeForm(forms
.ModelForm
):
409 """ Formulaire des employés. """
412 fields
= ('employe', 'nom', 'prenom', 'genre')
414 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
415 employe
= forms
.ChoiceField(choices
=(), required
=False)
417 def __init__(self
, *args
, **kwargs
):
418 """ Mise à jour dynamique du contenu du menu des employés. """
419 request
= kwargs
.pop('request', None)
420 super(EmployeForm
, self
).__init__(*args
, **kwargs
)
421 self
.fields
['employe'].choices
= _employe_choices(self
, request
)
424 class DossierForm(forms
.ModelForm
):
425 """ Formulaire des dossiers. """
427 exclude
= ('etat', 'employe', 'poste', 'date_debut',)
429 widgets
= dict(statut_residence
=forms
.RadioSelect(),
430 contrat_date_debut
=admin_widgets
.AdminDateWidget(),
431 contrat_date_fin
=admin_widgets
.AdminDateWidget(),
437 class PosteWorkflowForm(WorkflowFormMixin
):
438 bouton_libelles
= POSTE_ETATS_BOUTONS
444 def __init__(self
, *args
, **kwargs
):
445 super(PosteWorkflowForm
, self
).__init__(*args
, **kwargs
)
446 self
.fields
['etat'].help_text
= WF_HELP_TEXT
449 class DossierWorkflowForm(WorkflowFormMixin
):
450 bouton_libelles
= POSTE_ETATS_BOUTONS
# meme workflow que poste...
456 def __init__(self
, *args
, **kwargs
):
457 super(DossierWorkflowForm
, self
).__init__(*args
, **kwargs
)
458 self
.fields
['etat'].help_text
= WF_HELP_TEXT
459 self
._etat_initial
= self
.instance
.etat
462 super(DossierWorkflowForm
, self
).save()
463 poste
= self
.instance
.poste
464 if poste
.etat
== self
._etat_initial
:
465 poste
.etat
= self
.instance
.etat
469 class ContratForm(forms
.ModelForm
):
472 fields
= ('type_contrat', 'fichier', )
476 class DAENumeriseeForm(forms
.ModelForm
):
480 fields
= ('dae_numerisee',)
483 class DAEFinaliseesSearchForm(forms
.Form
):
485 label
='Recherche', required
=False,
486 widget
=forms
.TextInput(attrs
={'size': 40})
488 importees
= forms
.ChoiceField(
489 label
='Importation', required
=False, choices
=(
491 ('oui', 'DAE importées seulement'),
492 ('non', 'DAE non-importées seulement'),