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 \
22 grp_drh
, POSTE_ETATS_BOUTONS
, POSTE_ETAT_FINALISE
25 class BaseInlineFormSetWithInitial(BaseInlineFormSet
):
27 Cette classe permet de fournir l'option initial aux inlineformsets.
28 Elle devient désuette en django 1.4.
30 def __init__(self
, data
=None, files
=None, instance
=None,
31 save_as_new
=False, prefix
=None, queryset
=None, **kwargs
):
33 self
.initial_extra
= kwargs
.pop('initial', None)
35 from django
.db
.models
.fields
.related
import RelatedObject
37 self
.instance
= self
.fk
.rel
.to()
39 self
.instance
= instance
40 self
.save_as_new
= save_as_new
41 # is there a better way to get the object descriptor?
42 self
.rel_name
= RelatedObject(self
.fk
.rel
.to
, self
.model
, self
.fk
).get_accessor_name()
44 queryset
= self
.model
._default_manager
45 qs
= queryset
.filter(**{self
.fk
.name
: self
.instance
})
46 super(BaseInlineFormSetWithInitial
, self
).__init__(data
, files
, prefix
=prefix
,
47 queryset
=qs
, **kwargs
)
49 def _construct_form(self
, i
, **kwargs
):
50 if self
.is_bound
and i
< self
.initial_form_count():
51 # Import goes here instead of module-level because importing
52 # django.db has side effects.
53 from django
.db
import connections
54 pk_key
= "%s-%s" % (self
.add_prefix(i
), self
.model
._meta
.pk
.name
)
55 pk
= self
.data
[pk_key
]
56 pk_field
= self
.model
._meta
.pk
57 pk
= pk_field
.get_db_prep_lookup('exact', pk
,
58 connection
=connections
[self
.get_queryset().db
])
59 if isinstance(pk
, list):
61 kwargs
['instance'] = self
._existing_object(pk
)
62 if i
< self
.initial_form_count() and not kwargs
.get('instance'):
63 kwargs
['instance'] = self
.get_queryset()[i
]
64 if i
>= self
.initial_form_count() and self
.initial_extra
:
65 # Set initial values for extra forms
67 kwargs
['initial'] = self
.initial_extra
[i
-self
.initial_form_count()]
71 defaults
= {'auto_id': self
.auto_id
, 'prefix': self
.add_prefix(i
)}
73 defaults
['data'] = self
.data
74 defaults
['files'] = self
.files
77 defaults
['initial'] = self
.initial
[i
]
80 # Allow extra forms to be empty.
81 if i
>= self
.initial_form_count():
82 defaults
['empty_permitted'] = True
83 defaults
.update(kwargs
)
84 form
= self
.form(**defaults
)
85 self
.add_fields(form
, i
)
89 def _implantation_choices(obj
, request
):
91 employe
= get_employe_from_user(request
.user
)
93 if is_user_dans_services_centraux(request
.user
):
94 q
= Q(**{'id': employe
.implantation_id
})
97 q
= Q(**{'region': employe
.implantation
.region
})
100 if grp_drh
in request
.user
.groups
.all():
102 return [('', '----------')] + \
103 [(i
.id, unicode(i
), )for i
in ref
.Implantation
.objects
.filter(q
)]
106 def _employe_choices(obj
, request
):
108 employe
= get_employe_from_user(request
.user
)
110 if is_user_dans_services_centraux(request
.user
):
111 q_dae_region_service
= Q(poste__implantation
=employe
.implantation
)
112 q_rh_region_service
= Q(poste__implantation
=employe
.implantation
)
115 q_dae_region_service
= Q(
116 poste__implantation__region
=employe
.implantation
.region
118 q_rh_region_service
= Q(
119 poste__implantation__region
=employe
.implantation
.region
122 if grp_drh
in request
.user
.groups
.all():
123 q_dae_region_service
= Q()
124 q_rh_region_service
= Q()
126 # On filtre les employes avec les droits régionaux et on s'assure que
127 # c'est bien le dernier dossier en date pour sortir l'employe. On retient
128 # un employé qui travaille présentement dans la même région que le user
130 dossiers_regionaux_ids
= [
131 d
.id for d
in dae
.Dossier
.objects
.filter(q_dae_region_service
)
135 for d
in dae
.Dossier
.objects
137 .annotate(dernier_dossier
=Max('id'))
138 if d
['dernier_dossier'] in dossiers_regionaux_ids
140 dae_employe
= dae
.Employe
.objects
.filter(id__in
=employes_ids
)
141 dae_
= dae_employe
.filter(id_rh__isnull
=True)
142 copies
= dae_employe
.filter(Q(id_rh__isnull
=False))
143 id_copies
= [p
.id_rh_id
for p
in copies
.all()]
145 dossiers_regionaux_ids
= [
146 d
.id for d
in rh
.Dossier
.objects
.filter(q_rh_region_service
)
150 for d
in rh
.Dossier
.objects
152 .annotate(dernier_dossier
=Max('id'))
153 if d
['dernier_dossier'] in dossiers_regionaux_ids
155 rhv1
= rh
.Employe
.objects \
156 .filter(id__in
=employes_ids
) \
157 .exclude(id__in
=id_copies
)
159 # On ajoute les nouveaux Employés DAE qui ont été crées, mais qui n'ont
160 # pas de Dossier associés
161 employes_avec_dae
= [d
.employe_id
for d
in dae
.Dossier
.objects
.all()]
162 employes_orphelins
= dae
.Employe
.objects
.exclude(id__in
=employes_avec_dae
)
164 def option_label(employe
):
165 return "%s %s" % (employe
.nom
.upper(), employe
.prenom
.title())
167 return [('', 'Nouvel employé')] + \
169 [('dae-%s' % p
.id, option_label(p
))
170 for p
in dae_ | copies | employes_orphelins
] +
171 [('rh-%s' % p
.id, option_label(p
)) for p
in rhv1
],
176 def label_poste_display(poste
):
177 """Formate un visuel pour un poste dans une liste déroulante"""
180 annee
= poste
.date_debut
.year
181 label
= u
"%s %s - %s [%s]" % (
182 annee
, poste
.type_poste
, poste
.type_poste
.categorie_emploi
.nom
,
187 PostePieceForm
= inlineformset_factory(
190 #formset=BaseInlineFormSetWithInitial,
194 DossierPieceForm
= inlineformset_factory(dae
.Dossier
, dae
.DossierPiece
)
197 FinancementForm
= inlineformset_factory(
199 dae
.PosteFinancement
,
200 formset
=BaseInlineFormSetWithInitial
,
205 class DossierComparaisonForm(forms
.ModelForm
):
207 recherche
= AutoCompleteSelectField('dossiers', required
=False)
208 poste
= forms
.CharField(
209 max_length
=255, widget
=forms
.TextInput(attrs
={'size': '60'})
213 model
= dae
.DossierComparaison
214 exclude
= ('dossier',)
216 DossierComparaisonFormSet
= modelformset_factory(
217 dae
.DossierComparaison
, extra
=3, max_num
=3, form
=DossierComparaisonForm
221 class PosteComparaisonForm(forms
.ModelForm
):
223 recherche
= AutoCompleteSelectField('dae_postes', required
=False)
226 model
= dae
.PosteComparaison
229 PosteComparaisonFormSet
= inlineformset_factory(
231 dae
.PosteComparaison
,
234 form
=PosteComparaisonForm
,
235 #formset=BaseModelFormSetWithInitial,
236 formset
=BaseInlineFormSetWithInitial
,
240 class FlexibleRemunForm(forms
.ModelForm
):
242 montant_mensuel
= forms
.DecimalField(required
=False)
243 montant
= forms
.DecimalField(required
=True, label
='Montant annuel')
246 model
= dae
.Remuneration
248 def clean_devise(self
):
249 devise
= self
.cleaned_data
['devise']
250 if devise
.code
== 'EUR':
252 implantation
= ref
.Implantation
.objects
.get(
253 id=self
.data
['implantation']
255 liste_taux
= devise
.tauxchange_set
.order_by('-annee')
256 if len(liste_taux
) == 0:
257 raise forms
.ValidationError(
258 u
"La devise %s n'a pas de taux pour l'implantation %s" %
259 (devise
, implantation
)
264 RemunForm
= inlineformset_factory(
265 dae
.Dossier
, dae
.Remuneration
, extra
=5, form
=FlexibleRemunForm
269 class PosteForm(forms
.ModelForm
):
270 """ Formulaire des postes. """
272 # On ne propose que les services actifs
273 service
= forms
.ModelChoiceField(
274 queryset
=rh
.Service
.objects
.all(), required
=True
277 responsable
= AutoCompleteSelectField('responsables', required
=True)
278 #responsable = forms.ModelChoiceField(
279 # queryset=rh.Poste.objects.select_related(depth=1))
281 # La liste des choix est laissée vide. Voir __init__ pour la raison.
282 poste
= forms
.ChoiceField(label
="Nouveau poste ou évolution du poste",
283 choices
=(), required
=False)
285 valeur_point_min
= forms
.ModelChoiceField(
286 queryset
=rh
.ValeurPoint
.actuelles
.all(), required
=False
288 valeur_point_max
= forms
.ModelChoiceField(
289 queryset
=rh
.ValeurPoint
.actuelles
.all(), required
=False
294 fields
= ('type_intervention',
295 'poste', 'implantation', 'type_poste', 'service', 'nom',
296 'responsable', 'local', 'expatrie', 'mise_a_disposition',
297 'appel', 'date_debut', 'date_fin',
298 'regime_travail', 'regime_travail_nb_heure_semaine',
299 'classement_min', 'classement_max',
300 'valeur_point_min', 'valeur_point_max',
301 'devise_min', 'devise_max',
302 'salaire_min', 'salaire_max',
303 'indemn_expat_min', 'indemn_expat_max',
304 'indemn_fct_min', 'indemn_fct_max',
305 'charges_patronales_min', 'charges_patronales_max',
306 'autre_min', 'autre_max', 'devise_comparaison',
307 'comp_locale_min', 'comp_locale_max',
308 'comp_universite_min', 'comp_universite_max',
309 'comp_fonctionpub_min', 'comp_fonctionpub_max',
310 'comp_ong_min', 'comp_ong_max',
311 'comp_autre_min', 'comp_autre_max',
314 widgets
= dict(type_intervention
=forms
.RadioSelect(),
315 appel
=forms
.RadioSelect(),
316 nom
=forms
.TextInput(attrs
={'size': 60},),
317 date_debut
=admin_widgets
.AdminDateWidget(),
318 date_fin
=admin_widgets
.AdminDateWidget(),
319 justification
=forms
.Textarea(attrs
={'cols': 80},),
320 #devise_min=forms.Select(attrs={'disabled':'disabled'}),
321 #devise_max=forms.Select(attrs={'disabled':'disabled'}),
324 def __init__(self
, *args
, **kwargs
):
325 """ Mise à jour dynamique du contenu du menu des postes.
327 Si on ne met le menu à jour de cette façon, à chaque instantiation du
328 formulaire, son contenu est mis en cache par le système et il ne
329 reflète pas les changements apportés par les ajouts, modifications,
332 Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField
333 car le "id" de chaque choix est spécial (voir _poste_choices).
336 request
= kwargs
.pop('request')
337 super(PosteForm
, self
).__init__(*args
, **kwargs
)
338 self
.fields
['poste'].choices
= self
._poste_choices(request
)
339 self
.fields
['implantation'].choices
= \
340 _implantation_choices(self
, request
)
342 # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1
343 if self
.instance
and self
.instance
.id is None:
344 dossiers
= self
.instance
.get_dossiers()
345 if len(dossiers
) > 0:
346 self
.initial
['service'] = dossiers
[0].poste
.service
348 def _poste_choices(self
, request
):
349 """ Menu déroulant pour les postes.
351 Constitué des postes de dae et des postes de rh_v1 qui n'ont pas
352 d'équivalent dans dae.
355 copies
= dae
.Poste
.objects \
356 .ma_region_ou_service(request
.user
) \
357 .exclude(id_rh__isnull
=True) \
358 .filter(etat
=POSTE_ETAT_FINALISE
)
359 id_copies
= [p
.id_rh_id
for p
in copies
.all()]
360 rhv1
= rh
.Poste
.objects
.ma_region_ou_service(request
.user
) \
361 .exclude(id__in
=id_copies
)
362 # Optimisation de la requête
363 rhv1
= rhv1
.select_related(depth
=1)
365 return [('', 'Nouveau poste')] + \
366 sorted([('rh-%s' % p
.id, label_poste_display(p
)) for p
in rhv1
],
371 Validation conditionnelles de certains champs.
373 cleaned_data
= self
.cleaned_data
375 if cleaned_data
.get("local") is False \
376 and cleaned_data
.get("expatrie") is False:
377 msg
= "Le poste doit au moins être ouvert localement " \
379 self
._errors
["local"] = self
.error_class([msg
])
380 self
._errors
["expatrie"] = ''
381 raise forms
.ValidationError(msg
)
385 def save(self
, *args
, **kwargs
):
386 kwargs2
= kwargs
.copy()
387 kwargs2
['commit'] = False
388 poste
= super(PosteForm
, self
).save(*args
, **kwargs2
)
390 if 'commit' not in kwargs
or kwargs
['commit']:
392 poste
.date_creation
= datetime
.datetime
.now()
397 class ChoosePosteForm(forms
.ModelForm
):
402 # La liste des choix est laissée vide. Voir PosteForm.__init__.
403 poste
= forms
.ChoiceField(choices
=(), required
=False)
405 def __init__(self
, request
=None, *args
, **kwargs
):
406 super(ChoosePosteForm
, self
).__init__(*args
, **kwargs
)
407 self
.fields
['poste'].choices
= self
._poste_choices(request
)
409 def _poste_choices(self
, request
):
410 """ Menu déroulant pour les postes. """
411 dae_
= dae
.Poste
.objects
.ma_region_ou_service(request
.user
) \
412 .filter(id_rh__isnull
=True)
413 copies
= dae
.Poste
.objects
.ma_region_ou_service(request
.user
) \
414 .exclude(id_rh__isnull
=True)
416 return [('', '----------')] + \
417 sorted([('dae-%s' % p
.id, unicode(p
)) for p
in dae_ | copies
],
421 class EmployeForm(forms
.ModelForm
):
422 """ Formulaire des employés. """
425 fields
= ('employe', 'nom', 'prenom', 'genre')
427 # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison.
428 employe
= forms
.ChoiceField(choices
=(), required
=False)
430 def __init__(self
, *args
, **kwargs
):
431 """ Mise à jour dynamique du contenu du menu des employés. """
432 request
= kwargs
.pop('request', None)
433 super(EmployeForm
, self
).__init__(*args
, **kwargs
)
434 self
.fields
['employe'].choices
= _employe_choices(self
, request
)
437 class DossierForm(forms
.ModelForm
):
438 """ Formulaire des dossiers. """
440 exclude
= ('etat', 'employe', 'poste', 'date_debut',)
442 widgets
= dict(statut_residence
=forms
.RadioSelect(),
443 contrat_date_debut
=admin_widgets
.AdminDateWidget(),
444 contrat_date_fin
=admin_widgets
.AdminDateWidget(),
447 def save(self
, *args
, **kwargs
):
448 dossier
= super(DossierForm
, self
).save(*args
, **kwargs
)
449 if dossier
.id is None:
450 dossier
.date_creation
= datetime
.datetime
.now()
457 class PosteWorkflowForm(WorkflowFormMixin
):
458 bouton_libelles
= POSTE_ETATS_BOUTONS
464 def __init__(self
, *args
, **kwargs
):
465 super(PosteWorkflowForm
, self
).__init__(*args
, **kwargs
)
466 self
.fields
['etat'].help_text
= WF_HELP_TEXT
469 class DossierWorkflowForm(WorkflowFormMixin
):
470 bouton_libelles
= POSTE_ETATS_BOUTONS
# meme workflow que poste...
476 def __init__(self
, *args
, **kwargs
):
477 super(DossierWorkflowForm
, self
).__init__(*args
, **kwargs
)
478 self
.fields
['etat'].help_text
= WF_HELP_TEXT
479 self
._etat_initial
= self
.instance
.etat
482 super(DossierWorkflowForm
, self
).save()
483 poste
= self
.instance
.poste
484 if poste
.etat
== self
._etat_initial
:
485 poste
.etat
= self
.instance
.etat
489 class ContratForm(forms
.ModelForm
):
492 fields
= ('type_contrat', 'fichier', )
496 class DAENumeriseeForm(forms
.ModelForm
):
500 fields
= ('dae_numerisee',)
503 class DAEFinaliseesSearchForm(forms
.Form
):
505 label
='Recherche', required
=False,
506 widget
=forms
.TextInput(attrs
={'size': 40})
508 importees
= forms
.ChoiceField(
509 label
='Importation', required
=False, choices
=(
511 ('oui', 'DAE importées seulement'),
512 ('non', 'DAE non-importées seulement'),