X-Git-Url: http://git.auf.org/?p=auf_rh_dae.git;a=blobdiff_plain;f=project%2Fdae%2Fforms.py;h=373099fa80f012ecc4a8be598e7c45741881f94e;hp=f06683d55838f6963d3f737b6a2c9f8be119100d;hb=892a50fd99f2506664f1699024d673fd39765c65;hpb=3383b2d11c3e532fb193ef99e827cb8f2fefd32e diff --git a/project/dae/forms.py b/project/dae/forms.py index f06683d..373099f 100644 --- a/project/dae/forms.py +++ b/project/dae/forms.py @@ -1,20 +1,46 @@ # -*- encoding: utf-8 -*- +import datetime +from ordereddict import OrderedDict +from dateutil.relativedelta import relativedelta from django import forms +from django.core.urlresolvers import reverse +from django.core.exceptions import MultipleObjectsReturned from django.forms.models import BaseInlineFormSet +from django.forms.models import ( + inlineformset_factory, + modelformset_factory, + _get_foreign_key, + ) +from django.db.models import Q, Max, Count +from django.shortcuts import redirect from django.contrib.admin import widgets as admin_widgets -from django.db.models import Q, Max -from django.forms.models import inlineformset_factory, modelformset_factory from ajax_select.fields import AutoCompleteSelectField from auf.django.references import models as ref from auf.django.workflow.forms import WorkflowFormMixin +from auf.django.workflow.models import WorkflowCommentaire from project import groups from project.rh import models as rh from project.dae import models as dae -from project.dae.workflow import POSTE_ETATS_BOUTONS +from .widgets import ReadOnlyChoiceWidget, ReadOnlyWidget +from project.dae.workflow import POSTE_ETATS_BOUTONS, POSTE_ETAT_FINALISE + + +def filtered_archived_fields_form_factory(*fields): + """ + Retourne un model form + """ + class FilterArchivedFields(object): + def __init__(self, *a, **kw): + super(FilterArchivedFields, self).__init__(*a, **kw) + for f in fields: + self.fields[f].queryset = ( + self.fields[f]._queryset.filter(archive=False) + ) + return FilterArchivedFields class BaseInlineFormSetWithInitial(BaseInlineFormSet): @@ -84,16 +110,12 @@ class BaseInlineFormSetWithInitial(BaseInlineFormSet): def _implantation_choices(obj, request): # TRAITEMENT NORMAL employe = groups.get_employe_from_user(request.user) - # SERVICE - if groups.is_user_dans_services_centraux(request.user): - q = Q(**{'id': employe.implantation_id}) - # REGION - else: - q = Q(**{'region': employe.implantation.region}) + q = Q(**{'zone_administrative__in': groups.get_zones_from_user(request.user)}) # TRAITEMENT DRH user_groupes = [g.name for g in request.user.groups.all()] - if groups.DRH_NIVEAU_1 in user_groupes: + if groups.DRH_NIVEAU_1 in user_groupes or \ + groups.DRH_NIVEAU_2 in user_groupes: q = Q() return [('', '----------')] + \ [(i.id, unicode(i), )for i in ref.Implantation.objects.filter(q)] @@ -102,21 +124,20 @@ def _implantation_choices(obj, request): def _employe_choices(obj, request): # TRAITEMENT NORMAL employe = groups.get_employe_from_user(request.user) - # SERVICE - if groups.is_user_dans_services_centraux(request.user): - q_dae_region_service = Q(poste__implantation=employe.implantation) - q_rh_region_service = Q(poste__implantation=employe.implantation) - # REGION - else: - q_dae_region_service = Q( - poste__implantation__region=employe.implantation.region + q_dae_region_service = Q( + poste__implantation__zone_administrative__in=( + groups.get_zones_from_user(request.user) ) - q_rh_region_service = Q( - poste__implantation__region=employe.implantation.region + ) + q_rh_region_service = Q( + poste__implantation__zone_administrative__in=( + groups.get_zones_from_user(request.user) ) + ) # TRAITEMENT DRH user_groupes = [g.name for g in request.user.groups.all()] - if groups.DRH_NIVEAU_1 in user_groupes: + if groups.DRH_NIVEAU_1 in user_groupes or \ + groups.DRH_NIVEAU_2 in user_groupes: q_dae_region_service = Q() q_rh_region_service = Q() @@ -158,16 +179,16 @@ def _employe_choices(obj, request): employes_avec_dae = [d.employe_id for d in dae.Dossier.objects.all()] employes_orphelins = dae.Employe.objects.exclude(id__in=employes_avec_dae) - def option_label(employe): - return "%s %s" % (employe.nom.upper(), employe.prenom.title()) + def option_label(employe, extra=""): + if extra: + extra = " [%s]" % extra + return "%s %s %s" % (employe.nom.upper(), employe.prenom.title(), extra) - return [('', 'Nouvel employé')] + \ - sorted( - [('dae-%s' % p.id, option_label(p)) - for p in dae_ | copies | employes_orphelins] + - [('rh-%s' % p.id, option_label(p)) for p in rhv1], - key=lambda t: t[1] - ) + lbl_rh = sorted([('rh-%s' % p.id, option_label(p, "existant dans rh")) for p in rhv1], + key=lambda t: t[1]) + lbl_dae = sorted([('dae-%s' % p.id, option_label(p)) for p in dae_ | copies | employes_orphelins], + key=lambda t: t[1]) + return [('', 'Nouvel employé')] + lbl_rh + lbl_dae def label_poste_display(poste): @@ -177,10 +198,13 @@ def label_poste_display(poste): annee = poste.date_debut.year nom = poste.nom - - label = u"%s %s - %s [%s]" % ( - annee, nom, poste.type_poste.categorie_emploi.nom, poste.id - ) + label = u"%s (%s) %s [%s]" % ( + annee, + poste.implantation.nom_court, + nom, + #poste.type_poste.categorie_emploi.nom, + poste.id, + ) return label @@ -202,12 +226,20 @@ FinancementFormSet = inlineformset_factory( ) -class DossierComparaisonForm(forms.ModelForm): +class DossierComparaisonForm( + filtered_archived_fields_form_factory( + 'classement', + ), + forms.ModelForm): recherche = AutoCompleteSelectField('dossiers', required=False) poste = forms.CharField( max_length=255, widget=forms.TextInput(attrs={'size': '60'}) ) + cmp_dossier = forms.IntegerField( + widget=forms.widgets.HiddenInput, + required=False + ) class Meta: model = dae.DossierComparaison @@ -218,10 +250,17 @@ DossierComparaisonFormSet = modelformset_factory( ) -class PosteComparaisonForm(forms.ModelForm): +class PosteComparaisonForm( + filtered_archived_fields_form_factory('classement'), + forms.ModelForm): recherche = AutoCompleteSelectField('dae_postes', required=False) + cmp_poste = forms.IntegerField( + widget=forms.widgets.HiddenInput, + required=False, + ) + class Meta: model = dae.PosteComparaison exclude = ('poste',) @@ -245,14 +284,22 @@ PosteComparaisonFormSet = inlineformset_factory( ) -class FlexibleRemunForm(forms.ModelForm): - +class FlexibleRemunForm( + filtered_archived_fields_form_factory( + 'type', + ), + forms.ModelForm): + # Utilisé dans templats. montant_mensuel = forms.DecimalField(required=False) montant = forms.DecimalField(required=True, label='Montant annuel') class Meta: model = dae.Remuneration + def __init__(self, *a, **kw): + super(FlexibleRemunForm, self).__init__(*a, **kw) + # self.fields['type'].widget = ReadOnlyChoiceWidget(choices=self.fields['type'].choices) + def clean_devise(self): devise = self.cleaned_data['devise'] if devise.code == 'EUR': @@ -269,12 +316,270 @@ class FlexibleRemunForm(forms.ModelForm): else: return devise -RemunForm = inlineformset_factory( - dae.Dossier, dae.Remuneration, extra=5, form=FlexibleRemunForm + def has_changed(self): + """ + Modification de has_changed pour qu'il ignore les montant a 0 + et les 'types'. + """ + + changed_data = self.changed_data + + # Type is set in hidden fields, it shouldn't be changed by the + # user; ignore when checking if data has changed. + if 'type' in changed_data: + changed_data.pop(changed_data.index('type')) + + # Montant is set to 0 in javascript, ifnore 'montant' data if + # its value is 0. + + # Generer le key tel qu'identifié dans self.data: + montant_key = '-'.join((self.prefix, 'montant')) + + if ('montant' in changed_data and + self.data.get(montant_key, '0') == '0'): + changed_data.pop(changed_data.index('montant')) + + return bool(changed_data) + + +class ReadOnlyRemunForm(FlexibleRemunForm): + # Utilisé dans templats. + + def __init__(self, *a, **kw): + super (ReadOnlyRemunForm, self).__init__(*a, **kw) + for field in self.fields: + field = self.fields[field] + if not isinstance(field.widget, ( + forms.widgets.HiddenInput, + forms.widgets.Select)): + field.widget = ReadOnlyWidget() + elif isinstance(field.widget, forms.widgets.Select): + field.widget = ReadOnlyChoiceWidget(choices=field.choices) + + +class GroupedInlineFormset(BaseInlineFormSet): + + def set_groups(self, + groups, + group_accessor, + choice_overrides=[]): + + + # Create pre-defined groups. + self.groups = OrderedDict() + for group in groups: + self.groups[group[0]] = { + 'name': group[1], + 'key': group[0], + 'forms': [], + } + + # Assign each form to a group. + ungrouped_forms = [] + for form in self.forms: + if bool(form.initial): + grp = group_accessor(form) + if grp[0] not in self.groups: + self.groups[grp[0]] = { + 'name': grp[1], + 'key': grp[0], + 'forms': [], + } + self.groups[grp[0]]['forms'].append(form) + else: + ungrouped_forms.append(form) + + + # Distribuer les extras de django dans les groupes, et ajouter + # des extras pour les groupes en nécessitant. + f_count = len(self.forms) + for g in self.groups: + for i in xrange(f_count, f_count + self.extra): + if len(ungrouped_forms) == 0: + f_count += 1 + + if len(ungrouped_forms) > 0: + new_form = ungrouped_forms.pop() + else: + new_form = self._construct_form(i) + self.forms.append(new_form) + + self.groups[g]['forms'].append(new_form) + + + # Override form choices with the data provided in + # choice_overrides + for key in choice_overrides: + for form in self.groups.get(key, {'forms': []})['forms']: + for field_key in choice_overrides[key]: + form.fields[field_key].choices = choice_overrides[ + key][field_key] + + + # Create an iterable for easier access in template. + self.group_list = self.groups.values() + + +def remun_formset_factory(parent_model, + model, + form=forms.ModelForm, + formset=GroupedInlineFormset, + fk_name=None, + fields=None, + exclude=None, + can_order=False, + can_delete=True, + read_only=False, + extra=2, + max_num=None, + formfield_callback=None, + groups=None, + choice_overrides=[]): + trs = rh.TypeRemuneration.objects.all() + # extra = max_num = trs.count() + fk = _get_foreign_key(parent_model, model, fk_name=fk_name) + # enforce a max_num=1 when the foreign key to the parent model is unique. + if fk.unique: + max_num = 1 + kwargs = { + 'form': form, + 'formfield_callback': formfield_callback, + 'formset': formset, + 'extra': extra, + 'can_delete': can_delete, + 'can_order': can_order, + 'fields': fields, + 'exclude': exclude, + 'max_num': max_num, + } + FormSet = modelformset_factory(model, **kwargs) + FormSet.fk = fk + FormSet.read_only = read_only + + def grouper(form): + rtype = form.initial['type'] + if not isinstance(rtype, rh.TypeRemuneration): + rtype = rh.TypeRemuneration.objects.get(id=rtype) + return (rtype.nature_remuneration, + rtype.nature_remuneration + ) + + + + # Monkey patch FormSet. + def __init__(inst, *a, **kw): + super(inst.__class__, inst).__init__(*a, **kw) + inst.set_groups(groups, grouper, choice_overrides) + + FormSet.__init__ = __init__ + + return FormSet + + +def remun_formset_factory_factory( + read_only=False, + parent_model=dae.Dossier, + model=dae.Remuneration, + exclude_archived=False): + """ + Don't we love factory factories? + """ + + null_choice = ('', '-' * 10) + extras = 2 if not read_only else 0 + can_delete = False if read_only else True + form_class = ReadOnlyRemunForm if read_only else FlexibleRemunForm + + choice_override_extra_q = {} + + if exclude_archived: + choice_override_extra_q.update({ + 'archive': False + }) + + return remun_formset_factory( + parent_model, + model, + form=form_class, + extra=extras, + can_delete=can_delete, + read_only=read_only, + groups = rh.NATURE_REMUNERATION_CHOICES, + choice_overrides = { + u'Traitement': { + 'type': [null_choice] + list( + rh.TypeRemuneration.objects.filter( + nature_remuneration=u'Traitement', + **choice_override_extra_q).values_list( + 'id', 'nom') + ) + }, + u'Indemnité': { + 'type': [null_choice] + list( + rh.TypeRemuneration.objects.filter( + nature_remuneration=u'Indemnité', + **choice_override_extra_q).values_list( + 'id', 'nom') + ) + }, + u'Charges': { + 'type': [null_choice] + list( + rh.TypeRemuneration.objects.filter( + nature_remuneration=u'Charges', + **choice_override_extra_q).values_list( + 'id', 'nom') + ) + }, + u'Accessoire': { + 'type': [null_choice] + list( + rh.TypeRemuneration.objects.filter( + nature_remuneration=u'Accessoire', + **choice_override_extra_q).values_list( + 'id', 'nom') + ) + }, + u'RAS': { + 'type': [null_choice] + list( + rh.TypeRemuneration.objects.filter( + nature_remuneration=u'RAS', + **choice_override_extra_q).values_list( + 'id', 'nom') + ) + }, + }, + ) + + +RemunForm = remun_formset_factory_factory( + read_only=False, + parent_model=dae.Dossier, + model=dae.Remuneration, + exclude_archived=True, ) +ReadOnlyRemunFormSet = remun_formset_factory_factory( + read_only=True, + parent_model=dae.Dossier, + model=dae.Remuneration, + ) + +PosteCompReadOnlyRemunFormSet = remun_formset_factory_factory( + read_only=True, + parent_model=dae.PosteComparaison, + model=dae.PosteComparaisonRemuneration, + ) + +DossierCompReadOnlyRemunFormSet = remun_formset_factory_factory( + read_only=True, + parent_model=dae.DossierComparaison, + model=dae.DossierComparaisonRemuneration, + ) + -class PosteForm(forms.ModelForm): +class PosteForm(filtered_archived_fields_form_factory( + 'classement_min', + 'classement_max',), + forms.ModelForm): """ Formulaire des postes. """ # On ne propose que les services actifs @@ -383,29 +688,62 @@ class PosteForm(forms.ModelForm): return cleaned_data -class ChoosePosteForm(forms.ModelForm): +class ChoosePosteForm(forms.Form): class Meta: - model = dae.Poste fields = ('poste',) # La liste des choix est laissée vide. Voir PosteForm.__init__. - poste = forms.ChoiceField(choices=(), required=False) + postes_dae = forms.ChoiceField(choices=(), required=False) + postes_rh = forms.ChoiceField(choices=(), required=False) def __init__(self, request=None, *args, **kwargs): super(ChoosePosteForm, self).__init__(*args, **kwargs) - self.fields['poste'].choices = self._poste_choices(request) + self.fields['postes_dae'].choices = self._poste_dae_choices(request) + self.fields['postes_rh'].choices = self._poste_rh_choices(request) - def _poste_choices(self, request): - """ Menu déroulant pour les postes. """ - dae_ = dae.Poste.objects.ma_region_ou_service(request.user) \ - .filter(id_rh__isnull=True) - copies = dae.Poste.objects.ma_region_ou_service(request.user) \ - .exclude(id_rh__isnull=True) + def _poste_dae_choices(self, request): + """ Menu déroulant pour les postes.""" + postes_dae = dae.Poste.objects.ma_region_ou_service(request.user) \ + .exclude(etat__in=(POSTE_ETAT_FINALISE, )) \ + .annotate(num_dae=Count('dae_dossiers')) \ + .filter(num_dae=0) \ + .order_by('implantation', '-date_debut', ) return [('', '----------')] + \ - sorted([('dae-%s' % p.id, unicode(p)) for p in dae_ | copies], - key=lambda t: t[1]) + [('dae-%s' % p.id, label_poste_display(p)) for p in postes_dae] + + def _poste_rh_choices(self, request): + """ Menu déroulant pour les postes.""" + postes_dae = dae.Poste.objects.exclude(etat__in=(POSTE_ETAT_FINALISE, )) + today = datetime.date.today() + id_poste_dae_commences = [p.id_rh_id for p in postes_dae if p.id_rh is not None] + postes_rh = rh.Poste.objects.ma_region_ou_service(request.user) \ + .exclude(id__in=id_poste_dae_commences) \ + .filter(Q(date_debut__lte=today) & + (Q(date_fin__gte=today) | + Q(date_fin__isnull=True)) + ) \ + .order_by('implantation', '-date_debut', ) + return [('', '----------')] + \ + [('rh-%s' % p.id, label_poste_display(p)) for p in postes_rh] + + def clean(self): + cleaned_data = super(ChoosePosteForm, self).clean() + postes_dae = cleaned_data.get("postes_dae") + postes_rh = cleaned_data.get("postes_rh") + if (postes_dae is u"" and postes_rh is u"") or \ + (postes_dae is not u"" and postes_rh is not u""): + raise forms.ValidationError("Choisissez un poste DAE ou un poste RH") + return cleaned_data + + def redirect(self): + poste_dae_key = self.cleaned_data.get("postes_dae") + if poste_dae_key is not u"": + return redirect(reverse('embauche', args=(poste_dae_key,))) + poste_rh_key = self.cleaned_data.get("postes_rh") + if poste_rh_key is not u"": + return redirect("%s?creer_dossier_dae='M'" % reverse('poste', args=(poste_rh_key,))) class EmployeForm(forms.ModelForm): """ Formulaire des employés. """ @@ -423,7 +761,13 @@ class EmployeForm(forms.ModelForm): self.fields['employe'].choices = _employe_choices(self, request) -class DossierForm(forms.ModelForm): +class DossierForm( + filtered_archived_fields_form_factory( + 'classement', + 'classement_anterieur', + 'classement_titulaire_anterieur', + ), + forms.ModelForm): """ Formulaire des dossiers. """ class Meta: exclude = ('etat', 'employe', 'poste', 'date_debut',) @@ -463,10 +807,25 @@ class DossierWorkflowForm(WorkflowFormMixin): def save(self): super(DossierWorkflowForm, self).save() poste = self.instance.poste + if poste.etat == self._etat_initial: poste.etat = self.instance.etat poste.save() + # créer le commentaire automatique pour le poste associé + commentaire = WorkflowCommentaire() + commentaire.content_object = poste + texte = u"Validation automatique à travers le dossier [%s] de %s\n%s" %( + self.instance.id, + self.instance, + self.data.get('commentaire', ''), + ) + commentaire.texte = texte + commentaire.etat_initial = self.instance._etat_courant + commentaire.etat_final = self.instance.etat + commentaire.owner = self.request.user + commentaire.save() + class ContratForm(forms.ModelForm):