Commit | Line | Data |
---|---|---|
5d680e84 | 1 | # -*- encoding: utf-8 -*- |
ce110fb9 | 2 | |
bed0c4c9 | 3 | import datetime |
6bec5651 | 4 | from ordereddict import OrderedDict |
b9098c33 | 5 | from dateutil.relativedelta import relativedelta |
5a1f75cb | 6 | from django import forms |
80be36aa | 7 | from django.core.urlresolvers import reverse |
661da766 | 8 | from django.core.exceptions import MultipleObjectsReturned |
2e672700 | 9 | from django.forms.models import BaseInlineFormSet |
4718c21c BS |
10 | from django.forms.models import ( |
11 | inlineformset_factory, | |
12 | modelformset_factory, | |
13 | _get_foreign_key, | |
14 | ) | |
80be36aa OL |
15 | from django.db.models import Q, Max, Count |
16 | from django.shortcuts import redirect | |
17 | from django.contrib.admin import widgets as admin_widgets | |
5a1f75cb | 18 | |
75f0e87b DB |
19 | from ajax_select.fields import AutoCompleteSelectField |
20 | ||
21 | from auf.django.references import models as ref | |
22 | from auf.django.workflow.forms import WorkflowFormMixin | |
66fefd2f | 23 | from auf.django.workflow.models import WorkflowCommentaire |
75f0e87b | 24 | |
3383b2d1 | 25 | from project import groups |
17c90428 | 26 | from project.rh import models as rh |
17c90428 | 27 | from project.dae import models as dae |
661da766 | 28 | from .widgets import ReadOnlyChoiceWidget, ReadOnlyWidget |
34950f36 | 29 | from project.dae.workflow import POSTE_ETATS_BOUTONS, POSTE_ETAT_FINALISE |
1b31de9f | 30 | |
f258e4e7 | 31 | |
f40a4829 BS |
32 | def filtered_archived_fields_form_factory(*fields): |
33 | """ | |
34 | Retourne un model form | |
35 | """ | |
36 | class FilterArchivedFields(object): | |
37 | def __init__(self, *a, **kw): | |
38 | super(FilterArchivedFields, self).__init__(*a, **kw) | |
39 | for f in fields: | |
40 | self.fields[f].queryset = ( | |
41 | self.fields[f]._queryset.filter(archive=False) | |
42 | ) | |
43 | return FilterArchivedFields | |
44 | ||
45 | ||
2e672700 OL |
46 | class BaseInlineFormSetWithInitial(BaseInlineFormSet): |
47 | """ | |
48 | Cette classe permet de fournir l'option initial aux inlineformsets. | |
49 | Elle devient désuette en django 1.4. | |
50 | """ | |
51 | def __init__(self, data=None, files=None, instance=None, | |
52 | save_as_new=False, prefix=None, queryset=None, **kwargs): | |
53 | ||
54 | self.initial_extra = kwargs.pop('initial', None) | |
55 | ||
56 | from django.db.models.fields.related import RelatedObject | |
57 | if instance is None: | |
58 | self.instance = self.fk.rel.to() | |
59 | else: | |
60 | self.instance = instance | |
61 | self.save_as_new = save_as_new | |
62 | # is there a better way to get the object descriptor? | |
63 | self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name() | |
64 | if queryset is None: | |
65 | queryset = self.model._default_manager | |
66 | qs = queryset.filter(**{self.fk.name: self.instance}) | |
67 | super(BaseInlineFormSetWithInitial, self).__init__(data, files, prefix=prefix, | |
68 | queryset=qs, **kwargs) | |
69 | ||
70 | def _construct_form(self, i, **kwargs): | |
71 | if self.is_bound and i < self.initial_form_count(): | |
72 | # Import goes here instead of module-level because importing | |
73 | # django.db has side effects. | |
74 | from django.db import connections | |
75 | pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name) | |
76 | pk = self.data[pk_key] | |
77 | pk_field = self.model._meta.pk | |
78 | pk = pk_field.get_db_prep_lookup('exact', pk, | |
79 | connection=connections[self.get_queryset().db]) | |
80 | if isinstance(pk, list): | |
81 | pk = pk[0] | |
82 | kwargs['instance'] = self._existing_object(pk) | |
83 | if i < self.initial_form_count() and not kwargs.get('instance'): | |
84 | kwargs['instance'] = self.get_queryset()[i] | |
85 | if i >= self.initial_form_count() and self.initial_extra: | |
86 | # Set initial values for extra forms | |
87 | try: | |
88 | kwargs['initial'] = self.initial_extra[i-self.initial_form_count()] | |
89 | except IndexError: | |
90 | pass | |
91 | ||
92 | defaults = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)} | |
93 | if self.is_bound: | |
94 | defaults['data'] = self.data | |
95 | defaults['files'] = self.files | |
96 | if self.initial: | |
97 | try: | |
98 | defaults['initial'] = self.initial[i] | |
99 | except IndexError: | |
100 | pass | |
101 | # Allow extra forms to be empty. | |
102 | if i >= self.initial_form_count(): | |
103 | defaults['empty_permitted'] = True | |
104 | defaults.update(kwargs) | |
105 | form = self.form(**defaults) | |
106 | self.add_fields(form, i) | |
107 | return form | |
108 | ||
109 | ||
f258e4e7 OL |
110 | def _implantation_choices(obj, request): |
111 | # TRAITEMENT NORMAL | |
3383b2d1 | 112 | employe = groups.get_employe_from_user(request.user) |
01971ac9 | 113 | q = Q(**{'zone_administrative__in': groups.get_zones_from_user(request.user)}) |
f258e4e7 OL |
114 | |
115 | # TRAITEMENT DRH | |
3383b2d1 | 116 | user_groupes = [g.name for g in request.user.groups.all()] |
713b824a OL |
117 | if groups.DRH_NIVEAU_1 in user_groupes or \ |
118 | groups.DRH_NIVEAU_2 in user_groupes: | |
f258e4e7 | 119 | q = Q() |
5a1f75cb EMS |
120 | return [('', '----------')] + \ |
121 | [(i.id, unicode(i), )for i in ref.Implantation.objects.filter(q)] | |
122 | ||
f258e4e7 OL |
123 | |
124 | def _employe_choices(obj, request): | |
f258e4e7 | 125 | # TRAITEMENT NORMAL |
3383b2d1 | 126 | employe = groups.get_employe_from_user(request.user) |
b0cf30b8 | 127 | q_dae_region_service = Q( |
01971ac9 BS |
128 | poste__implantation__zone_administrative__in=( |
129 | groups.get_zones_from_user(request.user) | |
5a1f75cb | 130 | ) |
b0cf30b8 EMS |
131 | ) |
132 | q_rh_region_service = Q( | |
01971ac9 BS |
133 | poste__implantation__zone_administrative__in=( |
134 | groups.get_zones_from_user(request.user) | |
5a1f75cb | 135 | ) |
b0cf30b8 | 136 | ) |
f258e4e7 | 137 | # TRAITEMENT DRH |
3383b2d1 | 138 | user_groupes = [g.name for g in request.user.groups.all()] |
713b824a OL |
139 | if groups.DRH_NIVEAU_1 in user_groupes or \ |
140 | groups.DRH_NIVEAU_2 in user_groupes: | |
072820fc OL |
141 | q_dae_region_service = Q() |
142 | q_rh_region_service = Q() | |
f258e4e7 | 143 | |
5a1f75cb EMS |
144 | # On filtre les employes avec les droits régionaux et on s'assure que |
145 | # c'est bien le dernier dossier en date pour sortir l'employe. On retient | |
146 | # un employé qui travaille présentement dans la même région que le user | |
147 | # connecté. | |
148 | dossiers_regionaux_ids = [ | |
149 | d.id for d in dae.Dossier.objects.filter(q_dae_region_service) | |
150 | ] | |
151 | employes_ids = [ | |
152 | d['employe'] | |
153 | for d in dae.Dossier.objects | |
154 | .values('employe') | |
155 | .annotate(dernier_dossier=Max('id')) | |
156 | if d['dernier_dossier'] in dossiers_regionaux_ids | |
157 | ] | |
072820fc OL |
158 | dae_employe = dae.Employe.objects.filter(id__in=employes_ids) |
159 | dae_ = dae_employe.filter(id_rh__isnull=True) | |
160 | copies = dae_employe.filter(Q(id_rh__isnull=False)) | |
f258e4e7 | 161 | id_copies = [p.id_rh_id for p in copies.all()] |
072820fc | 162 | |
5a1f75cb EMS |
163 | dossiers_regionaux_ids = [ |
164 | d.id for d in rh.Dossier.objects.filter(q_rh_region_service) | |
165 | ] | |
166 | employes_ids = [ | |
167 | d['employe'] | |
168 | for d in rh.Dossier.objects | |
169 | .values('employe') | |
170 | .annotate(dernier_dossier=Max('id')) | |
171 | if d['dernier_dossier'] in dossiers_regionaux_ids | |
172 | ] | |
173 | rhv1 = rh.Employe.objects \ | |
174 | .filter(id__in=employes_ids) \ | |
175 | .exclude(id__in=id_copies) | |
176 | ||
177 | # On ajoute les nouveaux Employés DAE qui ont été crées, mais qui n'ont | |
178 | # pas de Dossier associés | |
67c15007 OL |
179 | employes_avec_dae = [d.employe_id for d in dae.Dossier.objects.all()] |
180 | employes_orphelins = dae.Employe.objects.exclude(id__in=employes_avec_dae) | |
181 | ||
0339920c OL |
182 | def option_label(employe, extra=""): |
183 | if extra: | |
184 | extra = " [%s]" % extra | |
185 | return "%s %s %s" % (employe.nom.upper(), employe.prenom.title(), extra) | |
f258e4e7 | 186 | |
0339920c OL |
187 | lbl_rh = sorted([('rh-%s' % p.id, option_label(p, "existant dans rh")) for p in rhv1], |
188 | key=lambda t: t[1]) | |
189 | lbl_dae = sorted([('dae-%s' % p.id, option_label(p)) for p in dae_ | copies | employes_orphelins], | |
190 | key=lambda t: t[1]) | |
191 | return [('', 'Nouvel employé')] + lbl_rh + lbl_dae | |
5a1f75cb | 192 | |
f258e4e7 | 193 | |
4bce4d24 OL |
194 | def label_poste_display(poste): |
195 | """Formate un visuel pour un poste dans une liste déroulante""" | |
23294f7d OL |
196 | annee = "" |
197 | if poste.date_debut: | |
198 | annee = poste.date_debut.year | |
9c1ff333 OL |
199 | |
200 | nom = poste.nom | |
67ae0181 | 201 | label = u"%s (%s) %s [%s]" % ( |
34950f36 OL |
202 | annee, |
203 | poste.implantation.nom_court, | |
204 | nom, | |
67ae0181 | 205 | #poste.type_poste.categorie_emploi.nom, |
34950f36 | 206 | poste.id, |
93817ef3 | 207 | ) |
4bce4d24 | 208 | return label |
9cb4de55 | 209 | |
2e672700 | 210 | |
874949f3 | 211 | PostePieceFormSet = inlineformset_factory(dae.Poste, dae.PostePiece,) |
25086dcf | 212 | DossierPieceForm = inlineformset_factory(dae.Dossier, dae.DossierPiece) |
2e672700 | 213 | |
874949f3 OL |
214 | # Ce formset est utilisé dans le cas de la création de poste prépopulé avec les |
215 | # données de RH | |
216 | FinancementFormSetInitial = inlineformset_factory( | |
2e672700 OL |
217 | dae.Poste, |
218 | dae.PosteFinancement, | |
219 | formset=BaseInlineFormSetWithInitial, | |
220 | extra=2 | |
5a1f75cb | 221 | ) |
874949f3 OL |
222 | FinancementFormSet = inlineformset_factory( |
223 | dae.Poste, | |
224 | dae.PosteFinancement, | |
225 | extra=2 | |
226 | ) | |
5a1f75cb | 227 | |
03b395db | 228 | |
f40a4829 BS |
229 | class DossierComparaisonForm( |
230 | filtered_archived_fields_form_factory( | |
231 | 'classement', | |
232 | ), | |
233 | forms.ModelForm): | |
11f22317 | 234 | |
03b395db | 235 | recherche = AutoCompleteSelectField('dossiers', required=False) |
5a1f75cb EMS |
236 | poste = forms.CharField( |
237 | max_length=255, widget=forms.TextInput(attrs={'size': '60'}) | |
238 | ) | |
b9098c33 BS |
239 | cmp_dossier = forms.IntegerField( |
240 | widget=forms.widgets.HiddenInput, | |
241 | required=False | |
242 | ) | |
03b395db | 243 | |
320d7584 | 244 | class Meta: |
03b395db | 245 | model = dae.DossierComparaison |
320d7584 | 246 | exclude = ('dossier',) |
03b395db | 247 | |
320d7584 EMS |
248 | DossierComparaisonFormSet = modelformset_factory( |
249 | dae.DossierComparaison, extra=3, max_num=3, form=DossierComparaisonForm | |
25086dcf | 250 | ) |
03b395db | 251 | |
5a1f75cb | 252 | |
f40a4829 BS |
253 | class PosteComparaisonForm( |
254 | filtered_archived_fields_form_factory('classement'), | |
255 | forms.ModelForm): | |
11f22317 | 256 | |
e503e64d | 257 | recherche = AutoCompleteSelectField('dae_postes', required=False) |
068d1462 | 258 | |
b9098c33 BS |
259 | cmp_poste = forms.IntegerField( |
260 | widget=forms.widgets.HiddenInput, | |
261 | required=False, | |
262 | ) | |
263 | ||
320d7584 | 264 | class Meta: |
068d1462 | 265 | model = dae.PosteComparaison |
320d7584 | 266 | exclude = ('poste',) |
068d1462 | 267 | |
874949f3 OL |
268 | # Ce formset est utilisé dans le cas de la création de poste prépopulé avec les |
269 | # données de RH | |
270 | PosteComparaisonFormSetInitial = inlineformset_factory( | |
2e672700 OL |
271 | dae.Poste, |
272 | dae.PosteComparaison, | |
273 | extra=3, | |
274 | max_num=3, | |
275 | form=PosteComparaisonForm, | |
2e672700 | 276 | formset=BaseInlineFormSetWithInitial, |
25086dcf | 277 | ) |
874949f3 OL |
278 | PosteComparaisonFormSet = inlineformset_factory( |
279 | dae.Poste, | |
280 | dae.PosteComparaison, | |
281 | extra=3, | |
282 | max_num=3, | |
283 | form=PosteComparaisonForm, | |
284 | ) | |
068d1462 | 285 | |
5a1f75cb | 286 | |
156d6b4d BS |
287 | class FlexibleRemunForm( |
288 | filtered_archived_fields_form_factory( | |
289 | 'type', | |
290 | ), | |
291 | forms.ModelForm): | |
bc8dcc0e | 292 | # Utilisé dans templats. |
0a085c42 OL |
293 | montant_mensuel = forms.DecimalField(required=False) |
294 | montant = forms.DecimalField(required=True, label='Montant annuel') | |
295 | ||
296 | class Meta: | |
297 | model = dae.Remuneration | |
298 | ||
4718c21c BS |
299 | def __init__(self, *a, **kw): |
300 | super(FlexibleRemunForm, self).__init__(*a, **kw) | |
661da766 | 301 | # self.fields['type'].widget = ReadOnlyChoiceWidget(choices=self.fields['type'].choices) |
4718c21c | 302 | |
dc4b78a7 OL |
303 | def clean_devise(self): |
304 | devise = self.cleaned_data['devise'] | |
67173010 OL |
305 | if devise.code == 'EUR': |
306 | return devise | |
5a1f75cb EMS |
307 | implantation = ref.Implantation.objects.get( |
308 | id=self.data['implantation'] | |
309 | ) | |
2455f48d | 310 | liste_taux = devise.tauxchange_set.order_by('-annee') |
dc4b78a7 | 311 | if len(liste_taux) == 0: |
5a1f75cb EMS |
312 | raise forms.ValidationError( |
313 | u"La devise %s n'a pas de taux pour l'implantation %s" % | |
314 | (devise, implantation) | |
315 | ) | |
dc4b78a7 OL |
316 | else: |
317 | return devise | |
318 | ||
6bec5651 BS |
319 | def has_changed(self): |
320 | """ | |
321 | Modification de has_changed pour qu'il ignore les montant a 0 | |
322 | et les 'types'. | |
323 | """ | |
324 | ||
325 | changed_data = self.changed_data | |
326 | ||
327 | # Type is set in hidden fields, it shouldn't be changed by the | |
328 | # user; ignore when checking if data has changed. | |
329 | if 'type' in changed_data: | |
330 | changed_data.pop(changed_data.index('type')) | |
331 | ||
332 | # Montant is set to 0 in javascript, ifnore 'montant' data if | |
333 | # its value is 0. | |
334 | ||
335 | # Generer le key tel qu'identifié dans self.data: | |
336 | montant_key = '-'.join((self.prefix, 'montant')) | |
337 | ||
338 | if ('montant' in changed_data and | |
339 | self.data.get(montant_key, '0') == '0'): | |
340 | changed_data.pop(changed_data.index('montant')) | |
341 | ||
342 | return bool(changed_data) | |
343 | ||
4718c21c | 344 | |
661da766 | 345 | class ReadOnlyRemunForm(FlexibleRemunForm): |
bc8dcc0e BS |
346 | # Utilisé dans templats. |
347 | ||
661da766 | 348 | def __init__(self, *a, **kw): |
bc8dcc0e | 349 | super (ReadOnlyRemunForm, self).__init__(*a, **kw) |
661da766 BS |
350 | for field in self.fields: |
351 | field = self.fields[field] | |
352 | if not isinstance(field.widget, ( | |
353 | forms.widgets.HiddenInput, | |
354 | forms.widgets.Select)): | |
355 | field.widget = ReadOnlyWidget() | |
356 | elif isinstance(field.widget, forms.widgets.Select): | |
357 | field.widget = ReadOnlyChoiceWidget(choices=field.choices) | |
358 | ||
359 | ||
4718c21c BS |
360 | class GroupedInlineFormset(BaseInlineFormSet): |
361 | ||
661da766 BS |
362 | def set_groups(self, |
363 | groups, | |
364 | group_accessor, | |
365 | choice_overrides=[]): | |
366 | ||
4718c21c | 367 | |
661da766 | 368 | # Create pre-defined groups. |
6bec5651 | 369 | self.groups = OrderedDict() |
661da766 BS |
370 | for group in groups: |
371 | self.groups[group[0]] = { | |
372 | 'name': group[1], | |
373 | 'key': group[0], | |
374 | 'forms': [], | |
375 | } | |
376 | ||
377 | # Assign each form to a group. | |
4718c21c | 378 | for form in self.forms: |
661da766 BS |
379 | if bool(form.initial): |
380 | grp = group_accessor(form) | |
381 | if grp[0] not in self.groups: | |
382 | self.groups[grp[0]] = { | |
383 | 'name': grp[1], | |
384 | 'key': grp[0], | |
385 | 'forms': [], | |
386 | } | |
387 | self.groups[grp[0]]['forms'].append(form) | |
388 | ||
389 | # Add extra forms (n extra for each grop). | |
390 | tmp_extras = [] | |
391 | for i in xrange(len(self.groups) * self.extra): | |
bc8dcc0e BS |
392 | tmp_extras.insert(0, |
393 | self._construct_form(self.initial_form_count() + i)) | |
661da766 BS |
394 | |
395 | for g in self.groups: | |
396 | for i in xrange(self.extra): | |
56db8597 BS |
397 | tmp_form = tmp_extras.pop() |
398 | self.groups[g]['forms'].append(tmp_form) | |
399 | self.forms.append(tmp_form) | |
6bec5651 | 400 | |
661da766 BS |
401 | |
402 | # Override form choices with the data provided in | |
403 | # choice_overrides | |
404 | for key in choice_overrides: | |
405 | for form in self.groups.get(key, {'forms': []})['forms']: | |
406 | for field_key in choice_overrides[key]: | |
407 | form.fields[field_key].choices = choice_overrides[ | |
408 | key][field_key] | |
409 | ||
410 | ||
411 | # Create an iterable for easier access in template. | |
4718c21c BS |
412 | self.group_list = self.groups.values() |
413 | ||
661da766 BS |
414 | # def set_groups(self, group_accessor, groups, group_order=[]): |
415 | # """ | |
416 | # group_accessor: A function that will get the key and name from | |
417 | # each form. | |
418 | # group_order: list the group keys here in a list and | |
419 | # GroupedInlineFormset.groups will be ordered (ordereddict) by | |
420 | # the key sequence provided here. Any missing key from the | |
421 | # sequence will | |
422 | # """ | |
423 | ||
424 | # # Build group list. | |
425 | # self.groups = OrderedDict() | |
426 | # temp_groups = {} | |
427 | # # self.groups_and_forms = [] | |
428 | # for form in self.forms: | |
429 | # group_key, group_name = group_accessor(form) | |
430 | # if not temp_groups.has_key(group_key): | |
431 | # temp_groups[group_key] = { | |
432 | # 'name': group_name, | |
433 | # 'key': group_key, | |
434 | # 'forms': [], | |
435 | # } | |
436 | # temp_groups[group_key]['forms'].append(form) | |
437 | ||
438 | # for order_key in group_order: | |
439 | # if temp_groups.has_key(order_key): | |
440 | # self.groups[order_key] = temp_groups.pop(order_key) | |
441 | ||
442 | # for key in temp_groups: | |
443 | # self.groups[key] = temp_groups[key] | |
444 | ||
445 | # del temp_groups | |
446 | ||
447 | # self.group_list = self.groups.values() | |
448 | ||
4718c21c BS |
449 | |
450 | def remun_formset_factory(parent_model, | |
451 | model, | |
452 | form=forms.ModelForm, | |
453 | formset=GroupedInlineFormset, | |
454 | fk_name=None, | |
455 | fields=None, | |
456 | exclude=None, | |
457 | can_order=False, | |
458 | can_delete=True, | |
bc8dcc0e | 459 | read_only=False, |
661da766 | 460 | extra=2, |
4718c21c | 461 | max_num=None, |
6bec5651 | 462 | formfield_callback=None, |
661da766 BS |
463 | groups=None, |
464 | choice_overrides=[]): | |
4718c21c | 465 | trs = rh.TypeRemuneration.objects.all() |
661da766 | 466 | # extra = max_num = trs.count() |
4718c21c BS |
467 | fk = _get_foreign_key(parent_model, model, fk_name=fk_name) |
468 | # enforce a max_num=1 when the foreign key to the parent model is unique. | |
469 | if fk.unique: | |
470 | max_num = 1 | |
471 | kwargs = { | |
472 | 'form': form, | |
473 | 'formfield_callback': formfield_callback, | |
474 | 'formset': formset, | |
475 | 'extra': extra, | |
476 | 'can_delete': can_delete, | |
477 | 'can_order': can_order, | |
478 | 'fields': fields, | |
479 | 'exclude': exclude, | |
480 | 'max_num': max_num, | |
481 | } | |
482 | FormSet = modelformset_factory(model, **kwargs) | |
483 | FormSet.fk = fk | |
bc8dcc0e | 484 | FormSet.read_only = read_only |
4718c21c BS |
485 | |
486 | def grouper(form): | |
661da766 BS |
487 | rtype = form.initial['type'] |
488 | if not isinstance(rtype, rh.TypeRemuneration): | |
489 | rtype = rh.TypeRemuneration.objects.get(id=rtype) | |
490 | return (rtype.nature_remuneration, | |
491 | rtype.nature_remuneration | |
492 | ) | |
4718c21c | 493 | |
4718c21c | 494 | |
661da766 | 495 | |
bc8dcc0e BS |
496 | # Monkey patch FormSet. |
497 | def __init__(inst, *a, **kw): | |
498 | super(inst.__class__, inst).__init__(*a, **kw) | |
661da766 BS |
499 | inst.set_groups(groups, grouper, choice_overrides) |
500 | ||
4718c21c BS |
501 | FormSet.__init__ = __init__ |
502 | ||
503 | return FormSet | |
504 | ||
505 | ||
f5e9e6a4 BS |
506 | def remun_formset_factory_factory( |
507 | read_only=False, | |
508 | parent_model=dae.Dossier, | |
509 | model=dae.Remuneration, | |
510 | exclude_archived=False): | |
bc8dcc0e BS |
511 | """ |
512 | Don't we love factory factories? | |
513 | """ | |
514 | ||
515 | null_choice = ('', '-' * 10) | |
516 | extras = 2 if not read_only else 0 | |
517 | can_delete = False if read_only else True | |
518 | form_class = ReadOnlyRemunForm if read_only else FlexibleRemunForm | |
519 | ||
f5e9e6a4 BS |
520 | choice_override_extra_q = {} |
521 | ||
522 | if exclude_archived: | |
523 | choice_override_extra_q.update({ | |
524 | 'archive': False | |
525 | }) | |
526 | ||
bc8dcc0e | 527 | return remun_formset_factory( |
b9098c33 BS |
528 | parent_model, |
529 | model, | |
bc8dcc0e BS |
530 | form=form_class, |
531 | extra=extras, | |
532 | can_delete=can_delete, | |
533 | read_only=read_only, | |
46dab81b | 534 | groups = rh.NATURE_REMUNERATION_CHOICES, |
bc8dcc0e BS |
535 | choice_overrides = { |
536 | u'Traitement': { | |
537 | 'type': [null_choice] + list( | |
538 | rh.TypeRemuneration.objects.filter( | |
f5e9e6a4 BS |
539 | nature_remuneration=u'Traitement', |
540 | **choice_override_extra_q).values_list( | |
bc8dcc0e BS |
541 | 'id', 'nom') |
542 | ) | |
543 | }, | |
544 | u'Indemnité': { | |
545 | 'type': [null_choice] + list( | |
546 | rh.TypeRemuneration.objects.filter( | |
f5e9e6a4 BS |
547 | nature_remuneration=u'Indemnité', |
548 | **choice_override_extra_q).values_list( | |
bc8dcc0e BS |
549 | 'id', 'nom') |
550 | ) | |
551 | }, | |
552 | u'Charges': { | |
553 | 'type': [null_choice] + list( | |
554 | rh.TypeRemuneration.objects.filter( | |
f5e9e6a4 BS |
555 | nature_remuneration=u'Charges', |
556 | **choice_override_extra_q).values_list( | |
bc8dcc0e BS |
557 | 'id', 'nom') |
558 | ) | |
559 | }, | |
560 | u'Accessoire': { | |
561 | 'type': [null_choice] + list( | |
562 | rh.TypeRemuneration.objects.filter( | |
f5e9e6a4 BS |
563 | nature_remuneration=u'Accessoire', |
564 | **choice_override_extra_q).values_list( | |
bc8dcc0e BS |
565 | 'id', 'nom') |
566 | ) | |
567 | }, | |
568 | u'RAS': { | |
569 | 'type': [null_choice] + list( | |
570 | rh.TypeRemuneration.objects.filter( | |
f5e9e6a4 BS |
571 | nature_remuneration=u'RAS', |
572 | **choice_override_extra_q).values_list( | |
bc8dcc0e BS |
573 | 'id', 'nom') |
574 | ) | |
575 | }, | |
f5e9e6a4 | 576 | }, |
bc8dcc0e | 577 | ) |
0a085c42 | 578 | |
b9098c33 BS |
579 | RemunForm = remun_formset_factory_factory( |
580 | read_only=False, | |
581 | parent_model=dae.Dossier, | |
582 | model=dae.Remuneration, | |
f5e9e6a4 | 583 | exclude_archived=True, |
b9098c33 BS |
584 | ) |
585 | ||
586 | ReadOnlyRemunFormSet = remun_formset_factory_factory( | |
587 | read_only=True, | |
588 | parent_model=dae.Dossier, | |
589 | model=dae.Remuneration, | |
590 | ) | |
591 | ||
592 | PosteCompReadOnlyRemunFormSet = remun_formset_factory_factory( | |
593 | read_only=True, | |
594 | parent_model=dae.PosteComparaison, | |
595 | model=dae.PosteComparaisonRemuneration, | |
596 | ) | |
597 | ||
598 | DossierCompReadOnlyRemunFormSet = remun_formset_factory_factory( | |
599 | read_only=True, | |
600 | parent_model=dae.DossierComparaison, | |
601 | model=dae.DossierComparaisonRemuneration, | |
602 | ) | |
661da766 | 603 | |
5a1f75cb | 604 | |
f40a4829 BS |
605 | class PosteForm(filtered_archived_fields_form_factory( |
606 | 'classement_min', | |
607 | 'classement_max',), | |
608 | forms.ModelForm): | |
5d680e84 | 609 | """ Formulaire des postes. """ |
12c7f8a7 | 610 | |
ea7adc69 | 611 | # On ne propose que les services actifs |
5a1f75cb EMS |
612 | service = forms.ModelChoiceField( |
613 | queryset=rh.Service.objects.all(), required=True | |
614 | ) | |
ea7adc69 | 615 | |
5a1f75cb | 616 | responsable = AutoCompleteSelectField('responsables', required=True) |
12c7f8a7 OL |
617 | #responsable = forms.ModelChoiceField( |
618 | # queryset=rh.Poste.objects.select_related(depth=1)) | |
619 | ||
620 | # La liste des choix est laissée vide. Voir __init__ pour la raison. | |
621 | poste = forms.ChoiceField(label="Nouveau poste ou évolution du poste", | |
622 | choices=(), required=False) | |
11f22317 | 623 | |
5a1f75cb EMS |
624 | valeur_point_min = forms.ModelChoiceField( |
625 | queryset=rh.ValeurPoint.actuelles.all(), required=False | |
626 | ) | |
627 | valeur_point_max = forms.ModelChoiceField( | |
628 | queryset=rh.ValeurPoint.actuelles.all(), required=False | |
629 | ) | |
11f22317 | 630 | |
5d680e84 NC |
631 | class Meta: |
632 | model = dae.Poste | |
c3be904d OL |
633 | fields = ('type_intervention', |
634 | 'poste', 'implantation', 'type_poste', 'service', 'nom', | |
154677c3 | 635 | 'responsable', 'local', 'expatrie', 'mise_a_disposition', |
b15bf543 | 636 | 'appel', 'date_debut', 'date_fin', |
5d680e84 NC |
637 | 'regime_travail', 'regime_travail_nb_heure_semaine', |
638 | 'classement_min', 'classement_max', | |
639 | 'valeur_point_min', 'valeur_point_max', | |
3d627bfd | 640 | 'devise_min', 'devise_max', |
5f61bccb OL |
641 | 'salaire_min', 'salaire_max', |
642 | 'indemn_expat_min', 'indemn_expat_max', | |
643 | 'indemn_fct_min', 'indemn_fct_max', | |
644 | 'charges_patronales_min', 'charges_patronales_max', | |
5d680e84 NC |
645 | 'autre_min', 'autre_max', 'devise_comparaison', |
646 | 'comp_locale_min', 'comp_locale_max', | |
647 | 'comp_universite_min', 'comp_universite_max', | |
648 | 'comp_fonctionpub_min', 'comp_fonctionpub_max', | |
649 | 'comp_ong_min', 'comp_ong_max', | |
8fa94e8b | 650 | 'comp_autre_min', 'comp_autre_max', |
2e092e0c | 651 | 'justification', |
8fa94e8b | 652 | ) |
c3be904d OL |
653 | widgets = dict(type_intervention=forms.RadioSelect(), |
654 | appel=forms.RadioSelect(), | |
3d627bfd | 655 | nom=forms.TextInput(attrs={'size': 60},), |
e88caaf0 OL |
656 | date_debut=admin_widgets.AdminDateWidget(), |
657 | date_fin=admin_widgets.AdminDateWidget(), | |
2e092e0c | 658 | justification=forms.Textarea(attrs={'cols': 80},), |
3d627bfd | 659 | #devise_min=forms.Select(attrs={'disabled':'disabled'}), |
660 | #devise_max=forms.Select(attrs={'disabled':'disabled'}), | |
661 | ) | |
5d680e84 | 662 | |
c2458db6 | 663 | def __init__(self, *args, **kwargs): |
5d680e84 NC |
664 | """ Mise à jour dynamique du contenu du menu des postes. |
665 | ||
666 | Si on ne met le menu à jour de cette façon, à chaque instantiation du | |
667 | formulaire, son contenu est mis en cache par le système et il ne | |
668 | reflète pas les changements apportés par les ajouts, modifications, | |
669 | etc... | |
670 | ||
139686f2 NC |
671 | Aussi, dans ce cas-ci, on ne peut pas utiliser un ModelChoiceField |
672 | car le "id" de chaque choix est spécial (voir _poste_choices). | |
673 | ||
5d680e84 | 674 | """ |
c2458db6 | 675 | request = kwargs.pop('request') |
5d680e84 | 676 | super(PosteForm, self).__init__(*args, **kwargs) |
f258e4e7 | 677 | self.fields['poste'].choices = self._poste_choices(request) |
9c1ff333 | 678 | |
5a1f75cb EMS |
679 | self.fields['implantation'].choices = \ |
680 | _implantation_choices(self, request) | |
5d680e84 | 681 | |
cc3098d0 OL |
682 | # Quand le dae.Poste n'existe pas, on recherche dans les dossiers rhv1 |
683 | if self.instance and self.instance.id is None: | |
684 | dossiers = self.instance.get_dossiers() | |
685 | if len(dossiers) > 0: | |
09aa8374 | 686 | self.initial['service'] = dossiers[0].poste.service |
9508a5b8 | 687 | |
f258e4e7 | 688 | def _poste_choices(self, request): |
5d680e84 | 689 | """ Menu déroulant pour les postes. |
9c1ff333 | 690 | Constitué des postes de RH |
5d680e84 | 691 | """ |
9c1ff333 OL |
692 | postes_rh = rh.Poste.objects.ma_region_ou_service(request.user).all() |
693 | postes_rh = postes_rh.select_related(depth=1) | |
5d680e84 | 694 | |
98d51b59 | 695 | return [('', 'Nouveau poste')] + \ |
9c1ff333 OL |
696 | sorted([('rh-%s' % p.id, label_poste_display(p)) for p in |
697 | postes_rh], | |
5d680e84 | 698 | key=lambda t: t[1]) |
3ed49093 | 699 | |
4dd75e7b OL |
700 | def clean(self): |
701 | """ | |
702 | Validation conditionnelles de certains champs. | |
703 | """ | |
5a1f75cb | 704 | cleaned_data = self.cleaned_data |
4dd75e7b | 705 | |
5a1f75cb EMS |
706 | if cleaned_data.get("local") is False \ |
707 | and cleaned_data.get("expatrie") is False: | |
708 | msg = "Le poste doit au moins être ouvert localement " \ | |
709 | "ou aux expatriés" | |
f42c6e20 OL |
710 | self._errors["local"] = self.error_class([msg]) |
711 | self._errors["expatrie"] = '' | |
712 | raise forms.ValidationError(msg) | |
f42c6e20 | 713 | |
4dd75e7b OL |
714 | return cleaned_data |
715 | ||
3ed49093 | 716 | |
34950f36 | 717 | class ChoosePosteForm(forms.Form): |
139686f2 | 718 | class Meta: |
139686f2 NC |
719 | fields = ('poste',) |
720 | ||
721 | # La liste des choix est laissée vide. Voir PosteForm.__init__. | |
34950f36 OL |
722 | postes_dae = forms.ChoiceField(choices=(), required=False) |
723 | postes_rh = forms.ChoiceField(choices=(), required=False) | |
139686f2 | 724 | |
4ee6d70a | 725 | def __init__(self, request=None, *args, **kwargs): |
139686f2 | 726 | super(ChoosePosteForm, self).__init__(*args, **kwargs) |
34950f36 OL |
727 | self.fields['postes_dae'].choices = self._poste_dae_choices(request) |
728 | self.fields['postes_rh'].choices = self._poste_rh_choices(request) | |
139686f2 | 729 | |
34950f36 OL |
730 | def _poste_dae_choices(self, request): |
731 | """ Menu déroulant pour les postes.""" | |
732 | postes_dae = dae.Poste.objects.ma_region_ou_service(request.user) \ | |
733 | .exclude(etat__in=(POSTE_ETAT_FINALISE, )) \ | |
734 | .annotate(num_dae=Count('dae_dossiers')) \ | |
735 | .filter(num_dae=0) \ | |
67ae0181 | 736 | .order_by('implantation', '-date_debut', ) |
139686f2 | 737 | |
98d51b59 | 738 | return [('', '----------')] + \ |
34950f36 OL |
739 | [('dae-%s' % p.id, label_poste_display(p)) for p in postes_dae] |
740 | ||
741 | def _poste_rh_choices(self, request): | |
742 | """ Menu déroulant pour les postes.""" | |
80be36aa | 743 | postes_dae = dae.Poste.objects.exclude(etat__in=(POSTE_ETAT_FINALISE, )) |
bed0c4c9 | 744 | today = datetime.date.today() |
80be36aa | 745 | id_poste_dae_commences = [p.id_rh_id for p in postes_dae if p.id_rh is not None] |
34950f36 | 746 | postes_rh = rh.Poste.objects.ma_region_ou_service(request.user) \ |
80be36aa | 747 | .exclude(id__in=id_poste_dae_commences) \ |
bed0c4c9 BS |
748 | .filter(Q(date_debut__lte=today) & |
749 | (Q(date_fin__gte=today) | | |
750 | Q(date_fin__isnull=True)) | |
751 | ) \ | |
67ae0181 | 752 | .order_by('implantation', '-date_debut', ) |
34950f36 OL |
753 | |
754 | return [('', '----------')] + \ | |
755 | [('rh-%s' % p.id, label_poste_display(p)) for p in postes_rh] | |
139686f2 | 756 | |
80be36aa OL |
757 | def clean(self): |
758 | cleaned_data = super(ChoosePosteForm, self).clean() | |
759 | postes_dae = cleaned_data.get("postes_dae") | |
760 | postes_rh = cleaned_data.get("postes_rh") | |
761 | if (postes_dae is u"" and postes_rh is u"") or \ | |
762 | (postes_dae is not u"" and postes_rh is not u""): | |
763 | raise forms.ValidationError("Choisissez un poste DAE ou un poste RH") | |
764 | return cleaned_data | |
765 | ||
766 | def redirect(self): | |
767 | poste_dae_key = self.cleaned_data.get("postes_dae") | |
768 | if poste_dae_key is not u"": | |
769 | return redirect(reverse('embauche', args=(poste_dae_key,))) | |
770 | poste_rh_key = self.cleaned_data.get("postes_rh") | |
771 | if poste_rh_key is not u"": | |
67ae0181 | 772 | return redirect("%s?creer_dossier_dae='M'" % reverse('poste', args=(poste_rh_key,))) |
139686f2 | 773 | |
139686f2 NC |
774 | class EmployeForm(forms.ModelForm): |
775 | """ Formulaire des employés. """ | |
776 | class Meta: | |
777 | model = dae.Employe | |
778 | fields = ('employe', 'nom', 'prenom', 'genre') | |
779 | ||
780 | # La liste des choix est laissée vide. Voir Poste.__init__ pour la raison. | |
781 | employe = forms.ChoiceField(choices=(), required=False) | |
782 | ||
ac6235f6 | 783 | def __init__(self, *args, **kwargs): |
139686f2 | 784 | """ Mise à jour dynamique du contenu du menu des employés. """ |
ac6235f6 | 785 | request = kwargs.pop('request', None) |
139686f2 | 786 | super(EmployeForm, self).__init__(*args, **kwargs) |
f258e4e7 | 787 | self.fields['employe'].choices = _employe_choices(self, request) |
139686f2 | 788 | |
139686f2 | 789 | |
f40a4829 BS |
790 | class DossierForm( |
791 | filtered_archived_fields_form_factory( | |
792 | 'classement', | |
793 | 'classement_anterieur', | |
794 | 'classement_titulaire_anterieur', | |
795 | ), | |
796 | forms.ModelForm): | |
139686f2 NC |
797 | """ Formulaire des dossiers. """ |
798 | class Meta: | |
5a1f75cb | 799 | exclude = ('etat', 'employe', 'poste', 'date_debut',) |
139686f2 | 800 | model = dae.Dossier |
4d25e2ba | 801 | widgets = dict(statut_residence=forms.RadioSelect(), |
0e0aeb7e OL |
802 | contrat_date_debut=admin_widgets.AdminDateWidget(), |
803 | contrat_date_fin=admin_widgets.AdminDateWidget(), | |
4d25e2ba | 804 | ) |
e6f52402 | 805 | |
3799cafc | 806 | WF_HELP_TEXT = "" |
e0b93e3a | 807 | |
5a1f75cb | 808 | |
e6f52402 | 809 | class PosteWorkflowForm(WorkflowFormMixin): |
56589624 | 810 | bouton_libelles = POSTE_ETATS_BOUTONS |
5a1f75cb | 811 | |
e6f52402 OL |
812 | class Meta: |
813 | fields = ('etat', ) | |
814 | model = dae.Poste | |
9536ea21 | 815 | |
e0b93e3a | 816 | def __init__(self, *args, **kwargs): |
e54b7d5d | 817 | super(PosteWorkflowForm, self).__init__(*args, **kwargs) |
e0b93e3a OL |
818 | self.fields['etat'].help_text = WF_HELP_TEXT |
819 | ||
820 | ||
e6f52402 | 821 | class DossierWorkflowForm(WorkflowFormMixin): |
5a1f75cb EMS |
822 | bouton_libelles = POSTE_ETATS_BOUTONS # meme workflow que poste... |
823 | ||
e6f52402 | 824 | class Meta: |
9e40cfbe | 825 | fields = ('etat', ) |
e6f52402 | 826 | model = dae.Dossier |
e0b93e3a OL |
827 | |
828 | def __init__(self, *args, **kwargs): | |
e54b7d5d | 829 | super(DossierWorkflowForm, self).__init__(*args, **kwargs) |
e0b93e3a | 830 | self.fields['etat'].help_text = WF_HELP_TEXT |
e54b7d5d | 831 | self._etat_initial = self.instance.etat |
e0b93e3a | 832 | |
e54b7d5d EMS |
833 | def save(self): |
834 | super(DossierWorkflowForm, self).save() | |
835 | poste = self.instance.poste | |
66fefd2f | 836 | |
72068c80 BS |
837 | if poste.etat == self._etat_initial: |
838 | poste.etat = self.instance.etat | |
839 | poste.save() | |
840 | ||
841 | # créer le commentaire automatique pour le poste associé | |
842 | commentaire = WorkflowCommentaire() | |
843 | commentaire.content_object = poste | |
844 | texte = u"Validation automatique à travers le dossier [%s] de %s\n%s" %( | |
66fefd2f OL |
845 | self.instance.id, |
846 | self.instance, | |
847 | self.data.get('commentaire', ''), | |
848 | ) | |
72068c80 BS |
849 | commentaire.texte = texte |
850 | commentaire.etat_initial = self.instance._etat_courant | |
851 | commentaire.etat_final = self.instance.etat | |
852 | commentaire.owner = self.request.user | |
853 | commentaire.save() | |
9536ea21 | 854 | |
5a1f75cb | 855 | |
9536ea21 EMS |
856 | class ContratForm(forms.ModelForm): |
857 | ||
858 | class Meta: | |
9dfa4296 | 859 | fields = ('type_contrat', 'fichier', ) |
9536ea21 EMS |
860 | model = dae.Contrat |
861 | ||
5a1f75cb | 862 | |
c3f0b49f EMS |
863 | class DAENumeriseeForm(forms.ModelForm): |
864 | ||
865 | class Meta: | |
866 | model = dae.Dossier | |
867 | fields = ('dae_numerisee',) | |
cbfd7bd4 EMS |
868 | |
869 | ||
870 | class DAEFinaliseesSearchForm(forms.Form): | |
871 | q = forms.CharField( | |
872 | label='Recherche', required=False, | |
873 | widget=forms.TextInput(attrs={'size': 40}) | |
874 | ) | |
875 | importees = forms.ChoiceField( | |
876 | label='Importation', required=False, choices=( | |
877 | ('', ''), | |
878 | ('oui', 'DAE importées seulement'), | |
879 | ('non', 'DAE non-importées seulement'), | |
880 | ) | |
881 | ) |