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

1  2 
project/rh/models.py
project/rh/views.py
project/settings.py

diff --combined project/rh/models.py
@@@ -22,7 -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 +440,6 @@@ class PosteCommentaire(Commentaire)
      )
  
  
  ### EMPLOYÉ/PERSONNE
  
  class Employe(AUFMetadata):
          q = search.get_q_temporel(self.rh_dossiers)
          return self.rh_dossiers.filter(q)
  
 +    def dossier_principal(self):
 +        """Retourne le dossier principal 
 +        (ou le plus ancien si il y en a plusieurs)
 +        """
 +        try:
 +            dossier = self.rh_dossiers.filter(principal=True).order_by('date_debut')[0]
 +        except IndexError, Dossier.DoesNotExist:
 +            dossier = None
 +        return dossier
 +
      def postes_encours(self):
          postes_encours = set()
          for d in self.dossiers_encours():
          Idée derrière :
          si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
          """
 +        # DEPRECATED : on a maintenant Dossier.principal
          poste = Poste.objects.none()
          try:
              poste = self.dossiers_encours().order_by('date_debut')[0].poste
@@@ -962,22 -950,6 +961,22 @@@ class Dossier_(AUFMetadata, DevisableMi
              total += r.montant_euros()
          return total
  
 +    def premier_contrat(self):
 +        """contrat avec plus petite date de début"""
 +        try:
 +            contrat = self.rh_contrats.exclude(date_debut=None).order_by('date_debut')[0]
 +        except IndexError, Contrat.DoesNotExist:
 +            contrat = None
 +        return contrat
 +        
 +    def dernier_contrat(self):
 +        """contrat avec plus grande date de fin"""
 +        try:
 +            contrat = self.rh_contrats.exclude(date_debut=None).order_by('-date_debut')[0]
 +        except IndexError, Contrat.DoesNotExist:
 +            contrat = None
 +        return contrat
 +
      def actif(self):
          today = date.today()
          return (self.date_debut is None or self.date_debut <= today) \
@@@ -1028,7 -1000,6 +1027,6 @@@ class DossierPiece(DossierPiece_)
      )
  
  
  class DossierCommentaire(Commentaire):
      dossier = models.ForeignKey(
          Dossier, db_column='dossier', related_name='commentaires'
@@@ -1091,6 -1062,8 +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']
diff --combined project/rh/views.py
@@@ -1,41 -1,37 +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 +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é"),
  
  @login_required
  @drh_or_admin_required
 +def rapports_employes_sans_contrat(request):
 +    """
 +    Employé sans contrat = a un Dossier qui n'a pas de Contrat associé
 +    Employé avec contrat échu = a un Dossier avec un Contrat se terminant hier
 +    au plus tard... (aujourd'hui = ok, pas date de fin = illimité donc ok)
 +    """
 +    lookup_params = get_lookup_params(request)
 +
 +    # contrats échus
 +    contrats_echus = rh.Contrat.objects.filter(
 +            date_fin__lt=date.today()
 +        ).filter(
 +            **lookup_params
 +        )
 +
 +    # dossiers en cours sans contrat ou contrats échus
 +    dossiers = rh.Dossier.objects.filter(
 +            Q(date_debut=None) | Q(date_debut__lte=date.today()),
 +        ).filter(
 +            Q(date_fin=None) | Q(date_fin__gte=date.today()),
 +        ).exclude(
 +            date_debut__gt=date.today()
 +        ).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(
 +            ('-' if request.GET.get('ot') == "desc" else '') + request.GET['o']
 +        )
 +
 +    # affichage
 +    headers = [
 +        ("employe__id", u"#"),
 +        ("employe__nom", u"Employé"),
 +        ("dossier", u"Dossier : Poste"),
 +        ("rh_contrats__type_contrat", u"Contrat"),
 +        ("rh_contrats__date_debut", u"Début contrat"),
 +        ("rh_contrats__date_fin", u"Fin contrat"),
 +        ("statut_residence", u"Statut"),
 +        ("poste__implantation__region__code", u"Région"),
 +        ("poste__implantation__nom", u"Implantation"),
 +    ]
 +    h = SortHeaders(
 +        request, headers, order_field_type="ot", order_field="o",
 +        not_sortable=('dossier',)
 +    )
 +
 +    c = {
 +        'title': u'Rapport des employés sans contrat ou contrat échu',
 +        'employes': employes,
 +        'dossiers': dossiers,
 +        'headers': list(h.headers()),
 +    }
 +
 +    return render(request, 'rh/rapports/employes_sans_contrat.html', c)
 +
 +
 +@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
  @drh_or_admin_required
 -def rapports_employes_sans_contrat(request):
 -    lookup_params = get_lookup_params(request)
 -
 -    # contrats échus
 -    contrats_echus = rh.Contrat.objects.filter(
 -            date_fin__lt=date.today()
 -        ).filter(
 -            **lookup_params
 -        )
 -
 -    # dossiers en cours sans contrat
 -    dossiers_sans_contrat = rh.Dossier.objects.filter(
 -            Q(date_fin=None) | Q(date_fin__gt=date.today()),
 -        ).exclude(
 -            date_debut__gt=date.today()
 -        ).filter(
 -            rh_contrats__in=contrats_echus
 -        )
 -
 -    # employés sans contrat
 -    employes = rh.Employe.objects \
 -            .filter(rh_dossiers__in=dossiers_sans_contrat).distinct()
 -    if 'o' in request.GET:
 -        employes = employes.order_by(
 -            ('-' if request.GET.get('ot') == "desc" else '') + request.GET['o']
 -        )
 -
 -    # affichage
 -    headers = [
 -        ("id", u"#"),
 -        ("nom", u"Employé"),
 -        ("dossier", u"Dossier : Poste"),
 -        ("dossier_date_debut", u"Début dossier"),
 -        ("dossier_date_fin", u"Fin dossier"),
 -    ]
 -    h = SortHeaders(
 -        request, headers, order_field_type="ot", order_field="o",
 -        not_sortable=('dossier', 'dossier_date_debut', 'dossier_date_fin')
 -    )
 -
 -    c = {
 -        'title': u'Rapport des employés sans contrat',
 -        'employes': employes,
 -        'dossiers_sans_contrat': dossiers_sans_contrat,
 -        'headers': list(h.headers()),
 -    }
 -
 -    return render(request, 'rh/rapports/employes_sans_contrat.html', c)
 -
 -
 -@login_required
 -@drh_or_admin_required
  def rapports_postes_modelisation(request):
      c = {}
      data = []
@@@ -372,6 -551,7 +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 -622,6 +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)
  @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}, \
          'svg': svg
      }
  
-     return render(request, 'rh/organigrammes/vide.html', c, 
+     return render(request, 'rh/organigrammes/vide.html', c,
          content_type="image/svg+xml"
      )
  
  @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}, \
          'svg': svg
      }
  
-     return render(request, 'rh/organigrammes/vide.html', c, 
+     return render(request, 'rh/organigrammes/vide.html', c,
          content_type="image/svg+xml"
      )
  
  @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}, \
          'svg': svg
      }
  
-     return render(request, 
-         'rh/organigrammes/vide.html', c, 
+     return render(request,
+         'rh/organigrammes/vide.html', c,
          content_type="image/svg+xml"
      )
diff --combined project/settings.py
@@@ -19,13 -19,16 +19,15 @@@ TIME_ZONE = 'America/Montreal
  DATE_FORMAT = 'd-m-Y'
  DATE_INPUT_FORMATS = (
      '%d-%m-%Y', '%d/%m/%Y', '%d %m %Y',
 -    '%d-%m-%y', '%d/%m/%y', '%d %m %y',
  )
 +SHORT_DATE_FORMAT = 'd-m-Y'
  
  SESSION_SAVE_EVERY_REQUEST = True
  SESSION_EXPIRE_AT_BROWSER_CLOSE = True
  
 -SHORT_DATE_FORMAT = 'd-m-Y'
  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 -81,12 +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',