Merge branch 'masse-salariale' into dev
authorEric Mc Sween <eric.mcsween@auf.org>
Fri, 15 Jun 2012 19:34:23 +0000 (15:34 -0400)
committerEric Mc Sween <eric.mcsween@auf.org>
Fri, 15 Jun 2012 19:34:23 +0000 (15:34 -0400)
Conflicts:
project/rh/views.py

buildout.cfg
project/rh/forms.py
project/rh/managers.py
project/rh/models.py
project/rh/ods.py
project/rh/static/rh/FixedHeader.min.js [new file with mode: 0644]
project/rh/templates/rh/rapports/masse_salariale.html
project/rh/views.py
project/settings.py
versions.cfg

index 0213d45..076ff10 100644 (file)
@@ -13,8 +13,9 @@ find-links = http://pypi.auf.org/simple/auf.recipe.django/
     http://pypi.auf.org/simple/auf.django.admingroup/
     http://pypi.auf.org/simple/auf.django.permissions/
     http://pypi.auf.org/simple/auf.django.emploi/
-    http://pypi.auf.org/django-alphafilter/
     http://pypi.auf.org/simple/auf.django.references/
+    http://pypi.auf.org/simple/django-alphafilter/
+    http://pypi.auf.org/simple/odsgen/
 develop = src/qbe
     src/auf.django.metadata
 eggs =
@@ -40,7 +41,7 @@ eggs =
     auf.django.skin
     auf.django.workflow
     auf.recipe.django
-    odfpy
+    odsgen
     django-picklefield
     pygraphviz
     simplejson
index a1671a2..c2d1aff 100644 (file)
@@ -1,9 +1,15 @@
 # -*- encoding: utf-8 -*-
 
-from django import forms
+from datetime import date
+
 from ajax_select.fields import AutoCompleteSelectField
-from project.rh.models import Dossier, Contrat, AyantDroit, Employe, \
-        ResponsableImplantation
+from auf.django.references import models as ref
+from django import forms
+from django.db.models import Min
+
+from project.groups import get_employe_from_user
+from project.permissions import user_gere_obj_de_sa_region
+from project.rh import models as rh
 
 
 class AjaxSelect(object):
@@ -38,13 +44,13 @@ class FormDate(object):
 class DossierForm(forms.ModelForm, FormDate):
 
     class Model:
-        model = Dossier
+        model = rh.Dossier
 
 
 class ContratForm(forms.ModelForm, FormDate):
 
     class Model:
-        model = Contrat
+        model = rh.Contrat
 
 
 class AyantDroitForm(forms.ModelForm, AjaxSelect):
@@ -56,11 +62,10 @@ class AyantDroitForm(forms.ModelForm, AjaxSelect):
         self.fields['date_naissance'].widget = forms.widgets.DateInput()
 
     class Meta:
-        model = AyantDroit
+        model = rh.AyantDroit
 
 
 class EmployeAdminForm(forms.ModelForm, AjaxSelect):
-
     nationalite = AutoCompleteSelectField(
         'pays', help_text="Taper le nom ou le code du pays", required=False
     )
@@ -69,7 +74,7 @@ class EmployeAdminForm(forms.ModelForm, AjaxSelect):
     )
 
     class Meta:
-        model = Employe
+        model = rh.Employe
 
     def __init__(self, *args, **kwargs):
         super(EmployeAdminForm, self).__init__(*args, **kwargs)
@@ -77,8 +82,41 @@ class EmployeAdminForm(forms.ModelForm, AjaxSelect):
 
 
 class ResponsableInlineForm(forms.ModelForm):
-    employes_actifs = Employe.objects.actifs()
+    employes_actifs = rh.Employe.objects.actifs()
     employe = forms.ModelChoiceField(queryset=employes_actifs)
 
     class Meta:
-        model = ResponsableImplantation
+        model = rh.ResponsableImplantation
+
+
+class MasseSalarialeForm(forms.Form):
+    region = forms.ModelChoiceField(
+        label=u'Région', queryset=ref.Region.objects.all(), required=False
+    )
+    implantation = forms.ModelChoiceField(
+        label=u'Implantation', queryset=ref.Implantation.objects.all(),
+        required=False
+    )
+
+    def __init__(self, user, *args, **kwargs):
+        super(MasseSalarialeForm, self).__init__(*args, **kwargs)
+        min_date = rh.Remuneration.objects \
+                .aggregate(Min('date_debut'))['date_debut__min']
+        years = range(
+            date.today().year,
+            min_date.year if min_date else 1997,
+            -1
+        )
+        self.fields['annee'] = forms.TypedChoiceField(
+            label='Année', choices=zip(years, years), coerce=int,
+            required=False
+        )
+        if user_gere_obj_de_sa_region(user):
+            employe = get_employe_from_user(user)
+            self.fields['region'].queryset = ref.Region.objects.filter(
+                id=employe.implantation.region.id
+            )
+            self.fields['implantation'].queryset = \
+                    ref.Implantation.objects.filter(
+                        region=employe.implantation.region
+                    )
index 15ee63e..605276e 100644 (file)
@@ -1,23 +1,17 @@
 # -*- encoding: utf-8 -*-
 
-import datetime
 from datetime import date
 
 from django.db import models
 from django.db.models import Q
+from django.db.models.query import QuerySet
 
 from auf.django.metadata.managers import NoDeleteManager, NoDeleteQuerySet
 
 from project.groups import get_employe_from_user
-from project.groups import grp_administrateurs, \
-                     grp_directeurs_bureau, \
-                     grp_drh, \
-                     grp_drh2, \
-                     grp_accior, \
-                     grp_abf, \
-                     grp_haute_direction, \
-                     grp_service_utilisateurs, \
-                     grp_correspondants_rh
+from project.groups import \
+        grp_drh, grp_drh2, grp_accior, grp_abf, grp_haute_direction, \
+        grp_service_utilisateurs
 
 
 class SecurityManager(models.Manager):
@@ -28,8 +22,8 @@ class SecurityManager(models.Manager):
     def ma_region_ou_service(self, user):
         """
         Filtrage des postes en fonction du user connecté (region / service)
-        On s'intéresse aussi au groupe auquel appartient le user car certains groupes
-        peuvent tout voir.
+        On s'intéresse aussi au groupe auquel appartient le user car
+        certains groupes peuvent tout voir.
         """
         employe = get_employe_from_user(user)
 
@@ -37,11 +31,12 @@ class SecurityManager(models.Manager):
         # TRAITEMENT NORMAL
         ############################################
         # REGION
-        q = Q(**{ self.prefixe_implantation : employe.implantation.region })
+        q = Q(**{self.prefixe_implantation: employe.implantation.region})
 
         # SERVICE
-        if self.prefixe_service and grp_service_utilisateurs in user.groups.all():
-            q = q | Q(**{ self.prefixe_service : employe.service})
+        if self.prefixe_service \
+           and grp_service_utilisateurs in user.groups.all():
+            q = q | Q(**{self.prefixe_service: employe.service})
 
         liste = self.get_query_set().filter(q)
 
@@ -75,35 +70,55 @@ class SecurityManager(models.Manager):
         return liste
 
 
+class ActifsQuerySet(QuerySet):
+
+    def _actifs(self, debut_field, fin_field, date_min=None, date_max=None,
+                annee=None):
+        qs = self
+        if annee:
+            janvier = date(annee, 1, 1)
+            decembre = date(annee, 12, 31)
+            date_min = max(janvier, date_min) if date_min else janvier
+            date_max = min(decembre, date_max) if date_max else decembre
+        if not date_min and not date_max:
+            date_min = date_max = date.today()
+        if date_min:
+            qs = qs.filter(
+                Q(**{fin_field + '__gte': date_min}) |
+                Q(**{fin_field: None})
+            )
+        if date_max:
+            qs = qs.filter(
+                Q(**{debut_field + '__lte': date_max}) |
+                Q(**{debut_field: None})
+            )
+        return qs
+
+    def actifs(self, *args, **kwargs):
+        return self._actifs('date_debut', 'date_fin', *args, **kwargs)
+
+
+class PosteQuerySet(NoDeleteQuerySet, ActifsQuerySet):
+    pass
+
+
 class PosteManager(SecurityManager, NoDeleteManager):
-    """
-    Chargement de tous les objets FK existants sur chaque QuerySet.
-    """
     prefixe_service = "service"
     prefixe_implantation = "implantation__region"
 
-    def actifs(self):
-        q_actif = Q(date_fin__gt=datetime.datetime.now()) | Q(date_fin__isnull=True)
-        return super(PosteManager, self).get_query_set().filter(q_actif)
+    def get_query_set(self):
+        return PosteQuerySet(self.model).filter(supprime=False) \
+                .select_related('type_poste')
+
+    def actifs(self, *args, **kwargs):
+        return self.get_query_set().actifs(*args, **kwargs)
 
     def ma_region_ou_service(self, user):
         return super(PosteManager, self).ma_region_ou_service(user)
 
-    def get_query_set(self):
-        fkeys = (
-            #'id_rh',
-            #'responsable',
-            #'implantation',
-            #'implantation.bureau_rattachement',
-            'type_poste',
-            #'service',
-            #'classement_min',
-            #'classement_max',
-            #'valeur_point_min',
-            #'valeur_point_max',
-        )
-        return super(PosteManager, self).get_query_set() \
-                                        .select_related(*fkeys).all()
+
+class DossierQuerySet(NoDeleteQuerySet, ActifsQuerySet):
+    pass
 
 
 class DossierManager(SecurityManager, NoDeleteManager):
@@ -111,36 +126,39 @@ class DossierManager(SecurityManager, NoDeleteManager):
     prefixe_implantation = "poste__implantation__region"
 
     def get_query_set(self):
-        fkeys = (
-            'poste',
-            'employe',
-        )
-        return super(DossierManager, self).get_query_set() \
-                                        .select_related(*fkeys).all()
+        return DossierQuerySet(self.model) \
+                .filter(supprime=False) \
+                .select_related('poste', 'employe')
 
-    def ma_region_ou_service(self, user):
-        return super(DossierManager, self).ma_region_ou_service(user)
+    def actifs(self, *args, **kwargs):
+        return self.get_query_set().actifs(*args, **kwargs)
+
+
+class RemunerationQuerySet(NoDeleteQuerySet, ActifsQuerySet):
+
+    def actifs(self, *args, **kwargs):
+        return self \
+                ._actifs('date_debut', 'date_fin', *args, **kwargs) \
+                ._actifs(
+                    'dossier__date_debut', 'dossier__date_fin', *args, **kwargs
+                )
+
+
+class RemunerationManager(NoDeleteManager):
+
+    def get_query_set(self):
+        return RemunerationQuerySet(self.model).filter(supprime=False)
+
+    def actifs(self, *args, **kwargs):
+        return self.get_query_set().actifs(*args, **kwargs)
 
 
-class EmployeQuerySet(NoDeleteQuerySet):
+class EmployeQuerySet(NoDeleteQuerySet, ActifsQuerySet):
+
     def actifs(self, date_min=None, date_max=None, annee=None):
-        qs = self
-        if annee:
-            janvier = date(annee, 1, 1)
-            decembre = date(annee, 12, 31)
-            date_min = max(janvier, date_min) if date_min else janvier
-            date_max = min(decembre, date_max) if date_max else decembre
-        if not date_min and not date_max:
-            date_min = date_max = date.today()
-        if date_min:
-            qs = qs.filter(
-                Q(rh_dossiers__date_fin__gte=date_min) | Q(rh_dossiers__date_fin=None)
-            )
-        if date_max:
-            qs = qs.filter(
-                Q(rh_dossiers__date_debut__lte=date_max) | Q(rh_dossiers__date_debut=None)
-            )
-        return qs.distinct()
+        return self._actifs('dossiers__date_debut', 'dossiers__date_fin') \
+                .distinct()
+
 
 class EmployeManager(NoDeleteManager):
     def get_query_set(self):
@@ -150,6 +168,7 @@ class EmployeManager(NoDeleteManager):
     def actifs(self, *args, **kwargs):
         return self.get_query_set().actifs(*args, **kwargs)
 
+
 class PosteComparaisonManager(SecurityManager):
     use_for_related_fields = True
     prefixe_implantation = "implantation__region"
@@ -163,8 +182,10 @@ class DossierComparaisonManager(SecurityManager):
 class DeviseManager(NoDeleteManager):
     pass
 
+
 class ServiceManager(NoDeleteManager):
     pass
 
+
 class TypeRemunerationManager(NoDeleteManager):
     pass
index 26f8644..dbc68d0 100644 (file)
@@ -22,7 +22,7 @@ from project.rh.managers import \
         PosteManager, DossierManager, EmployeManager, \
         DossierComparaisonManager, \
         PosteComparaisonManager, DeviseManager, ServiceManager, \
-        TypeRemunerationManager
+        TypeRemunerationManager, RemunerationManager
 from project.rh.validators import validate_date_passee
 
 
@@ -440,7 +440,6 @@ class PosteCommentaire(Commentaire):
     )
 
 
-
 ### EMPLOYÉ/PERSONNE
 
 class Employe(AUFMetadata):
@@ -1028,7 +1027,6 @@ class DossierPiece(DossierPiece_):
     )
 
 
-
 class DossierCommentaire(Commentaire):
     dossier = models.ForeignKey(
         Dossier, db_column='dossier', related_name='commentaires'
@@ -1091,6 +1089,8 @@ class RemunerationMixin(AUFMetadata):
     date_debut = models.DateField(u"date de début", null=True, blank=True)
     date_fin = models.DateField(u"date de fin", null=True, blank=True)
 
+    objects = RemunerationManager()
+
     class Meta:
         abstract = True
         ordering = ['type__nom', '-date_fin']
index 07f0976..bb76ae5 100644 (file)
 # encoding: utf-8
 
-from decimal import Decimal
-
-import odf.opendocument
-import odf.style
-import odf.table
-from odf.style import Style, MasterPage, PageLayout, PageLayoutProperties, \
-        TextProperties, GraphicProperties, ParagraphProperties, \
-        DrawingPageProperties
-
-
-class Separator():
-
-    def __unicode__(self):
-        return u""
-
-    def __str__(self):
-        return ""
-
-
-def valuetype(val):
-    valuetype = "string"
-    if isinstance(val, str):
-        valuetype = "string"
-    if isinstance(val, (int, float, Decimal)):
-        valuetype = "float"
-    if isinstance(val, bool):
-        valuetype = "boolean"
-
-    return valuetype
-
-
-class Wrapper(object):
-
-    def __init__(self, *args, **kwargs):
-        self.__wrapped = self._wrapper_constructor(*args, **kwargs)
-
-    def __getattr__(self, attr):
-        return getattr(self.__wrapped, attr)
-
-
-class OpenDocumentSpreadsheet(Wrapper):
-    _wrapper_constructor = staticmethod(
-        odf.opendocument.OpenDocumentSpreadsheet
+import odsgen
+
+
+def masse_salariale(lignes, annee, titres_traitements, titres_indemnites,
+                    titres_primes, titres_charges, masse_salariale_totale):
+    doc = odsgen.Document()
+    doc.add_iso_date_style('iso-date')
+
+    table = doc.add_table(name=u'Masse salariale')
+    for width in (
+        ['1.75cm', '5cm', '4cm', '3cm', '3.5cm', '5cm', '4cm', '6cm',
+         '14cm', '2.5cm', '1.5cm', '4.5cm', '3cm', '1.5cm', '3.75cm',
+         '0.5cm', '2.5cm', '2cm', '3cm', '0.5cm', '1.5cm', '4cm',
+         '3.25cm', '5cm', '0.5cm', '4.25cm'] +
+        ['5cm'] * len(titres_traitements) + ['4cm', '0.5cm'] +
+        ['5cm'] * len(titres_indemnites) + ['4cm', '0.5cm'] +
+        ['5cm'] * len(titres_primes) + ['4cm', '0.5cm'] +
+        ['5cm'] * len(titres_charges) + ['4cm', '0.5cm'] +
+        ['3.75cm'] * 4 + ['0.5cm', '2.75cm', '4cm']
+    ):
+        table.add_column(columnwidth=width)
+
+    row = table.add_row()
+    row.add_cells([
+        u'Bureau', u'Pays', u'Implantation', u'Valeur du point',
+        u"Numéro d'employé", u'Nom', u'Prénom', u'Type de poste',
+        u'Intitulé du poste', u'Niveau actuel', u'Points',
+        u'Régime de travail annuel', u'Local / Expatrié', u'Statut',
+        u'Date de fin de contrat'
+    ], fontweight='bold')
+    row.add_cell(backgroundcolor='#d3d3d3')
+    row.add_cells([
+        u'Date de début', u'Date de fin', u'Nombre de jours'
+    ], fontweight='bold')
+    row.add_cell(backgroundcolor='#d3d3d3')
+    row.add_cells([
+        u'Devise', u'Salaire BSTG ANNUEL', u'Salaire BSTG EUR',
+        u'Organisme BSTG'
+    ], fontweight='bold')
+    row.add_cell(backgroundcolor='#d3d3d3')
+    row.add_cells(
+        [u'Salaire théorique annuel'] + titres_traitements +
+        [u'Total des traitements'],
+        backgroundcolor='#ecab44', fontweight='bold'
+    )
+    row.add_cell(backgroundcolor='#d3d3d3')
+    row.add_cells(
+        titres_indemnites + [u'Total des indemnités'],
+        backgroundcolor='#fff840', fontweight='bold'
+    )
+    row.add_cell(backgroundcolor='#d3d3d3')
+    row.add_cells(
+        titres_primes + [u'Total des primes'],
+        backgroundcolor='#d7fb0f', fontweight='bold'
+    )
+    row.add_cell(backgroundcolor='#d3d3d3')
+    row.add_cells(
+        titres_charges + [u'Total des charges'],
+        backgroundcolor='#fb680f', fontweight='bold'
+    )
+    row.add_cell(backgroundcolor='#d3d3d3')
+    row.add_cell(
+        u'Total des traitements', backgroundcolor='#ecab44',
+        fontweight='bold'
+    )
+    row.add_cell(
+        u'Total des indemnités', backgroundcolor='#fff840',
+        fontweight='bold'
+    )
+    row.add_cell(
+        u'Total des primes', backgroundcolor='#d7fb0f',
+        fontweight='bold'
+    )
+    row.add_cell(
+        u'Total des charges', backgroundcolor='#fb680f',
+        fontweight='bold'
+    )
+    row.add_cell(backgroundcolor='#d3d3d3')
+    row.add_cells(
+        [u'Masse salariale', u'Masse salariale EUR'],
+        backgroundcolor='#e6c6ed', fontweight='bold'
     )
 
-    def __init__(self, *args, **kwargs):
-        super(OpenDocumentSpreadsheet, self).__init__(*args, **kwargs)
-        self._automatic_style_idx = 0
-
-    def add_table(self, **kwargs):
-        table = Table(**kwargs)
-        table._doc = self
-        self.spreadsheet.addElement(table)
-        return table
-
-    def add_automatic_style(self, **kwargs):
-        name = 'auto_style_%d' % self._automatic_style_idx
-        style = odf.style.Style(name=name, **kwargs)
-        self.automaticstyles.addElement(style)
-        self._automatic_style_idx += 1
-        return style
-
-
-class Table(Wrapper):
-    _wrapper_constructor = staticmethod(odf.table.Table)
-
-    def add_row(self, values=[], **kwargs):
-         # attributs appartenant à table-column-poperties
-#        props = {}
-#        for attr in ['rowheight']:
-#            if attr in kwargs:
-#                props[attr] = kwargs.pop(attr)
-
-        style = self._doc.add_automatic_style(family='table-row')
-        if 'rowheight' in kwargs:
-            style.addElement(odf.style.TableRowProperties(
-                rowheight=kwargs['rowheight']))
-            kwargs['stylename'] = style.getAttribute('name')
-            del kwargs['rowheight']
-
-        style = {}
-
-        row = TableRow(**kwargs)
-        row._doc = self._doc
-        for value in values:
-            row.add_cell(value, verticalalign='middle', **style)
-        self.addElement(row)
-        return row
-
-    def add_column(self, **kwargs):
-
-        # attributs appartenant à table-column-poperties
-        props = {}
-        for attr in ['columnwidth']:
-            if attr in kwargs:
-                props[attr] = kwargs.pop(attr)
-
-        if props:
-            style = self._doc.add_automatic_style(family='table-column')
-            style.addElement(odf.style.TableColumnProperties(**props))
-            kwargs['stylename'] = style.getAttribute('name')
-        col = odf.table.TableColumn(**kwargs)
-        self.addElement(col)
-        return col
-
-
-class TableRow(Wrapper):
-    _wrapper_constructor = staticmethod(odf.table.TableRow)
-
-    def add_cell(self, value=None, **kwargs):
-        if value:
-            if isinstance(value, (basestring, unicode)):
-                kwargs['stringvalue'] = unicode(value)
-            elif isinstance(value, (int, float, Decimal)):
-                kwargs['valuetype'] = "float"
-                kwargs['value'] = float(value)
-            elif type(value) == type(None) or isinstance(value, Separator):
-                kwargs['stringvalue'] = u""
-            else:
-                kwargs['stringvalue'] = unicode(value)
-
-        style = self._doc.add_automatic_style(family='table-cell')
-        if 'verticalalign' in kwargs:
-            style.addElement(odf.style.TableCellProperties(
-                verticalalign=kwargs['verticalalign'], wrapoption='wrap'))
-            del kwargs['verticalalign']
-
-        if isinstance(value, Separator) or type(value) == type(Separator()):
-            style.addElement(odf.style.TableCellProperties(
-                backgroundcolor='#D3D3D3'))
-
-        kwargs['stylename'] = style.getAttribute('name')
-#        props = {}
-#        if 'fontweight' in kwargs:
-#            props['fontweight'] = kwargs.pop('fontweight')
-#        if 'stringvalue' in kwargs:
-#            props['stringvalue'] = kwargs.pop('stringvalue')
-
-        cell = odf.table.TableCell(**kwargs)
-#        if 'fontweight' in props:
-#            tablecontents = Style(name="Bold", family="paragraph")
-#            tablecontents.addElement(TextProperties(fontweight="bold"))
-#            self._doc.styles.addElement(tablecontents)
-
-#        if 'stringvalue' in props:
-#            p = P(stylename='Bold',text=props['stringvalue'])
-#            cell.addElement(p)
-
-        self.addElement(cell)
-        return cell
+    for ligne in lignes:
+        row = table.add_row()
+        row.add_cells([
+            ligne['poste'].implantation.region.code,
+            ligne['poste'].implantation.adresse_physique_pays.nom,
+            ligne['poste'].implantation.nom_court
+        ])
+        row.add_cell(ligne['valeur_point'], decimalplaces=2)
+        row.add_cells([
+            ligne['dossier'].employe.id or 'VACANT',
+            ligne['dossier'].employe.nom,
+            ligne['dossier'].employe.prenom,
+            ligne['poste'].type_poste.nom,
+            ligne['poste'].nom,
+            unicode(ligne['dossier'].classement),
+            ligne['dossier'].classement
+            and ligne['dossier'].classement.coefficient,
+            ligne['regime_travail'],
+            ligne['local_expatrie'],
+            ligne['dossier'].statut.code,
+        ])
+        row.add_cell(ligne['dossier'].date_fin, datastylename='iso-date')
+        row.add_cell(backgroundcolor='#d3d3d3')
+        row.add_cells([
+            ligne['date_debut'], ligne['date_fin']
+        ], datastylename='iso-date')
+        row.add_cell(ligne['jours'])
+        row.add_cell(backgroundcolor='#d3d3d3')
+        row.add_cell(ligne['devise'])
+        row.add_cells([
+            ligne['salaire_bstg'], ligne['salaire_bstg_eur']
+        ], decimalplaces=2)
+        row.add_cell(
+            ligne['dossier'].organisme_bstg
+            and ligne['dossier'].organisme_bstg.nom,
+        )
+        row.add_cell(backgroundcolor='#d3d3d3')
+        row.add_cells(
+            [ligne['salaire_theorique']] + ligne['traitements'] +
+            [ligne['total_traitements']],
+            decimalplaces=2
+        )
+        row.add_cell(backgroundcolor='#d3d3d3')
+        row.add_cells(
+            ligne['indemnites'] + [ligne['total_indemnites']],
+            decimalplaces=2
+        )
+        row.add_cell(backgroundcolor='#d3d3d3')
+        row.add_cells(
+            ligne['primes'] + [ligne['total_primes']],
+            decimalplaces=2
+        )
+        row.add_cell(backgroundcolor='#d3d3d3')
+        row.add_cells(
+            ligne['charges'] + [ligne['total_charges']],
+            decimalplaces=2
+        )
+        row.add_cell(backgroundcolor='#d3d3d3')
+        row.add_cells([
+            ligne['total_traitements'], ligne['total_indemnites'],
+            ligne['total_primes'], ligne['total_charges']
+        ], decimalplaces=2)
+        row.add_cell(backgroundcolor='#d3d3d3')
+        row.add_cells([
+            ligne['masse_salariale'], ligne['masse_salariale_eur']
+        ], decimalplaces=2)
+    return doc
diff --git a/project/rh/static/rh/FixedHeader.min.js b/project/rh/static/rh/FixedHeader.min.js
new file mode 100644 (file)
index 0000000..7bd887f
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * File:        FixedHeader.min.js
+ * Version:     2.0.6
+ * Author:      Allan Jardine (www.sprymedia.co.uk)
+ * 
+ * Copyright 2009-2011 Allan Jardine, all rights reserved.
+ *
+ * This source file is free software, under either the GPL v2 license or a
+ * BSD (3 point) style license, as supplied with this software.
+ * 
+ * This source file is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
+ */
+var FixedHeader=function(a,c){if("function"!=typeof this.fnInit)alert("FixedHeader warning: FixedHeader must be initialised with the 'new' keyword.");else{var b={aoCache:[],oSides:{top:!0,bottom:!1,left:!1,right:!1},oZIndexes:{top:104,bottom:103,left:102,right:101},oMes:{iTableWidth:0,iTableHeight:0,iTableLeft:0,iTableRight:0,iTableTop:0,iTableBottom:0},oOffset:{top:0},nTable:null,bUseAbsPos:!1,bFooter:!1};this.fnGetSettings=function(){return b};this.fnUpdate=function(){this._fnUpdateClones();this._fnUpdatePositions()};
+this.fnPosition=function(){this._fnUpdatePositions()};this.fnInit(a,c);if("function"==typeof a.fnSettings)a._oPluginFixedHeader=this}};
+FixedHeader.prototype={fnInit:function(a,c){var b=this.fnGetSettings(),d=this;this.fnInitSettings(b,c);if("function"==typeof a.fnSettings){if("functon"==typeof a.fnVersionCheck&&!0!==a.fnVersionCheck("1.6.0")){alert("FixedHeader 2 required DataTables 1.6.0 or later. Please upgrade your DataTables installation");return}var e=a.fnSettings();if(""!=e.oScroll.sX||""!=e.oScroll.sY){alert("FixedHeader 2 is not supported with DataTables' scrolling mode at this time");return}b.nTable=e.nTable;e.aoDrawCallback.push({fn:function(){FixedHeader.fnMeasure();
+d._fnUpdateClones.call(d);d._fnUpdatePositions.call(d)},sName:"FixedHeader"})}else b.nTable=a;b.bFooter=0<$(">tfoot",b.nTable).length?!0:!1;b.bUseAbsPos=jQuery.browser.msie&&("6.0"==jQuery.browser.version||"7.0"==jQuery.browser.version);b.oSides.top&&b.aoCache.push(d._fnCloneTable("fixedHeader","FixedHeader_Header",d._fnCloneThead));b.oSides.bottom&&b.aoCache.push(d._fnCloneTable("fixedFooter","FixedHeader_Footer",d._fnCloneTfoot));b.oSides.left&&b.aoCache.push(d._fnCloneTable("fixedLeft","FixedHeader_Left",
+d._fnCloneTLeft));b.oSides.right&&b.aoCache.push(d._fnCloneTable("fixedRight","FixedHeader_Right",d._fnCloneTRight));FixedHeader.afnScroll.push(function(){d._fnUpdatePositions.call(d)});jQuery(window).resize(function(){FixedHeader.fnMeasure();d._fnUpdateClones.call(d);d._fnUpdatePositions.call(d)});FixedHeader.fnMeasure();d._fnUpdateClones();d._fnUpdatePositions()},fnInitSettings:function(a,c){if("undefined"!=typeof c){if("undefined"!=typeof c.top)a.oSides.top=c.top;if("undefined"!=typeof c.bottom)a.oSides.bottom=
+c.bottom;if("undefined"!=typeof c.left)a.oSides.left=c.left;if("undefined"!=typeof c.right)a.oSides.right=c.right;if("undefined"!=typeof c.zTop)a.oZIndexes.top=c.zTop;if("undefined"!=typeof c.zBottom)a.oZIndexes.bottom=c.zBottom;if("undefined"!=typeof c.zLeft)a.oZIndexes.left=c.zLeft;if("undefined"!=typeof c.zRight)a.oZIndexes.right=c.zRight;if("undefined"!=typeof c.offsetTop)a.oOffset.top=c.offsetTop}a.bUseAbsPos=jQuery.browser.msie&&("6.0"==jQuery.browser.version||"7.0"==jQuery.browser.version)},
+_fnCloneTable:function(a,c,b){var d=this.fnGetSettings(),e;if("absolute"!=jQuery(d.nTable.parentNode).css("position"))d.nTable.parentNode.style.position="relative";e=d.nTable.cloneNode(!1);e.removeAttribute("id");var f=document.createElement("div");f.style.position="absolute";f.style.top="0px";f.style.left="0px";f.className+=" FixedHeader_Cloned "+a+" "+c;if("fixedHeader"==a)f.style.zIndex=d.oZIndexes.top;if("fixedFooter"==a)f.style.zIndex=d.oZIndexes.bottom;if("fixedLeft"==a)f.style.zIndex=d.oZIndexes.left;
+else if("fixedRight"==a)f.style.zIndex=d.oZIndexes.right;e.style.margin="0";f.appendChild(e);document.body.appendChild(f);return{nNode:e,nWrapper:f,sType:a,sPosition:"",sTop:"",sLeft:"",fnClone:b}},_fnMeasure:function(){var a=this.fnGetSettings(),c=a.oMes,b=jQuery(a.nTable),d=b.offset(),e=this._fnSumScroll(a.nTable.parentNode,"scrollTop");this._fnSumScroll(a.nTable.parentNode,"scrollLeft");c.iTableWidth=b.outerWidth();c.iTableHeight=b.outerHeight();c.iTableLeft=d.left+a.nTable.parentNode.scrollLeft;
+c.iTableTop=d.top+e;c.iTableRight=c.iTableLeft+c.iTableWidth;c.iTableRight=FixedHeader.oDoc.iWidth-c.iTableLeft-c.iTableWidth;c.iTableBottom=FixedHeader.oDoc.iHeight-c.iTableTop-c.iTableHeight},_fnSumScroll:function(a,c){for(var b=a[c];(a=a.parentNode)&&!("HTML"==a.nodeName||"BODY"==a.nodeName);)b=a[c];return b},_fnUpdatePositions:function(){var a=this.fnGetSettings();this._fnMeasure();for(var c=0,b=a.aoCache.length;c<b;c++)"fixedHeader"==a.aoCache[c].sType?this._fnScrollFixedHeader(a.aoCache[c]):
+"fixedFooter"==a.aoCache[c].sType?this._fnScrollFixedFooter(a.aoCache[c]):"fixedLeft"==a.aoCache[c].sType?this._fnScrollHorizontalLeft(a.aoCache[c]):this._fnScrollHorizontalRight(a.aoCache[c])},_fnUpdateClones:function(){for(var a=this.fnGetSettings(),c=0,b=a.aoCache.length;c<b;c++)a.aoCache[c].fnClone.call(this,a.aoCache[c])},_fnScrollHorizontalRight:function(a){var c=this.fnGetSettings(),b=c.oMes,d=FixedHeader.oWin,e=FixedHeader.oDoc,f=a.nWrapper,g=jQuery(f).outerWidth();d.iScrollRight<b.iTableRight?
+(this._fnUpdateCache(a,"sPosition","absolute","position",f.style),this._fnUpdateCache(a,"sTop",b.iTableTop+"px","top",f.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft+b.iTableWidth-g+"px","left",f.style)):b.iTableLeft<e.iWidth-d.iScrollRight-g?c.bUseAbsPos?(this._fnUpdateCache(a,"sPosition","absolute","position",f.style),this._fnUpdateCache(a,"sTop",b.iTableTop+"px","top",f.style),this._fnUpdateCache(a,"sLeft",e.iWidth-d.iScrollRight-g+"px","left",f.style)):(this._fnUpdateCache(a,"sPosition","fixed",
+"position",f.style),this._fnUpdateCache(a,"sTop",b.iTableTop-d.iScrollTop+"px","top",f.style),this._fnUpdateCache(a,"sLeft",d.iWidth-g+"px","left",f.style)):(this._fnUpdateCache(a,"sPosition","absolute","position",f.style),this._fnUpdateCache(a,"sTop",b.iTableTop+"px","top",f.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft+"px","left",f.style))},_fnScrollHorizontalLeft:function(a){var c=this.fnGetSettings(),b=c.oMes,d=FixedHeader.oWin,e=a.nWrapper,f=jQuery(e).outerWidth();d.iScrollLeft<b.iTableLeft?
+(this._fnUpdateCache(a,"sPosition","absolute","position",e.style),this._fnUpdateCache(a,"sTop",b.iTableTop+"px","top",e.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft+"px","left",e.style)):d.iScrollLeft<b.iTableLeft+b.iTableWidth-f?c.bUseAbsPos?(this._fnUpdateCache(a,"sPosition","absolute","position",e.style),this._fnUpdateCache(a,"sTop",b.iTableTop+"px","top",e.style),this._fnUpdateCache(a,"sLeft",d.iScrollLeft+"px","left",e.style)):(this._fnUpdateCache(a,"sPosition","fixed","position",e.style),
+this._fnUpdateCache(a,"sTop",b.iTableTop-d.iScrollTop+"px","top",e.style),this._fnUpdateCache(a,"sLeft","0px","left",e.style)):(this._fnUpdateCache(a,"sPosition","absolute","position",e.style),this._fnUpdateCache(a,"sTop",b.iTableTop+"px","top",e.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft+b.iTableWidth-f+"px","left",e.style))},_fnScrollFixedFooter:function(a){var c=this.fnGetSettings(),b=c.oMes,d=FixedHeader.oWin,e=FixedHeader.oDoc,f=a.nWrapper,g=jQuery("thead",c.nTable).outerHeight(),h=jQuery(f).outerHeight();
+d.iScrollBottom<b.iTableBottom?(this._fnUpdateCache(a,"sPosition","absolute","position",f.style),this._fnUpdateCache(a,"sTop",b.iTableTop+b.iTableHeight-h+"px","top",f.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft+"px","left",f.style)):d.iScrollBottom<b.iTableBottom+b.iTableHeight-h-g?c.bUseAbsPos?(this._fnUpdateCache(a,"sPosition","absolute","position",f.style),this._fnUpdateCache(a,"sTop",e.iHeight-d.iScrollBottom-h+"px","top",f.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft+"px","left",
+f.style)):(this._fnUpdateCache(a,"sPosition","fixed","position",f.style),this._fnUpdateCache(a,"sTop",d.iHeight-h+"px","top",f.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft-d.iScrollLeft+"px","left",f.style)):(this._fnUpdateCache(a,"sPosition","absolute","position",f.style),this._fnUpdateCache(a,"sTop",b.iTableTop+h+"px","top",f.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft+"px","left",f.style))},_fnScrollFixedHeader:function(a){for(var c=this.fnGetSettings(),b=c.oMes,d=FixedHeader.oWin,e=
+a.nWrapper,f=0,g=c.nTable.getElementsByTagName("tbody"),h=0;h<g.length;++h)f+=g[h].offsetHeight;b.iTableTop>d.iScrollTop+c.oOffset.top?(this._fnUpdateCache(a,"sPosition","absolute","position",e.style),this._fnUpdateCache(a,"sTop",b.iTableTop+"px","top",e.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft+"px","left",e.style)):d.iScrollTop+c.oOffset.top>b.iTableTop+f?(this._fnUpdateCache(a,"sPosition","absolute","position",e.style),this._fnUpdateCache(a,"sTop",b.iTableTop+f+"px","top",e.style),this._fnUpdateCache(a,
+"sLeft",b.iTableLeft+"px","left",e.style)):c.bUseAbsPos?(this._fnUpdateCache(a,"sPosition","absolute","position",e.style),this._fnUpdateCache(a,"sTop",d.iScrollTop+"px","top",e.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft+"px","left",e.style)):(this._fnUpdateCache(a,"sPosition","fixed","position",e.style),this._fnUpdateCache(a,"sTop",c.oOffset.top+"px","top",e.style),this._fnUpdateCache(a,"sLeft",b.iTableLeft-d.iScrollLeft+"px","left",e.style))},_fnUpdateCache:function(a,c,b,d,e){a[c]!=b&&(e[d]=
+b,a[c]=b)},_fnCloneThead:function(a){var c=this.fnGetSettings(),b=a.nNode;for(a.nWrapper.style.width=jQuery(c.nTable).outerWidth()+"px";0<b.childNodes.length;)jQuery("thead th",b).unbind("click"),b.removeChild(b.childNodes[0]);a=jQuery("thead",c.nTable).clone(!0)[0];b.appendChild(a);jQuery("thead>tr th",c.nTable).each(function(a){jQuery("thead>tr th:eq("+a+")",b).width(jQuery(this).width())});jQuery("thead>tr td",c.nTable).each(function(a){jQuery("thead>tr td:eq("+a+")",b).width(jQuery(this).width())})},
+_fnCloneTfoot:function(a){var c=this.fnGetSettings(),b=a.nNode;for(a.nWrapper.style.width=jQuery(c.nTable).outerWidth()+"px";0<b.childNodes.length;)b.removeChild(b.childNodes[0]);a=jQuery("tfoot",c.nTable).clone(!0)[0];b.appendChild(a);jQuery("tfoot:eq(0)>tr th",c.nTable).each(function(a){jQuery("tfoot:eq(0)>tr th:eq("+a+")",b).width(jQuery(this).width())});jQuery("tfoot:eq(0)>tr td",c.nTable).each(function(a){jQuery("tfoot:eq(0)>tr th:eq("+a+")",b)[0].style.width(jQuery(this).width())})},_fnCloneTLeft:function(a){var c=
+this.fnGetSettings(),b=a.nNode,d=$("tbody",c.nTable)[0];for($("tbody tr:eq(0) td",c.nTable);0<b.childNodes.length;)b.removeChild(b.childNodes[0]);b.appendChild(jQuery("thead",c.nTable).clone(!0)[0]);b.appendChild(jQuery("tbody",c.nTable).clone(!0)[0]);c.bFooter&&b.appendChild(jQuery("tfoot",c.nTable).clone(!0)[0]);$("thead tr",b).each(function(){$("th:gt(0)",this).remove()});$("tfoot tr",b).each(function(){$("th:gt(0)",this).remove()});$("tbody tr",b).each(function(){$("td:gt(0)",this).remove()});
+this.fnEqualiseHeights("tbody",d.parentNode,b);c=jQuery("thead tr th:eq(0)",c.nTable).outerWidth();b.style.width=c+"px";a.nWrapper.style.width=c+"px"},_fnCloneTRight:function(a){for(var c=this.fnGetSettings(),b=$("tbody",c.nTable)[0],d=a.nNode,e=jQuery("tbody tr:eq(0) td",c.nTable).length;0<d.childNodes.length;)d.removeChild(d.childNodes[0]);d.appendChild(jQuery("thead",c.nTable).clone(!0)[0]);d.appendChild(jQuery("tbody",c.nTable).clone(!0)[0]);c.bFooter&&d.appendChild(jQuery("tfoot",c.nTable).clone(!0)[0]);
+jQuery("thead tr th:not(:nth-child("+e+"n))",d).remove();jQuery("tfoot tr th:not(:nth-child("+e+"n))",d).remove();$("tbody tr",d).each(function(){$("td:lt("+(e-1)+")",this).remove()});this.fnEqualiseHeights("tbody",b.parentNode,d);c=jQuery("thead tr th:eq("+(e-1)+")",c.nTable).outerWidth();d.style.width=c+"px";a.nWrapper.style.width=c+"px"},fnEqualiseHeights:function(a,c,b){var d=$(a+" tr:eq(0)",c).children(":eq(0)"),e=d.outerHeight()-d.height(),f=$.browser.msie&&("6.0"==$.browser.version||"7.0"==
+$.browser.version);$(a+" tr",b).each(function(b){$.browser.mozilla||$.browser.opera?$(this).children().height($(a+" tr:eq("+b+")",c).outerHeight()):$(this).children().height($(a+" tr:eq("+b+")",c).outerHeight()-e);f||$(a+" tr:eq("+b+")",c).height($(a+" tr:eq("+b+")",c).outerHeight())})}};FixedHeader.oWin={iScrollTop:0,iScrollRight:0,iScrollBottom:0,iScrollLeft:0,iHeight:0,iWidth:0};FixedHeader.oDoc={iHeight:0,iWidth:0};FixedHeader.afnScroll=[];
+FixedHeader.fnMeasure=function(){var a=jQuery(window),c=jQuery(document),b=FixedHeader.oWin,d=FixedHeader.oDoc;d.iHeight=c.height();d.iWidth=c.width();b.iHeight=a.height();b.iWidth=a.width();b.iScrollTop=a.scrollTop();b.iScrollLeft=a.scrollLeft();b.iScrollRight=d.iWidth-b.iScrollLeft-b.iWidth;b.iScrollBottom=d.iHeight-b.iScrollTop-b.iHeight};FixedHeader.VERSION="2.0.6";FixedHeader.prototype.VERSION=FixedHeader.VERSION;
+jQuery(window).scroll(function(){FixedHeader.fnMeasure();for(var a=0,c=FixedHeader.afnScroll.length;a<c;a++)FixedHeader.afnScroll[a]()});
index fb2df81..3d2e188 100644 (file)
-{% extends 'rh/rapports/base.html' %}
+{% extends 'admin/base_site.html' %}
 {% load adminmedia rapports i18n l10n %}
 
-{% block nomrapport %}Rapport de masse salariale{% endblock %}
-{% block count_elements %}<h2>Rapport année {{ request.GET.annee }}</h2>{% endblock %}
+{% block content_title %}<h1>Rapport de masse salariale</h1>{% endblock %}
 
 {% block extrastyle %}
 {{ block.super }}
 <style type="text/css">
-    #changelist .actions .filter {width: auto; float: left;}
-    #changelist .actions .filter h3 {font-size: 11px; margin-left: 0.5em;}
-    #result_list .highlighted {background: #ffff88;}
+table.rapport { border: 1px solid #dddddd; }
+table.rapport th { background: white; }
+table.rapport th.traitements { background: #ecab44; }
+table.rapport th.indemnites { background: #fff840; }
+table.rapport th.primes { background: #d7fb0f; }
+table.rapport th.charges { background: #fb680f; }
+table.rapport .highlighted { background: #ffff88; }
+th.section-end, td.section-end { border-right: 10px solid #dddddd; }
+td.nowrap { white-space: nowrap; }
 </style>
 {% endblock %}
 
 {% block extrahead %}
 {{ block.super }}
-<script type="text/javascript" src="{{ STATIC_URL }}js/jquery-1.5.1.min.js"></script>
-<script type="text/javascript" src="{% admin_media_prefix %}js/jquery-stickytableheaders.js"></script>
+<script type="text/javascript"
+        src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
+<script type="text/javascript" src="{{ STATIC_URL }}rh/FixedHeader.min.js"></script>
 <script type="text/javascript">
-$(function() {
-    $('#result_list tr').click(function() {
+$(document).ready(function() {
+    new FixedHeader($('table.rapport').get(0));
+    $('table.rapport tr').click(function() {
         $(this).toggleClass('highlighted');
     });
 });
 </script>
 {% endblock %}
 
-{% block contentrapport %}
-{% if url_ods %}
-<ul class="object-tools">
-    <li>
-        <a href="{{ url_ods }}">Exporter en ods</a>
-    </li>
-</ul>
-{% endif %}
-<form method="GET">
-    <input type="hidden" name="ne_pas_grouper" value="True" />
-    <div class="actions">
-        <div class="filter">
-            <h3>Région</h3>
-            {{ form.region }}
-        </div>
-        <div class="filter">
-            <h3>Implantation</h3>
-            {{ form.implantation }}
-        </div>
-        <div class="filter">
-            <h3>Année</h3>
-            {{ form.annee }}
-        </div>
-        <div class="filter" style="margin-left:20px">
-            <h3>&nbsp;</h3>
-            <button type="submit" class="button" title="Exécuter l'action sélectionnée">Rechercher</button>
-        </div>
-        <div class="clear"></div>
-        {% comment %}
-        <label>Plage de dates:
-            {{ form.date_debut }} au {{ form.date_fin }}
-        </label>
-        {% endcomment %}
-    </div>
+{% block content %}
+<form class="module">
+  <table style="width: 100%">
+    {{ form }}
+    <tr>
+      <td></td>
+      <td>
+        <input type="submit" value="Afficher">
+        <input type="submit" name="ods" value="Format Calc">
+      </td>
+    </tr>
+  </table>
 </form>
-<div class="clear"></div>
 
+{% if lignes %}
+<p>
+  <strong>Masse salariale totale: {{ masse_salariale_totale }} EUR</strong>
+</p>
 
-<script type="text/javascript">
-    jQuery(document).ready(function(){
-        $("#result_list").stickyTableHeaders();
-    });
-</script>
-<table id="result_list" class="results">
-<thead>
-<tr>
-    {% table_header headers %}
-</tr>
-</thead>
-{% localize on %}
-{% spaceless %}
-{% for row in rapport %}
-    <tr class="{% cycle 'row1' 'row2' %}">
-        {% for column in header_keys %}
-            {% if column == 'sep' %}
-                <td style="background:gray;">&nbsp;</td>
-            {% else %}
-                {% if row|hash:column|is_float %}
-                <td>
-                    {% comment %}
-                    {% if options.backgroundcolor %}
-                    style="background-color:{{ options.backgroundcolor }}"
-                    {% endif %}
-                    {% endcomment %}
-                    {{ row|hash:column|floatformat:2|localize }}
-                    {% if column != "point" %}
-                        {% if forloop.last or column|contains:"euro" %}
-                            EUR
-                        {% else %}
-                            {{ row.devise }}
-                        {% endif %}
-                    {% endif %}
-                </td>
-                {% else %}
-                    <td>{{ row|hash:column|default:"" }}</td>
-                {% endif %}
-            {% endif %}
-        {% endfor %}
-    </tr>
-{% endfor %}{% endspaceless %}
+<table class="rapport">
+  <thead>
     <tr>
-        <td colspan="{{ colspan }}" style="text-align:right;font-weight:bold;">
-            TOTAL : 
-        </td>
-        <td>{{ total_euro }}</td>
+      <th>Bureau</th>
+      <th>Pays</th>
+      <th>Implantation</th>
+      <th>Valeur du point</th>
+      <th>Numéro d'employé</th>
+      <th>Nom</th>
+      <th>Prénom</th>
+      <th>Type de poste</th>
+      <th>Intitulé du poste</th>
+      <th>Niveau actuel</th>
+      <th>Points</th>
+      <th>Régime de travail annuel</th>
+      <th>Local / Expatrié</th>
+      <th>Statut</th>
+      <th class="section-end">Date de fin de contrat</th>
+      <th>Date de début</th>
+      <th>Date de fin</th>
+      <th class="section-end">Nombre de jours</th>
+      <th>Devise</th>
+      <th>Salaire BSTG ANNUEL</th>
+      <th>Salaire BSTG EUR</th>
+      <th class="section-end">Organisme BSTG</th>
+      <th class="traitements">
+        Salaire théorique annuel
+      </th>
+      {% for titre in titres_traitements %}
+      <th class="traitements">{{ titre }}</th>
+      {% endfor %}
+      <th class="traitements section-end">
+        Total des traitements
+      </th>
+      {% for titre in titres_indemnites %}
+      <th class="indemnites">{{ titre }}</th>
+      {% endfor %}
+      <th class="indemnites section-end">Total des indemnités</th>
+      {% for titre in titres_primes %}
+      <th class="primes">{{ titre }}</th>
+      {% endfor %}
+      <th class="primes section-end">Total des primes</th>
+      {% for titre in titres_charges %}
+      <th class="charges">{{ titre }}</th>
+      {% endfor %}
+      <th class="charges section-end">Total des charges</th>
+      <th class="traitements">Total des traitements</th>
+      <th class="indemnites">Total des indemnités</th>
+      <th class="primes">Total des primes</th>
+      <th class="charges section-end">Total des charges</th>
+      <th>Masse salariale</th>
+      <th>Masse salariale EUR</th>
+    </tr>
+  </thead>
+  <tbody>
+    {% for ligne in lignes %}
+    <tr class="{% cycle 'row1' 'row2' %}">
+      <td>{{ ligne.poste.implantation.region.code }}</td>
+      <td>{{ ligne.poste.implantation.adresse_physique_pays.nom }}</td>
+      <td>{{ ligne.poste.implantation.nom_court }}</td>
+      <td class="nowrap">
+        {% if ligne.valeur_point %}
+        {{ ligne.valeur_point }} {{ ligne.valeur_point_devise }}
+        {% endif %}
+      </td>
+      <td>
+        {% if ligne.dossier %}
+        {{ ligne.dossier.employe.id|stringformat:"d" }}
+        {% else %}
+        VACANT
+        {% endif %}
+      </td>
+      <td>{{ ligne.dossier.employe.nom }}</td>
+      <td>{{ ligne.dossier.employe.prenom }}</td>
+      <td>{{ ligne.poste.type_poste.nom }}</td>
+      <td>{{ ligne.poste.nom }}</td>
+      <td>{{ ligne.dossier.classement }}</td>
+      <td class="nowrap">
+        {{ ligne.dossier.classement.coefficient|floatformat:2 }}
+      </td>
+      <td class="nowrap">
+        {{ ligne.regime_travail|floatformat }} %
+      </td>
+      <td>{{ ligne.local_expatrie }}</td>
+      <td>{{ ligne.dossier.statut.code }}</td>
+      <td class="section-end">{{ ligne.dossier.date_fin|date }}</td>
+      <td>
+        {{ ligne.date_debut|date }}
+      </td>
+      <td>
+        {{ ligne.date_fin|date }}
+      </td>
+      <td class="section-end">
+        {{ ligne.jours }}
+      </td>
+      <td>{{ ligne.devise }}</td>
+      <td class="nowrap">
+        {% if ligne.salaire_bstg %}
+        {{ ligne.salaire_bstg }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      <td class="nowrap">
+        {% if ligne.salaire_bstg_eur %}
+        {{ ligne.salaire_bstg_eur }} EUR
+        {% endif %}
+      </td>
+      <td class="section-end">{{ ligne.dossier.organisme_bstg.nom }}</td>
+      <td class="nowrap">
+        {% if ligne.salaire_theorique %}
+        {{ ligne.salaire_theorique }} {{ ligne.valeur_point_devise }}
+        {% endif %}
+      </td>
+      {% for traitement in ligne.traitements %}
+      <td class="nowrap">
+        {% if traitement %}
+        {{ traitement }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      {% endfor %}
+      <td class="section-end nowrap">
+        {% if ligne.total_traitements %}
+        {{ ligne.total_traitements }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      {% for indemnite in ligne.indemnites %}
+      <td class="nowrap">
+        {% if indemnite %}
+        {{ indemnite }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      {% endfor %}
+      <td class="section-end nowrap">
+        {% if ligne.total_indemnites %}
+        {{ ligne.total_indemnites }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      {% for prime in ligne.primes %}
+      <td class="nowrap">
+        {% if prime %}
+        {{ prime }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      {% endfor %}
+      <td class="section-end nowrap">
+        {% if ligne.total_primes %}
+        {{ ligne.total_primes }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      {% for charge in ligne.charges %}
+      <td class="nowrap">
+        {% if charge %}
+        {{ charge }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      {% endfor %}
+      <td class="section-end nowrap">
+        {% if ligne.total_charges %}
+        {{ ligne.total_charges }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      <td class="nowrap">
+        {% if ligne.total_traitements %}
+        {{ ligne.total_traitements }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      <td class="nowrap">
+        {% if ligne.total_indemnites %}
+        {{ ligne.total_indemnites }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      <td class="nowrap">
+        {% if ligne.total_primes %}
+        {{ ligne.total_primes }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      <td class="section-end nowrap">
+        {% if ligne.total_charges %}
+        {{ ligne.total_charges }} {{ ligne.devise }}
+        {% endif %}
+      </td>
+      <td class="nowrap">
+        {{ ligne.masse_salariale }} {{ ligne.devise }}
+      </td>
+      <td class="nowrap">
+        {{ ligne.masse_salariale_eur }} EUR
+      </td>
     </tr>
+    {% endfor %}
+  </tbody>
 </table>
-{% endlocalize %}
+{% endif %}
+
 {% endblock %}
index f778732..b65eef3 100644 (file)
@@ -1,41 +1,37 @@
 # -*- encoding: utf-8 -*-
 
-import urllib
+from collections import defaultdict
 from datetime import date
-from itertools import izip
-import StringIO
+from decimal import Decimal
 
 import pygraphviz as pgv
-
-from django import forms
+from auf.django.references import models as ref
 from django.conf import settings
 from django.contrib.auth.decorators import login_required
-from django.core.servers.basehttp import FileWrapper
 from django.core.urlresolvers import reverse
 from django.db.models import Q
 from django.http import HttpResponse
 from django.shortcuts import render, get_object_or_404
 
-from auf.django.references import models as ref
-
-from project.decorators import redirect_interdiction
 from project.decorators import drh_or_admin_required
 from project.decorators import region_protected
-from project.groups import get_employe_from_user
-from project.groups import grp_drh, grp_correspondants_rh
-
-from project.rh import models as rh
+from project.groups import \
+        get_employe_from_user, grp_drh, grp_correspondants_rh
+from project.rh import ods
 from project.rh import graph as rh_graph
+from project.rh import models as rh
 from project.rh.change_list import RechercheTemporelle
-from project.rh.lib import calc_remun, get_lookup_params
-from project.rh.masse_salariale import MasseSalariale
+from project.rh.forms import MasseSalarialeForm
+from project.rh.lib import get_lookup_params
 from project.rh.templatetags.rapports import SortHeaders
 
+TWOPLACES = Decimal('0.01')
+
 
 @login_required
 def profil(request):
     """Profil personnel de l'employé - éditable"""
-    employe = get_employe_from_user(user)
+    employe = get_employe_from_user(request.user)
     c = {
       'user': request.user,
       'employe': employe,
@@ -106,7 +102,6 @@ def rapports_contrat(request):
 
     # affichage
     employes = set([c.dossier.employe_id for c in contrats])
-
     headers = [
         ("dossier__employe__id", u"#"),
         ("dossier__employe__nom", u"Employé"),
@@ -160,12 +155,12 @@ def rapports_employes_sans_contrat(request):
         ).filter(
             # sans contrat | contrat échu
             Q(rh_contrats=None) | Q(rh_contrats__in=contrats_echus)
-        ).distinct()   
-    
+        ).distinct()
+
     # employés sans contrat ou contrats échus
     employes = rh.Employe.objects.filter(rh_dossiers__in=dossiers)  \
         .distinct().count()
-        
+
     # tri
     if 'o' in request.GET:
         dossiers = dossiers.order_by(
@@ -202,110 +197,308 @@ def rapports_employes_sans_contrat(request):
 @login_required
 @drh_or_admin_required
 def rapports_masse_salariale(request):
+    form = MasseSalarialeForm(request.user, request.GET)
+    if 'annee' in request.GET and form.is_valid():
+        region = form.cleaned_data['region']
+        implantation = form.cleaned_data['implantation']
+        annee = form.cleaned_data['annee']
+        debut_annee = date(annee, 1, 1)
+        fin_annee = date(annee, 12, 31)
+        jours_annee = (fin_annee - debut_annee).days + 1
+        today = date.today()
+
+        # Récupérer les dossiers actifs
+        dossiers = rh.Dossier.objects \
+                .actifs(annee=annee) \
+                .select_related(
+                    'poste', 'poste__implantation',
+                    'poste__implantation__region',
+                    'poste__implantation__adresse_physique_pays',
+                    'employe', 'poste__type_poste', 'classement',
+                    'statut', 'organisme_bstg'
+                ) \
+                .extra(
+                    tables=['rh_valeurpoint', 'rh_devise'],
+                    where=[
+                        'rh_valeurpoint.annee = %s',
+                        'rh_valeurpoint.implantation = ref_implantation.id',
+                        'rh_devise.id = rh_valeurpoint.devise'
+                    ],
+                    params=[annee],
+                    select={
+                        'valeur_point': 'rh_valeurpoint.valeur',
+                        'valeur_point_devise': 'rh_devise.code'
+                    }
+                )
+        if region:
+            dossiers = dossiers.filter(poste__implantation__region=region)
+        if implantation:
+            dossiers = dossiers.filter(poste__implantation=implantation)
+
+        # Récupérer les rémunérations actives
+        remuns = rh.Remuneration.objects \
+                .actifs(annee=annee) \
+                .select_related('devise', 'type') \
+                .extra(
+                    tables=['rh_tauxchange'],
+                    where=[
+                        'rh_tauxchange.annee = %s',
+                        'rh_tauxchange.devise = rh_devise.id'
+                    ],
+                    params=[annee],
+                    select={
+                        'taux_change': 'rh_tauxchange.taux'
+                    }
+                )
+        remuns_par_dossier = defaultdict(list)
+        for remun in remuns:
+            remuns_par_dossier[remun.dossier_id].append(remun)
+
+        # Récupérer les types de rémunération par nature
+        types_remun_par_nature = defaultdict(list)
+        for type in rh.TypeRemuneration.objects.all():
+            types_remun_par_nature[type.nature_remuneration].append(type)
+        titres_traitements = [
+            t.nom for t in types_remun_par_nature[u'Traitement']
+        ]
+        titres_indemnites = [
+            t.nom for t in types_remun_par_nature[u'Indemnité']
+        ]
+        titres_primes = [
+            t.nom for t in types_remun_par_nature[u'Accessoire']
+        ]
+        titres_charges = [
+            t.nom for t in types_remun_par_nature[u'Charges']
+        ]
 
-    class RechercheTemporelle(forms.Form):
-        CHOICE_ANNEES = range(
-            rh.Remuneration.objects.exclude(date_debut=None)
-            .order_by('date_debut')[0].date_debut.year,
-            date.today().year + 1
-        )
+        # Boucler sur les dossiers et préprarer les lignes du rapport
+        lignes = []
+        masse_salariale_totale = 0
+        for dossier in dossiers:
+            debut_cette_annee = \
+                    max(dossier.date_debut or debut_annee, debut_annee)
+            fin_cette_annee = \
+                    min(dossier.date_fin or fin_annee, fin_annee)
+            jours = (fin_cette_annee - debut_cette_annee).days + 1
+
+            remuns = remuns_par_dossier[dossier.id]
+            devises = set(remun.devise.code for remun in remuns)
+            if len(devises) == 1:
+                devise = remuns[0].devise.code
+                montant_remun = lambda r: r.montant
+                taux_change = Decimal(str(remuns[0].taux_change))
+            else:
+                devise = 'EUR'
+                montant_remun = lambda r: (
+                    r.montant * Decimal(str(r.taux_change))
+                )
+                taux_change = Decimal(1)
+
+            remuns_par_type = defaultdict(lambda: 0)
+            for remun in remuns:
+                if remun.type.nature_remuneration == u'Accessoire':
+                    remuns_par_type[remun.type_id] += montant_remun(remun)
+                else:
+                    remuns_par_type[remun.type_id] += (
+                        montant_remun(remun) * ((
+                            min(remun.date_fin or fin_annee, fin_annee) -
+                            max(remun.date_debut or debut_annee, debut_annee)
+                        ).days + 1) / jours_annee
+                    ).quantize(TWOPLACES)
+            traitements = [
+                remuns_par_type[type.id]
+                for type in types_remun_par_nature[u'Traitement']
+            ]
+            indemnites = [
+                remuns_par_type[type.id]
+                for type in types_remun_par_nature[u'Indemnité']
+            ]
+            primes = [
+                remuns_par_type[type.id]
+                for type in types_remun_par_nature[u'Accessoire']
+            ]
+            charges = [
+                remuns_par_type[type.id]
+                for type in types_remun_par_nature[u'Charges']
+            ]
+            masse_salariale = sum(remuns_par_type.values())
+            masse_salariale_eur = (
+                masse_salariale * taux_change
+            ).quantize(TWOPLACES)
+            masse_salariale_totale += masse_salariale_eur
+
+            if dossier.valeur_point and dossier.classement \
+               and dossier.classement.coefficient and dossier.regime_travail:
+                salaire_theorique = (Decimal(str(
+                    dossier.valeur_point * dossier.classement.coefficient
+                )) * dossier.regime_travail / 100).quantize(TWOPLACES)
+            else:
+                salaire_theorique = None
+
+            lignes.append({
+                'dossier': dossier,
+                'poste': dossier.poste,
+                'date_debut': (
+                    dossier.date_debut
+                    if dossier.date_debut and dossier.date_debut.year == annee
+                    else None
+                ),
+                'date_fin': (
+                    dossier.date_fin
+                    if dossier.date_fin and dossier.date_fin.year == annee
+                    else None
+                ),
+                'jours': jours,
+                'devise': devise,
+                'valeur_point': dossier.valeur_point,
+                'valeur_point_devise': dossier.valeur_point_devise,
+                'regime_travail': dossier.regime_travail,
+                'local_expatrie': {
+                    'local': 'L', 'expat': 'E'
+                }.get(dossier.statut_residence),
+                'salaire_bstg': remuns_par_type[2],
+                'salaire_bstg_eur': (
+                    (remuns_par_type[2] * taux_change).quantize(TWOPLACES)
+                ),
+                'salaire_theorique': salaire_theorique,
+                'traitements': traitements,
+                'total_traitements': sum(traitements),
+                'indemnites': indemnites,
+                'total_indemnites': sum(indemnites),
+                'primes': primes,
+                'total_primes': sum(primes),
+                'charges': charges,
+                'total_charges': sum(charges),
+                'masse_salariale': masse_salariale,
+                'masse_salariale_eur': masse_salariale_eur,
+            })
 
-        annee = forms.CharField(
-            initial=date.today().year,
-            widget=forms.Select(
-                choices=((a, a) for a in reversed(CHOICE_ANNEES))
+        # Récupérer les postes actifs pour déterminer le nombre de jours
+        # vacants.
+        postes = list(
+            rh.Poste.objects.actifs(annee=annee)
+            .select_related(
+                'devise_max', 'valeur_point_max', 'classement_max'
             )
-        )
-
-        region = forms.CharField(
-                widget=forms.Select(choices=[('', '')] +
-                    [(i.id, i) for i in ref.Region.objects.all()]
-                )
+            .extra(
+                tables=['rh_tauxchange'],
+                where=[
+                    'rh_tauxchange.annee = %s',
+                    'rh_tauxchange.devise = rh_devise.id'
+                ],
+                params=[annee],
+                select={
+                    'taux_change': 'rh_tauxchange.taux'
+                }
             )
-
-        implantation = forms.CharField(
-                widget=forms.Select(choices=[('', '')] +
-                    [(i.id, i) for i in ref.Implantation.objects.all()]
-                )
+        )
+        postes_par_id = dict((poste.id, poste) for poste in postes)
+        jours_vacants_date = dict(
+            (poste.id, max(today, poste.date_debut or today))
+            for poste in postes
+        )
+        jours_vacants = defaultdict(lambda: 0)
+        for dossier in rh.Dossier.objects.actifs(annee=annee) \
+                       .order_by('date_debut'):
+            if dossier.poste_id not in jours_vacants_date:
+                continue
+            derniere_date = jours_vacants_date[dossier.poste_id]
+            if dossier.date_debut is not None:
+                jours_vacants[dossier.poste_id] += max((
+                    dossier.date_debut - derniere_date
+                ).days - 1, 0)
+            jours_vacants_date[dossier.poste_id] = max(
+                min(dossier.date_fin or fin_annee, fin_annee),
+                derniere_date
             )
 
-        #date_debut = forms.DateField(widget=adminwidgets.AdminDateWidget)
-        #date_fin = forms.DateField(widget=adminwidgets.AdminDateWidget)
-
-    form = RechercheTemporelle(request.GET)
-    get_filtre = [
-        (k, v) for k, v in request.GET.items()
-        if k not in ('date_debut', 'date_fin', 'implantation')
-    ]
-    query_string = urllib.urlencode(get_filtre)
-
-    date_debut = None
-    date_fin = None
-    if request.GET.get('annee', None):
-        date_debut = "01-01-%s" % request.GET.get('annee', None)
-        date_fin = "31-12-%s" % request.GET.get('annee', None)
-
-    implantation = request.GET.get('implantation')
-    region = request.GET.get('region')
+        # Ajouter les lignes des postes vacants au rapport
+        for poste_id, jours in jours_vacants.iteritems():
+            if jours == 0:
+                continue
+            poste = postes_par_id[poste_id]
+            if poste.valeur_point_max and poste.classement_max \
+               and poste.classement_max.coefficient and poste.regime_travail:
+                salaire_theorique = (Decimal(str(
+                    poste.valeur_point_max.valeur *
+                    poste.classement_max.coefficient
+                )) * poste.regime_travail / 100).quantize(TWOPLACES)
+            else:
+                salaire_theorique = None
 
-    custom_filter = {}
-    if implantation:
-        custom_filter['dossier__poste__implantation'] = implantation
-    if region:
-        custom_filter['dossier__poste__implantation__region'] = region
+            local_expatrie = '/'.join(
+                (['L'] if poste.local else []) +
+                (['E'] if poste.expatrie else [])
+            )
 
-    c = {
-            'title': 'Rapport de masse salariale',
-            'form': form,
-            'headers': [],
-            'query_string': query_string,
-    }
-    if date_debut or date_fin:
-        masse = MasseSalariale(date_debut, date_fin, custom_filter,
-                request.GET.get('ne_pas_grouper', False))
-        if masse.rapport:
-            if request.GET.get('ods'):
-                for h in (
-                    h for h in masse.headers if 'background-color' in h[2]
-                ):
-                    del h[2]['background-color']
-                masse.ods()
-                output = StringIO.StringIO()
-                masse.doc.save(output)
-                output.seek(0)
-
-                response = HttpResponse(
-                    FileWrapper(output),
-                    content_type=(
-                        'application/vnd.oasis.opendocument.spreadsheet'
-                    )
-                )
-                response['Content-Disposition'] = \
-                        'attachment; filename=Masse Salariale %s.ods' % \
-                            masse.annee
-                return response
-            else:
-                c['rapport'] = masse.rapport
-                c['header_keys'] = [h[0] for h in masse.headers]
-                #on enleve le background pour le header
-                for h in (
-                    h for h in masse.headers if 'background-color' in h[2]
-                ):
-                    h[2]['background'] = 'none'
-                h = SortHeaders(request, masse.headers, order_field_type="ot",
-                        not_sortable=c['header_keys'], order_field="o")
-                c['headers'] = list(h.headers())
-                for key, nom, opts in masse.headers:
-                    c['headers']
-                c['total'] = masse.grand_totaux[0]
-                c['total_euro'] = masse.grand_totaux[1]
-                c['colspan'] = len(c['header_keys']) - 1
-                get_filtre.append(('ods', True))
-                query_string = urllib.urlencode(get_filtre)
-                c['url_ods'] = "%s?%s" % (
-                        reverse('rhr_masse_salariale'), query_string)
-
-    return render(request, 'rh/rapports/masse_salariale.html', c)
+            salaire = (
+                poste.salaire_max * jours / jours_annee *
+                poste.regime_travail / 100
+            ).quantize(TWOPLACES)
+            indemnites = (
+                poste.indemn_max * jours / jours_annee *
+                poste.regime_travail / 100
+            ).quantize(TWOPLACES)
+            charges = (
+                poste.autre_max * jours / jours_annee *
+                poste.regime_travail / 100
+            ).quantize(TWOPLACES)
+            masse_salariale = salaire + indemnites + charges
+            masse_salariale_eur = (
+                masse_salariale * Decimal(str(poste.taux_change))
+            ).quantize(TWOPLACES)
+            masse_salariale_totale += masse_salariale_eur
+
+            lignes.append({
+                'poste': poste,
+                'regime_travail': poste.regime_travail,
+                'local_expatrie': local_expatrie,
+                'jours': jours,
+                'devise': poste.devise_max and poste.devise_max.code,
+                'valeur_point': (
+                    poste.valeur_point_max and poste.valeur_point_max.valeur
+                ),
+                'valeur_point_devise': (
+                    poste.valeur_point_max and \
+                    poste.valeur_point_max.devise.code
+                ),
+                'salaire_theorique': salaire_theorique,
+                'salaire_de_base': salaire,
+                'total_indemnites': indemnites,
+                'total_charges': charges,
+                'masse_salariale': masse_salariale,
+                'masse_salariale_eur': masse_salariale_eur
+            })
+        if 'ods' in request.GET:
+            doc = ods.masse_salariale(
+                lignes=lignes,
+                annee=annee,
+                titres_traitements=titres_traitements,
+                titres_indemnites=titres_indemnites,
+                titres_primes=titres_primes,
+                titres_charges=titres_charges,
+                masse_salariale_totale=masse_salariale_totale
+            )
+            response = HttpResponse(
+                mimetype='vnd.oasis.opendocument.spreadsheet'
+            )
+            response['Content-Disposition'] = 'filename=masse-salariale.ods'
+            doc.write(response)
+            return response
+        else:
+            return render(request, 'rh/rapports/masse_salariale.html', {
+                'form': form,
+                'titres_traitements': titres_traitements,
+                'titres_indemnites': titres_indemnites,
+                'titres_primes': titres_primes,
+                'titres_charges': titres_charges,
+                'masse_salariale_totale': masse_salariale_totale,
+                'lignes': lignes,
+                'annee': annee
+            })
+    return render(request, 'rh/rapports/masse_salariale.html', {
+        'form': form
+    })
 
 
 @login_required
@@ -372,6 +565,7 @@ def rapports_postes_service(request):
     c['data'] = data
     return render(request, 'rh/rapports/postes_service.html', c)
 
+
 @region_protected(rh.Dossier)
 def dossier_apercu(request, dossier_id):
     d = get_object_or_404(rh.Dossier, pk=dossier_id)
@@ -442,7 +636,6 @@ def employe_apercu(request, employe_id):
 @login_required
 @drh_or_admin_required
 def organigrammes_employe(request, id, level="all"):
-
     poste = get_object_or_404(rh.Poste, pk=id)
     dossiers_by_poste = dict(
         (d.poste_id, d)
@@ -524,7 +717,6 @@ def organigrammes_employe(request, id, level="all"):
 @login_required
 @drh_or_admin_required
 def organigrammes_service(request, id):
-
     service = get_object_or_404(rh.Service, pk=id)
     svg = rh_graph.organigramme_postes_cluster( \
             cluster_filter={"service": service}, \
@@ -535,7 +727,7 @@ def organigrammes_service(request, id):
         'svg': svg
     }
 
-    return render(request, 'rh/organigrammes/vide.html', c, 
+    return render(request, 'rh/organigrammes/vide.html', c,
         content_type="image/svg+xml"
     )
 
@@ -543,7 +735,6 @@ def organigrammes_service(request, id):
 @login_required
 @drh_or_admin_required
 def organigrammes_implantation(request, id):
-
     implantation = get_object_or_404(ref.Implantation, pk=id)
     svg = rh_graph.organigramme_postes_cluster( \
             cluster_filter={"implantation": implantation}, \
@@ -554,7 +745,7 @@ def organigrammes_implantation(request, id):
         'svg': svg
     }
 
-    return render(request, 'rh/organigrammes/vide.html', c, 
+    return render(request, 'rh/organigrammes/vide.html', c,
         content_type="image/svg+xml"
     )
 
@@ -562,7 +753,6 @@ def organigrammes_implantation(request, id):
 @login_required
 @drh_or_admin_required
 def organigrammes_region(request, id):
-
     region = get_object_or_404(ref.Region, pk=id)
     svg = rh_graph.organigramme_postes_cluster( \
             cluster_filter={"implantation__region": region}, \
@@ -573,7 +763,7 @@ def organigrammes_region(request, id):
         'svg': svg
     }
 
-    return render(request, 
-        'rh/organigrammes/vide.html', c, 
+    return render(request,
+        'rh/organigrammes/vide.html', c,
         content_type="image/svg+xml"
     )
index 4181f8f..f0601e9 100644 (file)
@@ -26,6 +26,8 @@ SESSION_SAVE_EVERY_REQUEST = True
 SESSION_EXPIRE_AT_BROWSER_CLOSE = True
 
 LANGUAGE_CODE = 'fr-ca'
+USE_L10N = True
+USE_THOUSAND_SEPARATOR = True
 
 # Absolute path to the directory that holds media.
 # Example: "/home/media/media.lawrence.com/"
@@ -78,11 +80,12 @@ INSTALLED_APPS = (
     'admin_tools.theming',
     'admin_tools.menu',
     'admin_tools.dashboard',
+    'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
+    'django.contrib.humanize',
     'django.contrib.messages',
     'django.contrib.sessions',
-    'django.contrib.admin',
     'django.contrib.staticfiles',
     'django_qbe',
     'ajax_select',
index b4a5bfb..cd52e59 100644 (file)
@@ -62,3 +62,6 @@ django-picklefield = 0.2.1
 
 # Added by Buildout Versions at 2012-06-08 11:15:52.545562
 auf.django.emploi = 1.2dev
+
+# Added by Buildout Versions at 2012-06-15 14:24:42.911205
+odsgen = 0.1