Commit | Line | Data |
---|---|---|
5d680e84 | 1 | # -*- encoding: utf-8 -*- |
ce110fb9 | 2 | |
aa512122 | 3 | import datetime |
5a1f75cb | 4 | |
8fa94e8b | 5 | from auf.django.workflow.forms import WorkflowFormMixin |
bf6f2712 | 6 | from auf.django.references import models as ref |
5a1f75cb EMS |
7 | from ajax_select.fields import AutoCompleteSelectField |
8 | from django import forms | |
9 | from django.contrib.admin import widgets as admin_widgets | |
10 | from django.db.models import Q, Max | |
11 | from django.forms.models import inlineformset_factory, modelformset_factory | |
12 | ||
13 | from project.dae import models as dae | |
14 | from project.dae.utils import \ | |
15 | get_employe_from_user, is_user_dans_services_centraux | |
16 | from project.rh import models as rh | |
17 | from project.dae.workflow import \ | |
18 | grp_drh, POSTE_ETATS_BOUTONS, DOSSIER_ETAT_FINALISE, \ | |
19 | POSTE_ETAT_FINALISE | |
20 | ||
f258e4e7 OL |
21 | |
22 | def _implantation_choices(obj, request): | |
23 | # TRAITEMENT NORMAL | |
24 | employe = get_employe_from_user(request.user) | |
25 | # SERVICE | |
d8cfc3d5 | 26 | if is_user_dans_services_centraux(request.user): |
5a1f75cb | 27 | q = Q(**{'id': employe.implantation_id}) |
f258e4e7 OL |
28 | # REGION |
29 | else: | |
5a1f75cb | 30 | q = Q(**{'region': employe.implantation.region}) |
f258e4e7 OL |
31 | |
32 | # TRAITEMENT DRH | |
33 | if grp_drh in request.user.groups.all(): | |
34 | q = Q() | |
5a1f75cb EMS |
35 | return [('', '----------')] + \ |
36 | [(i.id, unicode(i), )for i in ref.Implantation.objects.filter(q)] | |
37 | ||
f258e4e7 OL |
38 | |
39 | def _employe_choices(obj, request): | |
f258e4e7 OL |
40 | # TRAITEMENT NORMAL |
41 | employe = get_employe_from_user(request.user) | |
42 | # SERVICE | |
d8cfc3d5 | 43 | if is_user_dans_services_centraux(request.user): |
072820fc | 44 | q_dae_region_service = Q(poste__implantation=employe.implantation) |
09aa8374 | 45 | q_rh_region_service = Q(poste__implantation=employe.implantation) |
f258e4e7 OL |
46 | # REGION |
47 | else: | |
5a1f75cb EMS |
48 | q_dae_region_service = Q( |
49 | poste__implantation__region=employe.implantation.region | |
50 | ) | |
51 | q_rh_region_service = Q( | |
52 | poste__implantation__region=employe.implantation.region | |
53 | ) | |
f258e4e7 OL |
54 | # TRAITEMENT DRH |
55 | if grp_drh in request.user.groups.all(): | |
072820fc OL |
56 | q_dae_region_service = Q() |
57 | q_rh_region_service = Q() | |
f258e4e7 | 58 | |
5a1f75cb EMS |
59 | # On filtre les employes avec les droits régionaux et on s'assure que |
60 | # c'est bien le dernier dossier en date pour sortir l'employe. On retient | |
61 | # un employé qui travaille présentement dans la même région que le user | |
62 | # connecté. | |
63 | dossiers_regionaux_ids = [ | |
64 | d.id for d in dae.Dossier.objects.filter(q_dae_region_service) | |
65 | ] | |
66 | employes_ids = [ | |
67 | d['employe'] | |
68 | for d in dae.Dossier.objects | |
69 | .values('employe') | |
70 | .annotate(dernier_dossier=Max('id')) | |
71 | if d['dernier_dossier'] in dossiers_regionaux_ids | |
72 | ] | |
072820fc OL |
73 | dae_employe = dae.Employe.objects.filter(id__in=employes_ids) |
74 | dae_ = dae_employe.filter(id_rh__isnull=True) | |
75 | copies = dae_employe.filter(Q(id_rh__isnull=False)) | |
f258e4e7 | 76 | id_copies = [p.id_rh_id for p in copies.all()] |
072820fc | 77 | |
5a1f75cb EMS |
78 | dossiers_regionaux_ids = [ |
79 | d.id for d in rh.Dossier.objects.filter(q_rh_region_service) | |
80 | ] | |
81 | employes_ids = [ | |
82 | d['employe'] | |
83 | for d in rh.Dossier.objects | |
84 | .values('employe') | |
85 | .annotate(dernier_dossier=Max('id')) | |
86 | if d['dernier_dossier'] in dossiers_regionaux_ids | |
87 | ] | |
88 | rhv1 = rh.Employe.objects \ | |
89 | .filter(id__in=employes_ids) \ | |
90 | .exclude(id__in=id_copies) | |
91 | ||
92 | # On ajoute les nouveaux Employés DAE qui ont été crées, mais qui n'ont | |
93 | # pas de Dossier associés | |
67c15007 OL |
94 | employes_avec_dae = [d.employe_id for d in dae.Dossier.objects.all()] |
95 | employes_orphelins = dae.Employe.objects.exclude(id__in=employes_avec_dae) | |
96 | ||
f258e4e7 OL |
97 | def option_label(employe): |
98 | return "%s %s" % (employe.nom.upper(), employe.prenom.title()) | |
99 | ||
100 | return [('', 'Nouvel employé')] + \ | |
5a1f75cb EMS |
101 | sorted( |
102 | [('dae-%s' % p.id, option_label(p)) | |
103 | for p in dae_ | copies | employes_orphelins] + | |
104 | [('rh-%s' % p.id, option_label(p)) for p in rhv1], | |
105 | key=lambda t: t[1] | |
106 | ) | |
107 | ||
f258e4e7 | 108 | |
4bce4d24 OL |
109 | def label_poste_display(poste): |
110 | """Formate un visuel pour un poste dans une liste déroulante""" | |
23294f7d OL |
111 | annee = "" |
112 | if poste.date_debut: | |
113 | annee = poste.date_debut.year | |
5a1f75cb | 114 | label = u"%s %s - %s [%s]" % ( |
7bf28694 | 115 | annee, poste.type_poste, poste.type_poste.categorie_emploi.nom, |
5a1f75cb EMS |
116 | poste.id |
117 | ) | |
4bce4d24 | 118 | return label |
9cb4de55 | 119 | |
25086dcf EMS |
120 | PostePieceForm = inlineformset_factory(dae.Poste, dae.PostePiece) |
121 | DossierPieceForm = inlineformset_factory(dae.Dossier, dae.DossierPiece) | |
5a1f75cb EMS |
122 | FinancementForm = inlineformset_factory( |
123 | dae.Poste, dae.PosteFinancement, extra=2 | |
124 | ) | |
125 | ||
03b395db OL |
126 | |
127 | class DossierComparaisonForm(forms.ModelForm): | |
11f22317 | 128 | |
03b395db | 129 | recherche = AutoCompleteSelectField('dossiers', required=False) |
5a1f75cb EMS |
130 | poste = forms.CharField( |
131 | max_length=255, widget=forms.TextInput(attrs={'size': '60'}) | |
132 | ) | |
03b395db | 133 | |
320d7584 | 134 | class Meta: |
03b395db | 135 | model = dae.DossierComparaison |
320d7584 | 136 | exclude = ('dossier',) |
03b395db | 137 | |
320d7584 EMS |
138 | DossierComparaisonFormSet = modelformset_factory( |
139 | dae.DossierComparaison, extra=3, max_num=3, form=DossierComparaisonForm | |
25086dcf | 140 | ) |
03b395db | 141 | |
5a1f75cb | 142 | |
068d1462 | 143 | class PosteComparaisonForm(forms.ModelForm): |
11f22317 | 144 | |
e503e64d | 145 | recherche = AutoCompleteSelectField('dae_postes', required=False) |
068d1462 | 146 | |
320d7584 | 147 | class Meta: |
068d1462 | 148 | model = dae.PosteComparaison |
320d7584 | 149 | exclude = ('poste',) |
068d1462 | 150 | |
320d7584 EMS |
151 | PosteComparaisonFormSet = modelformset_factory( |
152 | dae.PosteComparaison, extra=3, max_num=3, form=PosteComparaisonForm | |
25086dcf | 153 | ) |
068d1462 | 154 | |
5a1f75cb | 155 | |
0a085c42 OL |
156 | class FlexibleRemunForm(forms.ModelForm): |
157 | ||
158 | montant_mensuel = forms.DecimalField(required=False) | |
159 | montant = forms.DecimalField(required=True, label='Montant annuel') | |
160 | ||
161 | class Meta: | |
162 | model = dae.Remuneration | |
163 | ||
dc4b78a7 OL |
164 | def clean_devise(self): |
165 | devise = self.cleaned_data['devise'] | |
67173010 OL |
166 | if devise.code == 'EUR': |
167 | return devise | |
5a1f75cb EMS |
168 | implantation = ref.Implantation.objects.get( |
169 | id=self.data['implantation'] | |
170 | ) | |
2455f48d | 171 | liste_taux = devise.tauxchange_set.order_by('-annee') |
dc4b78a7 | 172 | if len(liste_taux) == 0: |
5a1f75cb EMS |
173 | raise forms.ValidationError( |
174 | u"La devise %s n'a pas de taux pour l'implantation %s" % | |
175 | (devise, implantation) | |
176 | ) | |
dc4b78a7 OL |
177 | else: |
178 | return devise | |
179 | ||
25086dcf EMS |
180 | RemunForm = inlineformset_factory( |
181 | dae.Dossier, dae.Remuneration, extra=5, form=FlexibleRemunForm | |
182 | ) | |
0a085c42 | 183 | |
5a1f75cb | 184 | |
1b217058 | 185 | class PosteForm(forms.ModelForm): |
5d680e84 | 186 | """ Formulaire des postes. """ |
12c7f8a7 | 187 | |
ea7adc69 | 188 | # On ne propose que les services actifs |
5a1f75cb EMS |
189 | service = forms.ModelChoiceField( |
190 | queryset=rh.Service.objects.all(), required=True | |
191 | ) | |
ea7adc69 | 192 | |
5a1f75cb | 193 | responsable = AutoCompleteSelectField('responsables', required=True) |
12c7f8a7 OL |
194 | #responsable = forms.ModelChoiceField( |
195 | # queryset=rh.Poste.objects.select_related(depth=1)) | |
196 | ||
197 | # La liste des choix est laissée vide. Voir __init__ pour la raison. | |
198 | poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste", | |
199 | choices=(), required=False) | |
11f22317 | 200 | |
5a1f75cb EMS |
201 | valeur_point_min = forms.ModelChoiceField( |
202 | queryset=rh.ValeurPoint.actuelles.all(), required=False | |
203 | ) | |
204 | valeur_point_max = forms.ModelChoiceField( | |
205 | queryset=rh.ValeurPoint.actuelles.all(), required=False | |
206 | ) | |
11f22317 | 207 | |
5d680e84 NC |
208 | class Meta: |
209 | model = dae.Poste | |
c3be904d OL |
210 | fields = ('type_intervention', |
211 | 'poste', 'implantation', 'type_poste', 'service', 'nom', | |
154677c3 | 212 | 'responsable', 'local', 'expatrie', 'mise_a_disposition', |
b15bf543 | 213 | 'appel', 'date_debut', 'date_fin', |
5d680e84 NC |
214 | 'regime_travail', 'regime_travail_nb_heure_semaine', |
215 | 'classement_min', 'classement_max', | |
216 | 'valeur_point_min', 'valeur_point_max', | |
3d627bfd | 217 | 'devise_min', 'devise_max', |
5f61bccb OL |
218 | 'salaire_min', 'salaire_max', |
219 | 'indemn_expat_min', 'indemn_expat_max', | |
220 | 'indemn_fct_min', 'indemn_fct_max', | |
221 | 'charges_patronales_min', 'charges_patronales_max', | |
5d680e84 NC |
222 | 'autre_min', 'autre_max', 'devise_comparaison', |
223 | 'comp_locale_min', 'comp_locale_max', | |
224 | 'comp_universite_min', 'comp_universite_max', | |
225 | 'comp_fonctionpub_min', 'comp_fonctionpub_max', | |
226 | 'comp_ong_min', 'comp_ong_max', | |
8fa94e8b | 227 | 'comp_autre_min', 'comp_autre_max', |
2e092e0c | 228 | 'justification', |
8fa94e8b | 229 | ) |
c3be904d OL |
230 | widgets = dict(type_intervention=forms.RadioSelect(), |
231 | appel=forms.RadioSelect(), | |
3d627bfd | 232 | nom=forms.TextInput(attrs={'size': 60},), |
e88caaf0 OL |
233 | date_debut=admin_widgets.AdminDateWidget(), |
234 | date_fin=admin_widgets.AdminDateWidget(), | |
2e092e0c | 235 | justification=forms.Textarea(attrs={'cols': 80},), |
3d627bfd | 236 | #devise_min=forms.Select(attrs={'disabled':'disabled'}), |
237 | #devise_max=forms.Select(attrs={'disabled':'disabled'}), | |
238 | ) | |
5d680e84 | 239 | |
c2458db6 | 240 | def __init__(self, *args, **kwargs): |
5d680e84 NC |
241 | """ Mise à jour dynamique du contenu du menu des postes. |
242 | ||
243 | Si on ne met le menu à jour de cette façon, à chaque instantiation du | |
244 | formulaire, son contenu est mis en cache par le système et il ne | |
245 | reflète pas les changements apportés par les ajouts, modifications, | |
246 | etc... | |
247 | ||
139686f2 NC |
248 | Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField |
249 | car le "id" de chaque choix est spécial (voir _poste_choices). | |
250 | ||
5d680e84 | 251 | """ |
c2458db6 | 252 | request = kwargs.pop('request') |
5d680e84 | 253 | super(PosteForm, self).__init__(*args, **kwargs) |
f258e4e7 | 254 | self.fields['poste'].choices = self._poste_choices(request) |
5a1f75cb EMS |
255 | self.fields['implantation'].choices = \ |
256 | _implantation_choices(self, request) | |
5d680e84 | 257 | |
cc3098d0 OL |
258 | # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1 |
259 | if self.instance and self.instance.id is None: | |
260 | dossiers = self.instance.get_dossiers() | |
261 | if len(dossiers) > 0: | |
09aa8374 | 262 | self.initial['service'] = dossiers[0].poste.service |
9508a5b8 | 263 | |
f258e4e7 | 264 | def _poste_choices(self, request): |
5d680e84 NC |
265 | """ Menu déroulant pour les postes. |
266 | ||
267 | Constitué des postes de dae et des postes de rh_v1 qui n'ont pas | |
268 | d'équivalent dans dae. | |
269 | ||
270 | """ | |
5a1f75cb EMS |
271 | copies = dae.Poste.objects \ |
272 | .ma_region_ou_service(request.user) \ | |
273 | .exclude(id_rh__isnull=True) \ | |
274 | .filter(etat=POSTE_ETAT_FINALISE) | |
5d680e84 | 275 | id_copies = [p.id_rh_id for p in copies.all()] |
5a1f75cb EMS |
276 | rhv1 = rh.Poste.objects.ma_region_ou_service(request.user) \ |
277 | .exclude(id__in=id_copies) | |
139686f2 NC |
278 | # Optimisation de la requête |
279 | rhv1 = rhv1.select_related(depth=1) | |
5d680e84 | 280 | |
98d51b59 | 281 | return [('', 'Nouveau poste')] + \ |
aa512122 | 282 | sorted([('rh-%s' % p.id, label_poste_display(p)) for p in rhv1], |
5d680e84 | 283 | key=lambda t: t[1]) |
3ed49093 | 284 | |
4dd75e7b OL |
285 | def clean(self): |
286 | """ | |
287 | Validation conditionnelles de certains champs. | |
288 | """ | |
5a1f75cb | 289 | cleaned_data = self.cleaned_data |
4dd75e7b | 290 | |
5a1f75cb EMS |
291 | if cleaned_data.get("local") is False \ |
292 | and cleaned_data.get("expatrie") is False: | |
293 | msg = "Le poste doit au moins être ouvert localement " \ | |
294 | "ou aux expatriés" | |
f42c6e20 OL |
295 | self._errors["local"] = self.error_class([msg]) |
296 | self._errors["expatrie"] = '' | |
297 | raise forms.ValidationError(msg) | |
f42c6e20 | 298 | |
4dd75e7b OL |
299 | return cleaned_data |
300 | ||
494ff2be NC |
301 | def save(self, *args, **kwargs): |
302 | kwargs2 = kwargs.copy() | |
303 | kwargs2['commit'] = False | |
304 | poste = super(PosteForm, self).save(*args, **kwargs2) | |
305 | # id_rh | |
306 | if 'commit' not in kwargs or kwargs['commit']: | |
aa512122 OL |
307 | if poste.id is None: |
308 | poste.date_creation = datetime.datetime.now() | |
494ff2be NC |
309 | poste.save() |
310 | return poste | |
311 | ||
3ed49093 | 312 | |
139686f2 NC |
313 | class ChoosePosteForm(forms.ModelForm): |
314 | class Meta: | |
315 | model = dae.Poste | |
316 | fields = ('poste',) | |
317 | ||
318 | # La liste des choix est laissée vide. Voir PosteForm.__init__. | |
319 | poste = forms.ChoiceField(choices=(), required=False) | |
320 | ||
4ee6d70a | 321 | def __init__(self, request=None, *args, **kwargs): |
139686f2 | 322 | super(ChoosePosteForm, self).__init__(*args, **kwargs) |
4ee6d70a | 323 | self.fields['poste'].choices = self._poste_choices(request) |
139686f2 | 324 | |
4ee6d70a | 325 | def _poste_choices(self, request): |
139686f2 | 326 | """ Menu déroulant pour les postes. """ |
5a1f75cb EMS |
327 | dae_ = dae.Poste.objects.ma_region_ou_service(request.user) \ |
328 | .filter(id_rh__isnull=True) | |
329 | copies = dae.Poste.objects.ma_region_ou_service(request.user) \ | |
330 | .exclude(id_rh__isnull=True) | |
139686f2 | 331 | |
98d51b59 | 332 | return [('', '----------')] + \ |
139686f2 NC |
333 | sorted([('dae-%s' % p.id, unicode(p)) for p in dae_ | copies], |
334 | key=lambda t: t[1]) | |
335 | ||
336 | ||
139686f2 NC |
337 | class EmployeForm(forms.ModelForm): |
338 | """ Formulaire des employés. """ | |
339 | class Meta: | |
340 | model = dae.Employe | |
341 | fields = ('employe', 'nom', 'prenom', 'genre') | |
342 | ||
343 | # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison. | |
344 | employe = forms.ChoiceField(choices=(), required=False) | |
345 | ||
ac6235f6 | 346 | def __init__(self, *args, **kwargs): |
139686f2 | 347 | """ Mise à jour dynamique du contenu du menu des employés. """ |
ac6235f6 | 348 | request = kwargs.pop('request', None) |
139686f2 | 349 | super(EmployeForm, self).__init__(*args, **kwargs) |
f258e4e7 | 350 | self.fields['employe'].choices = _employe_choices(self, request) |
139686f2 | 351 | |
139686f2 | 352 | |
139686f2 NC |
353 | class DossierForm(forms.ModelForm): |
354 | """ Formulaire des dossiers. """ | |
355 | class Meta: | |
5a1f75cb | 356 | exclude = ('etat', 'employe', 'poste', 'date_debut',) |
139686f2 | 357 | model = dae.Dossier |
4d25e2ba | 358 | widgets = dict(statut_residence=forms.RadioSelect(), |
0e0aeb7e OL |
359 | contrat_date_debut=admin_widgets.AdminDateWidget(), |
360 | contrat_date_fin=admin_widgets.AdminDateWidget(), | |
4d25e2ba | 361 | ) |
e6f52402 | 362 | |
aa512122 | 363 | def save(self, *args, **kwargs): |
38112342 | 364 | dossier = super(DossierForm, self).save(*args, **kwargs) |
aa512122 OL |
365 | if dossier.id is None: |
366 | dossier.date_creation = datetime.datetime.now() | |
367 | dossier.save() | |
368 | return dossier | |
369 | ||
3799cafc | 370 | WF_HELP_TEXT = "" |
e0b93e3a | 371 | |
5a1f75cb | 372 | |
e6f52402 | 373 | class PosteWorkflowForm(WorkflowFormMixin): |
56589624 | 374 | bouton_libelles = POSTE_ETATS_BOUTONS |
5a1f75cb | 375 | |
e6f52402 OL |
376 | class Meta: |
377 | fields = ('etat', ) | |
378 | model = dae.Poste | |
9536ea21 | 379 | |
e0b93e3a | 380 | def __init__(self, *args, **kwargs): |
e54b7d5d | 381 | super(PosteWorkflowForm, self).__init__(*args, **kwargs) |
e0b93e3a OL |
382 | self.fields['etat'].help_text = WF_HELP_TEXT |
383 | ||
384 | ||
e6f52402 | 385 | class DossierWorkflowForm(WorkflowFormMixin): |
5a1f75cb EMS |
386 | bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste... |
387 | ||
e6f52402 | 388 | class Meta: |
9e40cfbe | 389 | fields = ('etat', ) |
e6f52402 | 390 | model = dae.Dossier |
e0b93e3a OL |
391 | |
392 | def __init__(self, *args, **kwargs): | |
e54b7d5d | 393 | super(DossierWorkflowForm, self).__init__(*args, **kwargs) |
e0b93e3a | 394 | self.fields['etat'].help_text = WF_HELP_TEXT |
e54b7d5d | 395 | self._etat_initial = self.instance.etat |
e0b93e3a | 396 | |
e54b7d5d EMS |
397 | def save(self): |
398 | super(DossierWorkflowForm, self).save() | |
399 | poste = self.instance.poste | |
400 | if poste.etat == self._etat_initial: | |
401 | poste.etat = self.instance.etat | |
402 | poste.save() | |
9536ea21 | 403 | |
5a1f75cb | 404 | |
9536ea21 EMS |
405 | class ContratForm(forms.ModelForm): |
406 | ||
407 | class Meta: | |
9dfa4296 | 408 | fields = ('type_contrat', 'fichier', ) |
9536ea21 EMS |
409 | model = dae.Contrat |
410 | ||
5a1f75cb | 411 | |
c3f0b49f EMS |
412 | class DAENumeriseeForm(forms.ModelForm): |
413 | ||
414 | class Meta: | |
415 | model = dae.Dossier | |
416 | fields = ('dae_numerisee',) | |
cbfd7bd4 EMS |
417 | |
418 | ||
419 | class DAEFinaliseesSearchForm(forms.Form): | |
420 | q = forms.CharField( | |
421 | label='Recherche', required=False, | |
422 | widget=forms.TextInput(attrs={'size': 40}) | |
423 | ) | |
424 | importees = forms.ChoiceField( | |
425 | label='Importation', required=False, choices=( | |
426 | ('', ''), | |
427 | ('oui', 'DAE importées seulement'), | |
428 | ('non', 'DAE non-importées seulement'), | |
429 | ) | |
430 | ) |