Commit | Line | Data |
---|---|---|
5d680e84 | 1 | # -*- encoding: utf-8 -*- |
ce110fb9 | 2 | |
f258e4e7 | 3 | from django.db.models import Q |
5d680e84 | 4 | from django import forms |
36341125 | 5 | from django.forms.models import inlineformset_factory |
e88caaf0 | 6 | from django.contrib.admin import widgets as admin_widgets |
afc204bf | 7 | from ajax_select.fields import AutoCompleteSelectField |
8fa94e8b | 8 | from auf.django.workflow.forms import WorkflowFormMixin |
5d680e84 | 9 | from datamaster_modeles import models as ref |
5d680e84 | 10 | from dae import models as dae |
f258e4e7 | 11 | from utils import get_employe_from_user, is_user_dans_service |
5d680e84 | 12 | from rh_v1 import models as rh |
f258e4e7 OL |
13 | from workflow import grp_drh |
14 | ||
15 | def _implantation_choices(obj, request): | |
16 | # TRAITEMENT NORMAL | |
17 | employe = get_employe_from_user(request.user) | |
18 | # SERVICE | |
19 | if is_user_dans_service(request.user): | |
20 | q = Q(**{ 'id' : employe.implantation_id }) | |
21 | # REGION | |
22 | else: | |
23 | q = Q(**{ 'region' : employe.implantation.region }) | |
24 | ||
25 | # TRAITEMENT DRH | |
26 | if grp_drh in request.user.groups.all(): | |
27 | q = Q() | |
28 | return [('', '----------')] + [(i.id, unicode(i), )for i in ref.Implantation.objects.filter(q)] | |
29 | ||
30 | def _employe_choices(obj, request): | |
31 | q = Q(id_rh__isnull=True) & Q(id_rh__isnull=True) | |
32 | ||
33 | # TRAITEMENT NORMAL | |
34 | employe = get_employe_from_user(request.user) | |
35 | # SERVICE | |
36 | if is_user_dans_service(request.user): | |
37 | q_region_service = Q(implantation1=employe.implantation) | Q(implantation2=employe.implantation) | |
38 | # REGION | |
39 | else: | |
40 | q_region_service = Q(implantation1__region=employe.implantation.region) | Q(implantation2__region=employe.implantation.region) | |
41 | # TRAITEMENT DRH | |
42 | if grp_drh in request.user.groups.all(): | |
43 | q_region_service = Q() | |
44 | ||
45 | # Construction de la liste des employés en puisant dans DAE (pas d'info) et dans rh_v1 | |
46 | # Pour le filtrage par région/service, on est obligé d'aller regarder le dossier rh_v1 | |
47 | # car l'information dans le modèle rh_v1.Employe n'existe pas. | |
48 | dae_ = dae.Employe.objects.filter(id_rh__isnull=True) | |
49 | copies = dae.Employe.objects.filter(Q(id_rh__isnull=False)) | |
50 | id_copies = [p.id_rh_id for p in copies.all()] | |
51 | employes_ids = list(set([d.employe_id for d in rh.Dossier.objects.filter(q_region_service)])) | |
52 | rhv1 = rh.Employe.objects.filter(id__in=employes_ids).exclude(id__in=id_copies) | |
53 | ||
54 | def option_label(employe): | |
55 | return "%s %s" % (employe.nom.upper(), employe.prenom.title()) | |
56 | ||
57 | return [('', 'Nouvel employé')] + \ | |
58 | sorted([('dae-%s' % p.id, option_label(p)) for p in dae_ | copies] + | |
59 | [('rh-%s' % p.id, option_label(p)) for p in rhv1], | |
60 | key=lambda t: t[1]) | |
61 | ||
5d680e84 | 62 | |
4bce4d24 OL |
63 | def label_poste_display(poste): |
64 | """Formate un visuel pour un poste dans une liste déroulante""" | |
65 | label = u"%s - %s [%s]" %(poste.type_poste, poste.type_poste.famille_emploi.nom, poste.id) | |
66 | return label | |
9cb4de55 OL |
67 | |
68 | class PostePieceForm(inlineformset_factory(dae.Poste, dae.PostePiece)): | |
69 | pass | |
70 | ||
71 | class DossierPieceForm(inlineformset_factory(dae.Dossier, dae.DossierPiece)): | |
72 | pass | |
73 | ||
151e7bd0 OL |
74 | class FinancementForm(inlineformset_factory(dae.Poste, dae.PosteFinancement, extra=1)): |
75 | pass | |
76 | ||
03b395db OL |
77 | |
78 | class DossierComparaisonForm(forms.ModelForm): | |
79 | ||
80 | recherche = AutoCompleteSelectField('dossiers', required=False) | |
81 | poste = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'size':'60'})) | |
82 | ||
83 | class Model: | |
84 | model = dae.DossierComparaison | |
85 | ||
03b395db OL |
86 | class DossierComparaisonForm(inlineformset_factory(dae.Dossier, dae.DossierComparaison, extra=3, max_num=3, form=DossierComparaisonForm)): |
87 | pass | |
88 | ||
0a085c42 OL |
89 | class FlexibleRemunForm(forms.ModelForm): |
90 | ||
91 | montant_mensuel = forms.DecimalField(required=False) | |
92 | montant = forms.DecimalField(required=True, label='Montant annuel') | |
93 | ||
94 | class Meta: | |
95 | model = dae.Remuneration | |
96 | ||
97 | class RemunForm(inlineformset_factory(dae.Dossier, dae.Remuneration, extra=5, form=FlexibleRemunForm)): | |
98 | pass | |
99 | ||
72db8238 OL |
100 | class JustificationNouvelEmployeForm(inlineformset_factory(dae.Dossier, |
101 | dae.JustificationNouvelEmploye, | |
102 | extra=0, | |
103 | can_delete=False, | |
104 | exclude=('question',))): | |
105 | """ | |
106 | Formulaire de justification d'un nouvel employé. | |
107 | Le dossier a besoin d'être enregistré une première fois afin de prépopuler les questions. | |
108 | """ | |
109 | def __init__(self, *args, **kwargs): | |
110 | instance = kwargs['instance'] | |
111 | if instance.id: | |
1cc5e025 | 112 | q_ids = [j.question.id for j in instance.justificationnouvelemploye_set.filter(dossier=instance)] |
72db8238 OL |
113 | for q in dae.JustificationQuestion.objects.filter(type="N"): |
114 | if q.id in q_ids: | |
115 | continue | |
116 | j = dae.JustificationNouvelEmploye() | |
117 | j.dossier = instance | |
118 | j.question = q | |
119 | j.save() | |
120 | super(self.__class__, self).__init__(*args, **kwargs) | |
121 | ||
122 | class JustificationAutreEmployeForm(inlineformset_factory(dae.Dossier, | |
123 | dae.JustificationAutreEmploye, | |
124 | extra=0, | |
125 | can_delete=False, | |
126 | exclude=('question',))): | |
127 | """ | |
128 | Formulaire de justification d'un nouvel employé. | |
129 | Le dossier a besoin d'être enregistré une première fois afin de prépopuler les questions. | |
130 | """ | |
131 | def __init__(self, *args, **kwargs): | |
132 | instance = kwargs['instance'] | |
133 | if instance.id: | |
1cc5e025 | 134 | q_ids = [j.question.id for j in instance.justificationautreemploye_set.filter(dossier=instance)] |
72db8238 OL |
135 | for q in dae.JustificationQuestion.objects.filter(type="R"): |
136 | if q.id in q_ids: | |
137 | continue | |
138 | j = dae.JustificationAutreEmploye() | |
139 | j.dossier = instance | |
140 | j.question = q | |
141 | j.save() | |
142 | super(self.__class__, self).__init__(*args, **kwargs) | |
5d680e84 | 143 | |
1b217058 | 144 | class PosteForm(forms.ModelForm): |
5d680e84 | 145 | """ Formulaire des postes. """ |
12c7f8a7 OL |
146 | |
147 | responsable=AutoCompleteSelectField('responsables', required=True) | |
148 | #responsable = forms.ModelChoiceField( | |
149 | # queryset=rh.Poste.objects.select_related(depth=1)) | |
150 | ||
151 | # La liste des choix est laissée vide. Voir __init__ pour la raison. | |
152 | poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste", | |
153 | choices=(), required=False) | |
154 | ||
155 | valeur_point_min = forms.ModelChoiceField(queryset=rh.ValeurPoint.actuelles.all(), required=False) | |
156 | valeur_point_max = forms.ModelChoiceField(queryset=rh.ValeurPoint.actuelles.all(), required=False) | |
3121c13c OL |
157 | |
158 | ||
5d680e84 NC |
159 | class Meta: |
160 | model = dae.Poste | |
b15bf543 | 161 | exclude = ('actif', ) |
5d680e84 | 162 | fields = ('poste', 'implantation', 'type_poste', 'service', 'nom', |
154677c3 | 163 | 'responsable', 'local', 'expatrie', 'mise_a_disposition', |
b15bf543 | 164 | 'appel', 'date_debut', 'date_fin', |
5d680e84 NC |
165 | 'regime_travail', 'regime_travail_nb_heure_semaine', |
166 | 'classement_min', 'classement_max', | |
167 | 'valeur_point_min', 'valeur_point_max', | |
3d627bfd | 168 | 'devise_min', 'devise_max', |
5d680e84 NC |
169 | 'salaire_min', 'salaire_max', 'indemn_min', 'indemn_max', |
170 | 'autre_min', 'autre_max', 'devise_comparaison', | |
171 | 'comp_locale_min', 'comp_locale_max', | |
172 | 'comp_universite_min', 'comp_universite_max', | |
173 | 'comp_fonctionpub_min', 'comp_fonctionpub_max', | |
174 | 'comp_ong_min', 'comp_ong_max', | |
8fa94e8b | 175 | 'comp_autre_min', 'comp_autre_max', |
2e092e0c | 176 | 'justification', |
8fa94e8b | 177 | ) |
154677c3 | 178 | widgets = dict(appel=forms.RadioSelect(), |
3d627bfd | 179 | nom=forms.TextInput(attrs={'size': 60},), |
e88caaf0 OL |
180 | date_debut=admin_widgets.AdminDateWidget(), |
181 | date_fin=admin_widgets.AdminDateWidget(), | |
2e092e0c | 182 | justification=forms.Textarea(attrs={'cols': 80},), |
3d627bfd | 183 | #devise_min=forms.Select(attrs={'disabled':'disabled'}), |
184 | #devise_max=forms.Select(attrs={'disabled':'disabled'}), | |
185 | ) | |
5d680e84 | 186 | |
c2458db6 | 187 | def __init__(self, *args, **kwargs): |
5d680e84 NC |
188 | """ Mise à jour dynamique du contenu du menu des postes. |
189 | ||
190 | Si on ne met le menu à jour de cette façon, à chaque instantiation du | |
191 | formulaire, son contenu est mis en cache par le système et il ne | |
192 | reflète pas les changements apportés par les ajouts, modifications, | |
193 | etc... | |
194 | ||
139686f2 NC |
195 | Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField |
196 | car le "id" de chaque choix est spécial (voir _poste_choices). | |
197 | ||
5d680e84 | 198 | """ |
c2458db6 | 199 | request = kwargs.pop('request') |
5d680e84 | 200 | super(PosteForm, self).__init__(*args, **kwargs) |
f258e4e7 OL |
201 | self.fields['poste'].choices = self._poste_choices(request) |
202 | self.fields['implantation'].choices = _implantation_choices(self, request) | |
5d680e84 | 203 | |
cc3098d0 OL |
204 | # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1 |
205 | if self.instance and self.instance.id is None: | |
206 | dossiers = self.instance.get_dossiers() | |
207 | if len(dossiers) > 0: | |
208 | self.initial['service'] = dossiers[0].service_id | |
9508a5b8 OL |
209 | self.initial['nom'] = "%s %s" % (self.initial['nom'], self.instance.get_complement_nom()) |
210 | ||
cc3098d0 | 211 | |
f258e4e7 | 212 | def _poste_choices(self, request): |
5d680e84 NC |
213 | """ Menu déroulant pour les postes. |
214 | ||
215 | Constitué des postes de dae et des postes de rh_v1 qui n'ont pas | |
216 | d'équivalent dans dae. | |
217 | ||
218 | """ | |
f258e4e7 OL |
219 | dae_ = dae.Poste.objects.ma_region_ou_service(request.user).filter(actif=True, id_rh__isnull=True) |
220 | copies = dae.Poste.objects.ma_region_ou_service(request.user).exclude(id_rh__isnull=True) | |
5d680e84 | 221 | id_copies = [p.id_rh_id for p in copies.all()] |
f258e4e7 | 222 | rhv1 = rh.Poste.objects.ma_region_ou_service(request.user).filter(actif=True).exclude(id__in=id_copies) |
139686f2 NC |
223 | # Optimisation de la requête |
224 | rhv1 = rhv1.select_related(depth=1) | |
5d680e84 | 225 | |
98d51b59 | 226 | return [('', 'Nouveau poste')] + \ |
4bce4d24 OL |
227 | sorted([('dae-%s' % p.id, label_poste_display(p)) for p in dae_ | copies] + |
228 | [('rh-%s' % p.id, label_poste_display(p)) for p in rhv1], | |
5d680e84 | 229 | key=lambda t: t[1]) |
3ed49093 | 230 | |
4dd75e7b OL |
231 | def clean(self): |
232 | """ | |
233 | Validation conditionnelles de certains champs. | |
234 | """ | |
235 | cleaned_data = self.cleaned_data | |
236 | ||
237 | # Gestion de la mise à disposition | |
238 | mise_a_disposition = cleaned_data.get("mise_a_disposition") | |
239 | valeur_point_min = cleaned_data.get("valeur_point_min") | |
240 | valeur_point_max = cleaned_data.get("valeur_point_max") | |
241 | if mise_a_disposition is False and (valeur_point_min is None or valeur_point_max is None): | |
242 | msg = u"Ce champ est obligatoire." | |
243 | self._errors["valeur_point_min"] = self.error_class([msg]) | |
244 | self._errors["valeur_point_max"] = self.error_class([msg]) | |
245 | raise forms.ValidationError("Les valeurs de point sont vides") | |
f42c6e20 OL |
246 | |
247 | print cleaned_data.get("local") | |
248 | if cleaned_data.get("local") is False and cleaned_data.get("expatrie") is False: | |
249 | msg = "Le poste doit au moins être ouvert localement ou aux expatriés" | |
250 | self._errors["local"] = self.error_class([msg]) | |
251 | self._errors["expatrie"] = '' | |
252 | raise forms.ValidationError(msg) | |
253 | ||
254 | ||
4dd75e7b OL |
255 | return cleaned_data |
256 | ||
257 | ||
258 | ||
494ff2be NC |
259 | def save(self, *args, **kwargs): |
260 | kwargs2 = kwargs.copy() | |
261 | kwargs2['commit'] = False | |
262 | poste = super(PosteForm, self).save(*args, **kwargs2) | |
263 | # id_rh | |
264 | if 'commit' not in kwargs or kwargs['commit']: | |
265 | poste.save() | |
266 | return poste | |
267 | ||
3ed49093 | 268 | |
139686f2 NC |
269 | class ChoosePosteForm(forms.ModelForm): |
270 | class Meta: | |
271 | model = dae.Poste | |
272 | fields = ('poste',) | |
273 | ||
274 | # La liste des choix est laissée vide. Voir PosteForm.__init__. | |
275 | poste = forms.ChoiceField(choices=(), required=False) | |
276 | ||
4ee6d70a | 277 | def __init__(self, request=None, *args, **kwargs): |
139686f2 | 278 | super(ChoosePosteForm, self).__init__(*args, **kwargs) |
4ee6d70a | 279 | self.fields['poste'].choices = self._poste_choices(request) |
139686f2 | 280 | |
4ee6d70a | 281 | def _poste_choices(self, request): |
139686f2 | 282 | """ Menu déroulant pour les postes. """ |
4ee6d70a OL |
283 | dae_ = dae.Poste.objects.ma_region_ou_service(request.user).filter(id_rh__isnull=True) |
284 | copies = dae.Poste.objects.ma_region_ou_service(request.user).exclude(id_rh__isnull=True) | |
139686f2 NC |
285 | id_copies = [p.id_rh_id for p in copies.all()] |
286 | ||
98d51b59 | 287 | return [('', '----------')] + \ |
139686f2 NC |
288 | sorted([('dae-%s' % p.id, unicode(p)) for p in dae_ | copies], |
289 | key=lambda t: t[1]) | |
290 | ||
291 | ||
139686f2 NC |
292 | class EmployeForm(forms.ModelForm): |
293 | """ Formulaire des employés. """ | |
294 | class Meta: | |
295 | model = dae.Employe | |
296 | fields = ('employe', 'nom', 'prenom', 'genre') | |
297 | ||
298 | # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison. | |
299 | employe = forms.ChoiceField(choices=(), required=False) | |
300 | ||
ac6235f6 | 301 | def __init__(self, *args, **kwargs): |
139686f2 | 302 | """ Mise à jour dynamique du contenu du menu des employés. """ |
ac6235f6 | 303 | request = kwargs.pop('request', None) |
139686f2 | 304 | super(EmployeForm, self).__init__(*args, **kwargs) |
f258e4e7 | 305 | self.fields['employe'].choices = _employe_choices(self, request) |
139686f2 | 306 | |
139686f2 NC |
307 | |
308 | ||
309 | class DossierForm(forms.ModelForm): | |
310 | """ Formulaire des dossiers. """ | |
311 | class Meta: | |
b1baa306 | 312 | exclude= ('etat', ) |
139686f2 | 313 | model = dae.Dossier |
4d25e2ba | 314 | widgets = dict(statut_residence=forms.RadioSelect(), |
0e0aeb7e OL |
315 | contrat_date_debut=admin_widgets.AdminDateWidget(), |
316 | contrat_date_fin=admin_widgets.AdminDateWidget(), | |
4d25e2ba | 317 | ) |
e6f52402 | 318 | |
e0b93e3a OL |
319 | WF_HELP_TEXT = """Ce champs affiche par défaut l'étape de traitement de la demande. |
320 | La liste déroulante vous permet de sélectionner l'étape suivante.""" | |
321 | ||
e6f52402 | 322 | class PosteWorkflowForm(WorkflowFormMixin): |
e0b93e3a | 323 | |
e6f52402 OL |
324 | class Meta: |
325 | fields = ('etat', ) | |
326 | model = dae.Poste | |
327 | ||
e0b93e3a OL |
328 | def __init__(self, *args, **kwargs): |
329 | super(self.__class__, self).__init__(*args, **kwargs) | |
330 | self.fields['etat'].help_text = WF_HELP_TEXT | |
331 | ||
332 | ||
e6f52402 OL |
333 | class DossierWorkflowForm(WorkflowFormMixin): |
334 | ||
335 | class Meta: | |
336 | fields = ('etat', ) | |
337 | model = dae.Dossier | |
e0b93e3a OL |
338 | |
339 | def __init__(self, *args, **kwargs): | |
340 | super(self.__class__, self).__init__(*args, **kwargs) | |
341 | self.fields['etat'].help_text = WF_HELP_TEXT | |
342 |