Fix for salaire de base bug
[auf_rh_dae.git] / project / dae / forms.py
index ec466e3..373099f 100644 (file)
@@ -1,9 +1,17 @@
 # -*- encoding: utf-8 -*-
 
 # -*- 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 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
+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, Count
 from django.shortcuts import redirect
 from django.contrib.admin import widgets as admin_widgets
@@ -12,13 +20,29 @@ from ajax_select.fields import AutoCompleteSelectField
 
 from auf.django.references import models as ref
 from auf.django.workflow.forms import WorkflowFormMixin
 
 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 .widgets import ReadOnlyChoiceWidget, ReadOnlyWidget
 from project.dae.workflow import POSTE_ETATS_BOUTONS, POSTE_ETAT_FINALISE
 
 
 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):
     """
     Cette classe permet de fournir l'option initial aux inlineformsets.
 class BaseInlineFormSetWithInitial(BaseInlineFormSet):
     """
     Cette classe permet de fournir l'option initial aux inlineformsets.
@@ -86,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)]
@@ -104,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()
 
@@ -160,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):
@@ -179,11 +198,11 @@ 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 [%s]" % (
+    label = u"%s (%s) %s [%s]" % (
         annee,
         poste.implantation.nom_court,
         nom,
         annee,
         poste.implantation.nom_court,
         nom,
-        poste.type_poste.categorie_emploi.nom,
+        #poste.type_poste.categorie_emploi.nom,
         poste.id,
         )
     return label
         poste.id,
         )
     return label
@@ -207,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
@@ -223,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',)
@@ -250,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':
@@ -274,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,
+    )
 
 
-class PosteForm(forms.ModelForm):
+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(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
@@ -407,7 +707,7 @@ class ChoosePosteForm(forms.Form):
                 .exclude(etat__in=(POSTE_ETAT_FINALISE, )) \
                 .annotate(num_dae=Count('dae_dossiers')) \
                 .filter(num_dae=0) \
                 .exclude(etat__in=(POSTE_ETAT_FINALISE, )) \
                 .annotate(num_dae=Count('dae_dossiers')) \
                 .filter(num_dae=0) \
-                .order_by('-date_debut')
+                .order_by('implantation', '-date_debut', )
 
         return [('', '----------')] + \
                [('dae-%s' % p.id, label_poste_display(p)) for p in postes_dae]
 
         return [('', '----------')] + \
                [('dae-%s' % p.id, label_poste_display(p)) for p in postes_dae]
@@ -415,10 +715,15 @@ class ChoosePosteForm(forms.Form):
     def _poste_rh_choices(self, request):
         """ Menu déroulant pour les postes."""
         postes_dae = dae.Poste.objects.exclude(etat__in=(POSTE_ETAT_FINALISE, ))
     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) \
         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) \
-                .order_by('-date_debut')
+                .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]
 
         return [('', '----------')] + \
                [('rh-%s' % p.id, label_poste_display(p)) for p in postes_rh]
@@ -438,7 +743,7 @@ class ChoosePosteForm(forms.Form):
             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(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" % reverse('poste', args=(poste_rh_key,)))
+            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. """
@@ -456,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',)
@@ -496,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):