Fix for salaire de base bug
[auf_rh_dae.git] / project / dae / forms.py
index f06683d..373099f 100644 (file)
@@ -1,20 +1,46 @@
 # -*- encoding: utf-8 -*-
 
 # -*- encoding: utf-8 -*-
 
+import datetime
+from ordereddict import OrderedDict
+from dateutil.relativedelta import relativedelta
 from django import forms
 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 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.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 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 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):
 
 
 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)
 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()]
 
     # 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)]
         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)
 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()]
     # 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()
 
         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)
 
     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):
 
 
 def label_poste_display(poste):
@@ -177,10 +198,13 @@ def label_poste_display(poste):
         annee = poste.date_debut.year
 
     nom = poste.nom
         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
 
 
     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'})
     )
 
     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
 
     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)
 
 
     recherche = AutoCompleteSelectField('dae_postes', required=False)
 
+    cmp_poste = forms.IntegerField(
+        widget=forms.widgets.HiddenInput,
+        required=False,
+        )
+
     class Meta:
         model = dae.PosteComparaison
         exclude = ('poste',)
     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
 
     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':
     def clean_devise(self):
         devise = self.cleaned_data['devise']
         if devise.code == 'EUR':
@@ -269,12 +316,270 @@ class FlexibleRemunForm(forms.ModelForm):
         else:
             return devise
 
         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
     """ Formulaire des postes. """
 
     # On ne propose que les services actifs
@@ -383,29 +688,62 @@ class PosteForm(forms.ModelForm):
         return cleaned_data
 
 
         return cleaned_data
 
 
-class ChoosePosteForm(forms.ModelForm):
+class ChoosePosteForm(forms.Form):
     class Meta:
     class Meta:
-        model = dae.Poste
         fields = ('poste',)
 
     # La liste des choix est laissée vide. Voir PosteForm.__init__.
         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)
 
     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 [('', '----------')] + \
 
         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. """
 
 class EmployeForm(forms.ModelForm):
     """ Formulaire des employés. """
@@ -423,7 +761,13 @@ class EmployeForm(forms.ModelForm):
         self.fields['employe'].choices = _employe_choices(self, request)
 
 
         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',)
     """ 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
     def save(self):
         super(DossierWorkflowForm, self).save()
         poste = self.instance.poste
+
         if poste.etat == self._etat_initial:
             poste.etat = self.instance.etat
             poste.save()
 
         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):
 
 
 class ContratForm(forms.ModelForm):