merge qbe in rh : conflits réglés
authorDavin Baragiotta <davin.baragiotta@auf.org>
Thu, 10 Nov 2011 16:57:10 +0000 (11:57 -0500)
committerDavin BARAGIOTTA <davin.baragiotta@auf.org>
Thu, 10 Nov 2011 16:57:10 +0000 (11:57 -0500)
1  2 
buildout.cfg
project/dae/models.py
project/recrutement/models.py
project/rh/models.py
project/settings.py
project/urls.py
src/auf.django.emploi/auf/django/emploi/models.py

diff --combined buildout.cfg
@@@ -10,9 -10,10 +10,11 @@@ find-links = http://pypi.auf.org/simple
      http://pypi.auf.org/simple/auf.django.workflow/
      http://pypi.auf.org/simple/auf.django.admingroup/
      http://pypi.auf.org/simple/auf.django.metadata/
 +    http://pypi.auf.org/simple/auf.django.emploi/
      http://pypi.auf.org/django-alphafilter/
  
+ develop = src/*
  eggs =
      django
      south
      django-reversion
      simplejson
      django-ajax-selects
      django-form-utils
      django-tinymce
      django-simple-captcha
 +    auf.django.metadata
 +    django-alphafilter
 +    django-sendfile
+     django-qbe
  
  # LA PROD ne dispose que de reportlab 2.1, incompatible avec
  # les versions de pisa assez évoluées pour un bon rendu.
@@@ -51,15 -52,19 +54,15 @@@ django-reversion = 1.3.
  auf.django.workflow = 0.14dev
  django-ajax-selects = 1.1.4
  django-alphafilter = 0.5.3auf4
 +auf.django.emploi = 0.5dev
  #reportlab = 2.5
  #html5lib = 0.90
  #pyPDF = 1.13
  #pisa = 3.0.33
  
 -#reportlab = 2.5
 -#html5lib = 0.90
 -#pyPDF = 1.13
 -#pisa = 3.0.33
 -
 -[django] 
 +[django]
  recipe = auf.recipe.django
 -wsgi=true 
 +wsgi=true
  settings=production
  extra-paths = project
  eggs =${buildout:eggs}
diff --combined project/dae/models.py
@@@ -6,10 -6,8 +6,10 @@@ from django.core.files.storage import F
  from django.db import models
  import reversion
  from workflow import PosteWorkflow, DossierWorkflow
 -from workflow import DOSSIER_ETAT_DRH_FINALISATION
 -from managers import DossierManager, PosteManager
 +from workflow import DOSSIER_ETAT_DRH_FINALISATION, DOSSIER_ETAT_REGION_FINALISATION, \
 +                     DOSSIER_ETAT_FINALISE
 +from managers import DossierManager, PosteManager, PosteComparaisonManager, \
 +                     DossierComparaisonManager
  import datamaster_modeles.models as ref
  from rh_v1 import models as rh
  
@@@ -19,8 -17,18 +19,8 @@@ HELP_TEXT_DATE = "format: aaaa-mm-jj
  REGIME_TRAVAIL_DEFAULT=100.00
  REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT=35.00
  
 -
  # Upload de fichiers
 -storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT, 
 -                            base_url=settings.PRIVE_MEDIA_URL)
 -
 -def poste_piece_dispatch(instance, filename):
 -    path = "poste/%s/%s" % (instance.poste_id, filename)
 -    return path
 -
 -def dossier_piece_dispatch(instance, filename):
 -    path = "dossier/%s/%s" % (instance.dossier_id, filename)
 -    return path
 +UPLOAD_STORAGE = FileSystemStorage(settings.PRIVE_MEDIA_ROOT)
  
  
  ### POSTE
@@@ -36,9 -44,6 +36,9 @@@ POSTE_ACTION = 
  )
  
  
 +class DeviseException(Exception):
 +  silent_variable_failure = True
 +
  
  class Poste(PosteWorkflow, models.Model):
  
      # Modèle existant
      id_rh = models.ForeignKey(rh.Poste, null=True, related_name='+',
                              editable=False,
-                             verbose_name="Mise à jour du poste")
-     nom = models.CharField(verbose_name="Titre du poste", max_length=255)
+                             verbose_name=u"Mise à jour du poste")
+     nom = models.CharField(verbose_name=u"Titre du poste", max_length=255)
      implantation = models.ForeignKey(ref.Implantation)
      type_poste = models.ForeignKey(rh.TypePoste, null=True, related_name='+')
      service = models.ForeignKey(rh.Service, related_name='+',
                              verbose_name=u"Direction/Service/Pôle support")
      responsable = models.ForeignKey(rh.Poste, related_name='+',
-                             verbose_name="Poste du responsable")
+                             verbose_name=u"Poste du responsable")
  
      # Contrat
      regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
 -                            default=REGIME_TRAVAIL_DEFAULT, 
 -                            verbose_name=u"Temps de travail", 
 +                            default=REGIME_TRAVAIL_DEFAULT,
-                             verbose_name="Temps de travail",
++                            verbose_name=u"Temps de travail",
                              help_text="% du temps complet")
      regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
                              decimal_places=2,
                              default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
-                             verbose_name="Nb. heures par semaine")
+                             verbose_name=u"Nb. heures par semaine")
  
      # Recrutement
-     local = models.BooleanField(verbose_name="Local", default=True, blank=True)
-     expatrie = models.BooleanField(verbose_name="Expatrié", default=False,
+     local = models.BooleanField(verbose_name=u"Local", default=True, blank=True)
 -    expatrie = models.BooleanField(verbose_name=u"Expatrié", default=False, 
++    expatrie = models.BooleanField(verbose_name=u"Expatrié", default=False,
                              blank=True)
-     mise_a_disposition = models.BooleanField(verbose_name="Mise à disposition")
+     mise_a_disposition = models.BooleanField(verbose_name=u"Mise à disposition")
      appel = models.CharField(max_length=10, default='interne',
-                             verbose_name="Appel à candidature",
+                             verbose_name=u"Appel à candidature",
                              choices=POSTE_APPEL_CHOICES)
  
      # Rémunération
@@@ -80,9 -85,9 +80,9 @@@
                              blank=True, null=True)
      classement_max = models.ForeignKey(rh.Classement, related_name='+',
                              blank=True, null=True)
 -    valeur_point_min = models.ForeignKey(rh.ValeurPoint, related_name='+', 
 +    valeur_point_min = models.ForeignKey(rh.ValeurPoint, related_name='+',
                              blank=True, null=True)
 -    valeur_point_max = models.ForeignKey(rh.ValeurPoint, related_name='+', 
 +    valeur_point_max = models.ForeignKey(rh.ValeurPoint, related_name='+',
                              blank=True, null=True)
      devise_min = models.ForeignKey(rh.Devise, default=5, related_name='+')
      devise_max = models.ForeignKey(rh.Devise, default=5, related_name='+')
      # Méta
      date_creation = models.DateTimeField(auto_now_add=True)
      date_modification = models.DateTimeField(auto_now=True)
-     date_debut = models.DateField(verbose_name="Date de début",
+     date_debut = models.DateField(verbose_name=u"Date de début",
                                      help_text=HELP_TEXT_DATE,
                                      null=True, blank=True)
      date_fin = models.DateField(null=True, blank=True,
-                                     verbose_name="Date de fin",
+                                     verbose_name=u"Date de fin",
                                      help_text=HELP_TEXT_DATE)
      actif = models.BooleanField(default=True)
  
  
      def _get_key(self):
          """
 -        Les vues sont montées selon une clef spéciale 
 +        Les vues sont montées selon une clef spéciale
          pour identifier la provenance du poste.
 -        Cette méthode fournit un moyen de reconstruire cette clef 
 +        Cette méthode fournit un moyen de reconstruire cette clef
          afin de générer les URLs.
          """
          return "dae-%s" % self.id
      def get_dossiers(self):
          """
          Liste tous les anciens dossiers liés à ce poste.
 -        (Le nom de la relation sur le rh.Poste est mal choisi 
 +        (Le nom de la relation sur le rh.Poste est mal choisi
          poste1 au lieu de dossier1)
          Note1 : seulement le dosssier principal fait l'objet de la recherche.
 -        Note2 : les dossiers sont retournés du plus récent au plus vieux. 
 -        (Ce test est fait en fonction du id, 
 +        Note2 : les dossiers sont retournés du plus récent au plus vieux.
 +        (Ce test est fait en fonction du id,
          car les dates de création sont absentes de rh v1).
          """
          if self.id_rh is None:
              return []
          postes = [p for p in self.id_rh.poste1.all()]
          return sorted(postes, key=lambda poste: poste.id, reverse=True)
 -            
 +
      def get_complement_nom(self):
          """
 -        Inspecte les modèles rh v1 pour trouver dans le dernier dossier 
 +        Inspecte les modèles rh v1 pour trouver dans le dernier dossier
          un complément de titre de poste.
          """
          dossiers = self.get_dossiers()
              return None
  
      def get_default_devise(self):
 -        """Récupère la devise par défaut en fonction de l'implantation 
 +        """Récupère la devise par défaut en fonction de l'implantation
          (EUR autrement)
          """
          try:
      #####################
  
      def get_couts_minimum(self):
 -        return (float)(self.salaire_min + self.indemn_expat_min + + self.indemn_fct_min +  self.charges_patronales_min + self.autre_min)
 +        return self.salaire_min + self.indemn_expat_min + self.indemn_fct_min + self.charges_patronales_min + self.autre_min
 +
 +    def get_salaire_minimum(self):
 +        return self.get_couts_minimum() - self.charges_patronales_min
  
      def get_taux_minimum(self):
 -        taux_changes = rh.TauxChange.objects.filter(devise=self.devise_min).order_by('annee')
 -        for t in taux_changes:
 -            if t.implantation == self.implantation:
 -                return t.taux
 -        if len(taux_changes) > 0:
 -            return taux_changes[0].taux
 +        if self.devise_min.code == 'EUR':
 +          return 1
 +        liste_taux = self.devise_min.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
 +        if len(liste_taux) == 0:
 +            raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_min, self.implantation))
          else:
 -            raise Exception('Taux indisponible pour la devise %s (%s)' % (self.devise_min, self.implantation))
 +            return liste_taux[0].taux
  
      def get_couts_minimum_euros(self):
 -        return self.get_couts_minimum() * self.get_taux_minimum()
 +        return float(self.get_couts_minimum()) * self.get_taux_minimum()
 +
 +    def get_salaire_minimum_euros(self):
 +        return float(self.get_salaire_minimum()) * self.get_taux_minimum()
  
      def get_couts_maximum(self):
 -        return (float)(self.salaire_max + self.indemn_expat_max + + self.indemn_fct_max +  self.charges_patronales_max + self.autre_max)
 +        return self.salaire_max + self.indemn_expat_max + self.indemn_fct_max + self.charges_patronales_max + self.autre_max
 +
 +    def get_salaire_maximum(self):
 +        return self.get_couts_maximum() - self.charges_patronales_max
  
      def get_taux_maximum(self):
 -        taux_changes = rh.TauxChange.objects.filter(devise=self.devise_max).order_by('annee')
 -        for t in taux_changes:
 -            if t.implantation == self.implantation:
 -                return t.taux
 -        if len(taux_changes) > 0:
 -            return taux_changes[0].taux
 +        if self.devise_max.code == 'EUR':
 +          return 1
 +        liste_taux = self.devise_max.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
 +        if len(liste_taux) == 0:
 +            raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_max, self.implantation))
          else:
 -            raise Exception('Taux indisponible pour la devise %s (%s)' % (self.devise_max, self.implantation))
 +            return liste_taux[0].taux
  
      def get_couts_maximum_euros(self):
 -        return self.get_couts_maximum() * self.get_taux_maximum()
 +        return float(self.get_couts_maximum()) * self.get_taux_maximum()
  
 +    def get_salaire_maximum_euros(self):
 +        return float(self.get_salaire_maximum()) * self.get_taux_maximum()
  
      def show_taux_minimum(self):
          try:
              return self.get_taux_minimum()
 -        except Exception, e:
 +        except DeviseException, e:
              return e
  
      def show_couts_minimum_euros(self):
          try:
              return self.get_couts_minimum_euros()
 -        except Exception, e:
 +        except DeviseException, e:
 +            return e
 +
 +    def show_salaire_minimum_euros(self):
 +        try:
 +            return self.get_salaire_minimum_euros()
 +        except DeviseException, e:
              return e
  
      def show_taux_maximum(self):
          try:
              return self.get_taux_maximum()
 -        except Exception, e:
 +        except DeviseException, e:
              return e
  
      def show_couts_maximum_euros(self):
          try:
              return self.get_couts_maximum_euros()
 -        except Exception, e:
 +        except DeviseException, e:
 +            return e
 +
 +    def show_salaire_maximum_euros(self):
 +        try:
 +            return self.get_salaire_maximum_euros()
 +        except DeviseException, e:
              return e
  
  
              return False
          else:
              return True
 -    
 +
  
      def get_taux_comparaison(self):
          try:
  
      def __unicode__(self):
          """
 -        Cette fonction est consommatrice SQL car elle cherche les dossiers 
 +        Cette fonction est consommatrice SQL car elle cherche les dossiers
          qui ont été liés à celui-ci.
          """
          complement_nom_poste = self.get_complement_nom()
@@@ -380,7 -364,7 +380,7 @@@ class PosteFinancement(models.Model)
          ordering = ['type']
  
      def __unicode__(self):
 -        return u"%s %s %s" % (self.get_type_display(), self.pourcentage, self.commentaire)
 +        return u"%s %.0f%% %s" % (self.get_type_display(), self.pourcentage, self.commentaire)
  
  
  class PostePiece(models.Model):
      Ex.: Description de poste
      """
      poste = models.ForeignKey("Poste")
-     nom = models.CharField(verbose_name="Nom", max_length=255)
-     fichier = models.FileField(verbose_name="Fichier", upload_to='dae/poste', storage=UPLOAD_STORAGE)
+     nom = models.CharField(verbose_name=u"Nom", max_length=255)
+     fichier = models.FileField(verbose_name=u"Fichier", 
 -                            upload_to=poste_piece_dispatch, 
 -                            storage=storage_prive)
++                    upload_to='dae/poste', 
++                    storage=UPLOAD_STORAGE)
  
  class PosteComparaison(models.Model):
      poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
      implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
-     statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name='Statut', null=True, blank=True, )
-     classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name='Classement', null=True, blank=True, )
-     nom = models.CharField(verbose_name="Poste", max_length=255, null=True, blank=True)
+     statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name=u'Statut', null=True, blank=True, )
+     classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name=u'Classement', null=True, blank=True, )
+     nom = models.CharField(verbose_name=u"Poste", max_length=255, null=True, blank=True)
      montant = models.IntegerField(null=True)
      devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
  
 +    objects = PosteComparaisonManager()
 +
      def taux_devise(self):
 +        if self.devise.code == 'EUR':
 +          return 1
          liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
          if len(liste_taux) == 0:
 -            raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
 +            raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
          else:
              return liste_taux[0].taux
  
@@@ -427,10 -409,10 +429,10 @@@ GENRE_CHOICES = 
  class Employe(models.Model):
  
      # Modèle existant
 -    id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+', 
 +    id_rh = models.ForeignKey(rh.Employe, null=True, related_name='+',
-                             verbose_name='Employé')
+                             verbose_name=u'Employé')
      nom = models.CharField(max_length=255)
-     prenom = models.CharField(max_length=255, verbose_name='Prénom')
+     prenom = models.CharField(max_length=255, verbose_name=u'Prénom')
      genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
  
      def __unicode__(self):
@@@ -456,69 -438,67 +458,72 @@@ class Dossier(DossierWorkflow, models.M
      employe = models.ForeignKey('Employe', related_name='+', editable=False)
      poste = models.ForeignKey('Poste', related_name='dossiers', editable=False)
      statut = models.ForeignKey(rh.Statut, related_name='+')
 -    organisme_bstg = models.ForeignKey(rh.OrganismeBstg, 
 +    organisme_bstg = models.ForeignKey(rh.OrganismeBstg,
              null=True, blank=True,
-             verbose_name="Organisme",
 -            verbose_name=u"Organisme", 
++            verbose_name=u"Organisme",
              help_text="Si détaché (DET) ou mis à disposition (MAD), \
                      préciser l'organisme.",
              related_name='+')
      organisme_bstg_autre = models.CharField(max_length=255,
-         verbose_name="Autre organisme",
+         verbose_name=u"Autre organisme",
          help_text="indiquer l'organisme ici s'il n'est pas dans la liste",
          null=True,
          blank=True,)
  
      # Données antérieures de l'employé
 -    # la devise??
      statut_anterieur = models.ForeignKey(
              rh.Statut, related_name='+', null=True, blank=True,
-             verbose_name='Statut antérieur')
+             verbose_name=u'Statut antérieur')
      classement_anterieur = models.ForeignKey(
              rh.Classement, related_name='+', null=True, blank=True,
-             verbose_name='Classement précédent')
+             verbose_name=u'Classement précédent')
      salaire_anterieur = models.DecimalField(
              max_digits=12, decimal_places=2, null=True, default=None,
 -            blank=True, verbose_name=u'Salaire précédent')
 +            blank=True, verbose_name='Salaire précédent')
-     devise_anterieur = models.ForeignKey(rh.Devise, related_name='+', null=True, blank=True)
-     type_contrat_anterieur = models.ForeignKey(rh.TypeContrat, related_name='+', null=True, blank=True, verbose_name=u'Type contrat antérieur', )
++    devise_anterieur = models.ForeignKey(rh.Devise, related_name='+', 
++            null=True, blank=True)
++    type_contrat_anterieur = models.ForeignKey(rh.TypeContrat, 
++            related_name='+', null=True, blank=True, 
++            verbose_name=u'Type contrat antérieur', )
  
      # Données du titulaire précédent
      employe_anterieur = models.ForeignKey(
              rh.Employe, related_name='+', null=True, blank=True,
-             verbose_name='Employé précédent')
+             verbose_name=u'Employé précédent')
      statut_titulaire_anterieur = models.ForeignKey(
              rh.Statut, related_name='+', null=True, blank=True,
-             verbose_name='Statut titulaire précédent')
+             verbose_name=u'Statut titulaire précédent')
      classement_titulaire_anterieur = models.ForeignKey(
              rh.Classement, related_name='+', null=True, blank=True,
-             verbose_name='Classement titulaire précédent')
+             verbose_name=u'Classement titulaire précédent')
      salaire_titulaire_anterieur = models.DecimalField(
              max_digits=12, decimal_places=2, default=None, null=True,
 -            blank=True, verbose_name=u'Salaire titulaire précédent')
 +            blank=True, verbose_name='Salaire titulaire précédent')
 +    devise_titulaire_anterieur = models.ForeignKey(rh.Devise, related_name='+', null=True, blank=True)
  
      # Recrutement
      remplacement = models.BooleanField()
 -    statut_residence = models.CharField(max_length=10, default='local', 
 -                            verbose_name=u"Statut",
 +    statut_residence = models.CharField(max_length=10, default='local',
 +                            verbose_name="Statut",
                              choices=STATUT_RESIDENCE_CHOICES)
  
      # Rémunération
      classement = models.ForeignKey(rh.Classement, related_name='+',
                              null=True, blank=True,
-                             verbose_name='Classement proposé')
+                             verbose_name=u'Classement proposé')
      salaire = models.DecimalField(max_digits=12, decimal_places=2,
-                             verbose_name='Salaire de base',
+                             verbose_name=u'Salaire de base',
                              null=True, default=None)
      devise = models.ForeignKey(rh.Devise, default=5, related_name='+')
 -    regime_travail = models.DecimalField(max_digits=12, 
 +    regime_travail = models.DecimalField(max_digits=12,
                              decimal_places=2,
                              default=REGIME_TRAVAIL_DEFAULT,
-                             verbose_name="Régime de travail",
+                             verbose_name=u"Régime de travail",
                              help_text="% du temps complet")
      regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
 -                            decimal_places=2, 
 +                            decimal_places=2,
                              default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
-                             verbose_name="Nb. heures par semaine")
+                             verbose_name=u"Nb. heures par semaine")
  
      # Contrat
      type_contrat = models.ForeignKey(rh.TypeContrat, related_name='+')
                                      verbose_name=u'Compte comptabilité',
                                      choices=COMPTE_COMPTA_CHOICES)
      compte_courriel = models.BooleanField()
 -    
 +
      # Méta
      date_creation = models.DateTimeField(auto_now_add=True)
 - 
 +
      # Managers
      objects = DossierManager()
 -   
 +
      def __unicode__(self):
          return u'[%s] %s - %s' % (self.poste.implantation, self.poste.nom, self.employe)
  
 +    def taux_devise(self):
 +        if self.devise.code == 'EUR':
 +          return 1
 +        liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.poste.implantation)
 +        if len(liste_taux) == 0:
 +            raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.poste.implantation))
 +        else:
 +            return liste_taux[0].taux
 +
 +    def get_salaire_anterieur_euros(self):
 +        if self.devise_anterieur.code == 'EUR':
 +          tx = 1
 +        else:
 +            liste_taux = self.devise_anterieur.tauxchange_set.order_by('-annee').filter(implantation=self.poste.implantation)
 +            if len(liste_taux) == 0:
 +                raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_anterieur, self.poste.implantation))
 +            tx = liste_taux[0].taux
 +        return (float)(tx) * (float)(self.salaire_anterieur)
 +
 +    def get_salaire_titulaire_anterieur_euros(self):
 +        if self.devise_titulaire_anterieur.code == 'EUR':
 +          tx = 1
 +        else:
 +            liste_taux = self.devise_titulaire_anterieur.tauxchange_set.order_by('-annee').filter(implantation=self.poste.implantation)
 +            if len(liste_taux) == 0:
 +                raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise_titulaire_anterieur, self.poste.implantation))
 +            tx = liste_taux[0].taux
 +        return (float)(tx) * (float)(self.salaire_titulaire_anterieur)
 +
      def get_salaire_euros(self):
 -        try:
 -            tx = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.poste.implantation)[0].taux
 -        except:
 -            tx = 0
 +        tx = self.taux_devise()
          return (float)(tx) * (float)(self.salaire)
  
      def get_remunerations_brutes(self):
  
      def get_total_remunerations_tierces(self):
          total = 0.0
 -        for r in self.get_remunerations_tierces():    
 +        for r in self.get_remunerations_tierces():
              total += r.montant_euro()
          return total
  
 +    def valide(self):
 +        return self.etat in (DOSSIER_ETAT_REGION_FINALISATION,
 +                             DOSSIER_ETAT_DRH_FINALISATION,
 +                             DOSSIER_ETAT_FINALISE)
 +
  
  # Tester l'enregistrement car les models.py sont importés au complet
  if not reversion.is_registered(Dossier):
@@@ -697,8 -646,10 +702,10 @@@ class DossierPiece(models.Model)
      Ex.: Lettre de motivation.
      """
      dossier = models.ForeignKey("Dossier")
 -    nom = models.CharField(verbose_name=u"Nom", max_length=255)
 -    fichier = models.FileField(verbose_name=u"Fichier", 
 -                            upload_to=dossier_piece_dispatch, 
 -                            storage=storage_prive)
 +    nom = models.CharField(verbose_name="Nom", max_length=255)
-     fichier = models.FileField(verbose_name="Fichier", upload_to='dae/dossier', storage=UPLOAD_STORAGE)
++    fichier = models.FileField(verbose_name="Fichier", 
++            upload_to='dae/dossier', 
++            storage=UPLOAD_STORAGE)
  
  
  class DossierComparaison(models.Model):
      Photo d'une comparaison salariale au moment de l'embauche.
      """
      dossier = models.ForeignKey('Dossier', related_name='comparaisons')
-     statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name='Statut', null=True, blank=True, )
-     classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name='Classement', null=True, blank=True, )
-     implantation = models.ForeignKey(ref.Implantation, related_name='+', null=True, blank=True)
 -    statut = models.ForeignKey(rh.Statut, related_name='+', verbose_name=u'Statut', null=True, blank=True, )
 -    classement = models.ForeignKey(rh.Classement, related_name='+', verbose_name=u'Classement', null=True, blank=True, )
 -    implantation = models.ForeignKey(ref.Implantation, null=True, blank=True)
++    statut = models.ForeignKey(rh.Statut, related_name='+', 
++            verbose_name='Statut', null=True, blank=True, )
++    classement = models.ForeignKey(rh.Classement, related_name='+', 
++            verbose_name='Classement', null=True, blank=True, )
++    implantation = models.ForeignKey(ref.Implantation, related_name='+', 
++            null=True, blank=True)
      poste = models.CharField(max_length=255, null=True, blank=True)
      personne = models.CharField(max_length=255, null=True, blank=True)
      montant = models.IntegerField(null=True)
      devise = models.ForeignKey(rh.Devise, default=5, related_name='+', null=True, blank=True)
  
 +    objects = DossierComparaisonManager()
 +
      def taux_devise(self):
 -        liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
 +        if self.devise.code == 'EUR':
 +          return 1
 +        liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
          if len(liste_taux) == 0:
 -            raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
 +            raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
          else:
              return liste_taux[0].taux
  
@@@ -751,11 -698,9 +761,11 @@@ class Remuneration(models.Model)
          return round(self.montant / 12, 2)
  
      def taux_devise(self):
 +        if self.devise.code == 'EUR':
 +          return 1
          liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
          if len(liste_taux) == 0:
 -            raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
 +            raise DeviseException(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
          else:
              return liste_taux[0].taux
  
  
      def montant_euro_mois(self):
          return round(self.montant_euro() / 12, 2)
 +
 +
 +### CONTRATS
 +
 +class Contrat(models.Model):
 +    dossier = models.ForeignKey(Dossier, related_name='contrats')
 +    type = models.ForeignKey(rh.TypeContrat, related_name='+')
 +    fichier = models.FileField(upload_to='dae/contrats', storage=UPLOAD_STORAGE)
 +
@@@ -7,11 -7,7 +7,11 @@@ from tinymce import models as tinymce_m
  from django.db import models
  import settings
  
 +from south.modelsinspector import add_introspection_rules
 +add_introspection_rules([], ["^tinymce.models.HTMLField"])
  import datamaster_modeles.models as ref
 +
 +from project.rh import models as rh
  from recrutement.workflow import grp_evaluateurs_recrutement
  from auf.django.emploi import models as emploi
  from auf.django.emploi.models import TYPE_PIECE_CHOICES
@@@ -52,17 -48,6 +52,17 @@@ class Candidat(emploi.Candidat)
      class Meta:
          proxy = True
  
 +    def moyenne_notes(self):
 +        evaluations = CandidatEvaluation.objects.filter(candidat=self)
 +        notes = [evaluation.note for evaluation in evaluations.all() \
 +                    if evaluation.note is not None]
 +
 +        if len(notes) > 0:
 +            moyenne_votes = float(sum(notes)) / len(notes)
 +        else:
 +            moyenne_votes = "Non disponible"
 +        return moyenne_votes
 +
  class OffreEmploi(emploi.OffreEmploi):
      class Meta:
          proxy = True
@@@ -81,8 -66,8 +81,8 @@@ class OffreEmploiManager(models.Manager
  class ProxyOffreEmploi(emploi.OffreEmploi):
      class Meta:
          proxy = True
-         verbose_name = "Offre d'emploi (visualisation)"
-         verbose_name_plural = "Offres d'emploi (visualisation)"
+         verbose_name = u"Offre d'emploi (visualisation)"
+         verbose_name_plural = u"Offres d'emploi (visualisation)"
  
      def __unicode__(self):
          return '%s [%s] - View' % (self.nom, self.id)
  class ProxyCandidat(emploi.Candidat):
      class Meta:
          proxy = True
-         verbose_name = "Candidat (visualisation)"
-         verbose_name_plural = "Candidats (visualisation)"
+         verbose_name = u"Candidat (visualisation)"
+         verbose_name_plural = u"Candidats (visualisation)"
  
      def __unicode__(self):
          return '%s %s [%s]' % (self.prenom, self.nom, self.id)
  
  class Evaluateur(models.Model):
-     user = models.ForeignKey(User, unique=True, verbose_name="Évaluateur")
+     user = models.ForeignKey(User, unique=True, verbose_name=u"Évaluateur")
      offres_emploi = models.ManyToManyField(emploi.OffreEmploi, 
                  related_name="evaluateurs", blank=True)
  
          super(Evaluateur, self).save(*args, **kwargs)
      
      class Meta:
-         verbose_name = "évaluateur"
+         verbose_name = u"évaluateur"
  
      def __unicode__(self):
          return '%s %s' % (self.user.first_name, self.user.last_name)
  
 -class AdministrateurRegional(models.Model):
 -    user = models.ForeignKey(User, unique=True,
 -            verbose_name=u"Administrateur régionnal")
 -    regions = models.ManyToManyField(ref.Region, 
 -                           verbose_name=u"Régions", )
 -
 -    class Meta:
 -        verbose_name = u"administrateur régional"
 -        verbose_name_plural = u"administrateurs régionaux"
 -
 -    def __unicode__(self):
 -        return '%s %s' % (self.user.first_name, self.user.last_name)
 -
  class CandidatEvaluation(models.Model):
      candidat = models.ForeignKey(emploi.Candidat, db_column='candidat', 
                  related_name='+',) 
      evaluateur = models.ForeignKey(Evaluateur, db_column='evaluateur', 
-                     related_name='+', verbose_name='Évaluateur') 
+                     related_name='+', verbose_name=u'Évaluateur') 
      note = models.IntegerField(choices=NOTES, blank=True, null=True)
      commentaire = models.TextField(null=True, blank=True, default='Aucun')
      date = models.DateField(auto_now_add=True,
                          help_text=HELP_TEXT_FORMAT_DATE, )  
  
      class Meta:
-         verbose_name = 'évaluation du candidat'
-         verbose_name_plural = 'évaluations des candidats'
+         verbose_name = u'évaluation du candidat'
+         verbose_name_plural = u'évaluations des candidats'
  
 +    def __unicode__(self):
 +        return u"Évaluation de %s" % self.candidat
 +
  #### TEMPLATE COURRIEL
  TEMPLATE_CHOICES = (
      ('SEL', 'Sélectionné'),
  )
  
  class CourrielTemplate(models.Model):
-     nom_modele = models.CharField(max_length=100, verbose_name='Nom du modèle',)
-     sujet = models.CharField(max_length=100, verbose_name='Sujet du courriel')
-     plain_text = models.TextField(verbose_name='Texte', 
+     nom_modele = models.CharField(max_length=100, verbose_name=u'Nom du modèle',)
+     sujet = models.CharField(max_length=100, verbose_name=u'Sujet du courriel')
+     plain_text = models.TextField(verbose_name=u'Texte', 
                                      help_text=HELP_TEXT_TAGS_ACCEPTES,  )
-     html = tinymce_models.HTMLField(verbose_name='Texte en HTML', 
+     html = tinymce_models.HTMLField(verbose_name=u'Texte en HTML', 
                                      help_text=HELP_TEXT_TAGS_ACCEPTES,  )
  
 +
      def __unicode__(self):
          return u'%s' % self.nom_modele
  
      class Meta:
          ordering = ['nom_modele',]
 +        verbose_name = "Modèle de courriel"
 +        verbose_name_plural = "Modèles de courriel"
  
  class CandidatCourriel(models.Model):
-     candidats = models.ManyToManyField(Candidat, verbose_name="Candidats", )
+     candidats = models.ManyToManyField(Candidat, verbose_name=u"Candidats", )
      template = models.ForeignKey(CourrielTemplate, db_column='template', 
-                 related_name='+', verbose_name="Modèle de courriel", )
+                 related_name='+', verbose_name=u"Modèle de courriel", )
      sujet = models.CharField(max_length=255, blank=True, 
                                  help_text=HELP_TEXT_TAGS_ACCEPTES, )
-     plain_text = models.TextField(verbose_name='Texte', blank=True,
+     plain_text = models.TextField(verbose_name=u'Texte', blank=True,
                                      help_text=HELP_TEXT_TAGS_ACCEPTES,  )
-     html = tinymce_models.HTMLField(verbose_name='Texte en HTML', null=True, 
+     html = tinymce_models.HTMLField(verbose_name=u'Texte en HTML', null=True, 
                                  blank=True, help_text=HELP_TEXT_TAGS_ACCEPTES, )
  
      def __unicode__(self):
          return '%s' % (self.titre)
  
      class Meta:
-         verbose_name = "modèle de courriel"
-         verbose_name_plural = "modèles de courriel"
+         verbose_name = u"modèle de courriel"
+         verbose_name_plural = u"modèles de courriel"
 -    
diff --combined project/rh/models.py
@@@ -1,11 -1,8 +1,11 @@@
  # -=- encoding: utf-8 -=-
  
 +from datetime import date
 +
  from django.core.files.storage import FileSystemStorage
  from django.db import models
  from django.conf import settings
 +
  from auf.django.metadata.models import AUFMetadata
  from auf.django.metadata.managers import NoDeleteManager
  import datamaster_modeles.models as ref
@@@ -29,10 -26,6 +29,10 @@@ def dossier_piece_dispatch(instance, fi
      path = "dossier/%s/%s" % (instance.dossier_id, filename)
      return path
  
 +def employe_piece_dispatch(instance, filename):
 +    path = "employe/%s/%s" % (instance.employe_id, filename)
 +    return path
 +
  
  class Commentaire(AUFMetadata):
      texte = models.TextField()
@@@ -67,9 -60,9 +67,9 @@@ class Poste_(AUFMetadata)
  
      # Identification
      nom = models.CharField(max_length=255, 
-                             verbose_name="Titre du poste", )
+                             verbose_name = u"Titre du poste", )
      nom_feminin = models.CharField(max_length=255,
-                             verbose_name="Titre du poste (au féminin)",
+                             verbose_name = u"Titre du poste (au féminin)",
                              null=True)
      implantation = models.ForeignKey(ref.Implantation, 
                              db_column='implantation', related_name='+')
                              null=True)
      service = models.ForeignKey('Service', db_column='service', null=True,
                              related_name='+',
-                             verbose_name="Direction/Service/Pôle support",
+                             verbose_name = u"Direction/Service/Pôle support",
                              default=1)  # default = Rectorat
      responsable = models.ForeignKey('Poste', db_column='responsable', 
                              related_name='+', null=True,
-                             verbose_name="Poste du responsable",
+                             verbose_name = u"Poste du responsable",
                              default=149)    # default = Recteur
                                  
      # Contrat
      regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
                              default=REGIME_TRAVAIL_DEFAULT, null=True,
-                             verbose_name="Temps de travail", 
+                             verbose_name = u"Temps de travail", 
                              help_text="% du temps complet")
      regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
                              decimal_places=2, null=True,
                              default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
-                             verbose_name="Nb. heures par semaine")
+                             verbose_name = u"Nb. heures par semaine")
  
      # Recrutement
-     local = models.NullBooleanField(verbose_name="Local", default=True, 
+     local = models.NullBooleanField(verbose_name = u"Local", default=True, 
                              null=True, blank=True)
-     expatrie = models.NullBooleanField(verbose_name="Expatrié", default=False, 
+     expatrie = models.NullBooleanField(verbose_name = u"Expatrié", default=False, 
                              null=True, blank=True)
      mise_a_disposition = models.NullBooleanField(
-                             verbose_name="Mise à disposition",
+                             verbose_name = u"Mise à disposition",
                              null=True, default=False)
      appel = models.CharField(max_length=10, null=True,
-                             verbose_name="Appel à candidature",
+                             verbose_name = u"Appel à candidature",
                              choices=POSTE_APPEL_CHOICES,
                              default='interne')
  
  
      # Autres Metadata
      date_validation = models.DateTimeField(null=True, blank=True)   # de dae
-     date_debut = models.DateField(verbose_name="Date de début",
+     date_debut = models.DateField(verbose_name=u"Date de début",
                              help_text=HELP_TEXT_DATE,
                              null=True, blank=True)
-     date_fin = models.DateField(verbose_name="Date de fin",
+     date_fin = models.DateField(verbose_name=u"Date de fin",
                              help_text=HELP_TEXT_DATE,
                              null=True, blank=True)
  
      class Meta:
          abstract = True
          ordering = ['implantation__nom', 'nom']
-         verbose_name = "Poste"
-         verbose_name_plural = "Postes"
+         verbose_name = u"Poste"
+         verbose_name_plural = u"Postes"
  
      def __unicode__(self):
          representation = u'%s - %s [%s]' % (self.implantation, self.nom, 
@@@ -240,8 -233,8 +240,8 @@@ class PostePiece(models.Model)
      """
      poste = models.ForeignKey('Poste', db_column='poste', 
                              related_name='pieces')
-     nom = models.CharField(verbose_name="Nom", max_length=255)
-     fichier = models.FileField(verbose_name="Fichier", 
+     nom = models.CharField(verbose_name = u"Nom", max_length=255)
+     fichier = models.FileField(verbose_name = u"Fichier", 
                              upload_to=poste_piece_dispatch, 
                              storage=storage_prive)
  
@@@ -257,7 -250,7 +257,7 @@@ class PosteComparaison(models.Model)
      """
      poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
      implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
-     nom = models.CharField(verbose_name="Poste", max_length=255, null=True, blank=True)
+     nom = models.CharField(verbose_name = u"Poste", max_length=255, null=True, blank=True)
      montant = models.IntegerField(null=True)
      devise = models.ForeignKey("Devise", default=5, related_name='+', null=True, blank=True)
  
@@@ -297,16 -290,16 +297,16 @@@ class Employe(AUFMetadata)
      """
      # Identification
      nom = models.CharField(max_length=255)
-     prenom = models.CharField(max_length=255, verbose_name="Prénom")
+     prenom = models.CharField(max_length=255, verbose_name = u"Prénom")
      nom_affichage = models.CharField(max_length=255, 
-                             verbose_name="Nom d'affichage",
+                             verbose_name = u"Nom d'affichage",
                              null=True, blank=True)
      nationalite = models.ForeignKey(ref.Pays, to_field='code', 
                              db_column='nationalite',
                              related_name='employes_nationalite',
-                             verbose_name="Nationalité")
+                             verbose_name = u"Nationalité")
      date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
-                             verbose_name="Date de naissance",
+                             verbose_name = u"Date de naissance",
                              validators=[validate_date_passee],
                              null=True, blank=True)
      genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
      # Infos personnelles
      situation_famille = models.CharField(max_length=1, 
                              choices=SITUATION_CHOICES,
-                             verbose_name="Situation familiale",
+                             verbose_name = u"Situation familiale",
                              null=True, blank=True)
-     date_entree = models.DateField(verbose_name="Date d'entrée à l'AUF",
+     date_entree = models.DateField(verbose_name = u"Date d'entrée à l'AUF",
                              help_text=HELP_TEXT_DATE, 
                              null=True, blank=True)
      
      # Coordonnées
      tel_domicile = models.CharField(max_length=255, 
-                             verbose_name="Tél. domicile",
+                             verbose_name = u"Tél. domicile",
                              null=True, blank=True)
      tel_cellulaire = models.CharField(max_length=255, 
-                             verbose_name="Tél. cellulaire",
+                             verbose_name = u"Tél. cellulaire",
                              null=True, blank=True)
      adresse = models.CharField(max_length=255, null=True, blank=True)
      ville = models.CharField(max_length=255, null=True, blank=True)
  
      class Meta:
          ordering = ['nom_affichage','nom','prenom']
-         verbose_name = "Employé"
-         verbose_name_plural = "Employés"
+         verbose_name = u"Employé"
+         verbose_name_plural = u"Employés"
          
      def __unicode__(self):
          return u'%s' % (self.get_nom())
          elif self.genre.upper() == u'F':
              civilite = u'Mme'
          return civilite
 +        
 +    def url_photo(self):
 +        """Retourne l'URL du service retournant la photo de l'Employe.
 +        Équivalent reverse url 'rh_photo' avec id en param.
 +        """
 +        from django.core.urlresolvers import reverse
 +        return reverse('rh_photo', kwargs={'id':self.id})
 +               
 +    def dossiers_passes(self):
 +        today = date.today()
 +        dossiers_passes = self.dossiers.filter(date_fin__lt=today).order_by('-date_fin')
 +        for d in dossiers_passes:
 +            d.archive = True
 +        return dossiers_passes
 +        
 +    def dossiers_futurs(self):
 +        today = date.today()
 +        return self.dossiers.filter(date_debut__gt=today).order_by('-date_fin')
 +        
 +    def dossiers_encours(self):
 +        dossiers_p_f = self.dossiers_passes() | self.dossiers_futurs()
 +        ids_dossiers_p_f = [d.id for d in dossiers_p_f]
 +        dossiers_encours = self.dossiers.exclude(id__in=ids_dossiers_p_f).order_by('-date_fin')
 +        
 +        # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
 +        for d in dossiers_encours:
 +            d.remunerations = Remuneration.objects.filter(dossier=d.id).order_by('-id')
 +        return dossiers_encours
 +        
 +    def postes_encours(self):
 +        postes_encours = set()
 +        for d in self.dossiers_encours():
 +            postes_encours.add(d.poste)
 +        return postes_encours
 +        
 +    def poste_principal(self):
 +        """
 +        Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
 +        Idée derrière : 
 +        si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
 +        """
 +        poste = Poste.objects.none()
 +        try:
 +            poste = self.dossiers_encours().order_by('date_debut')[0].poste
 +        except:
 +            pass
 +        return poste
  
  class EmployePiece(models.Model):
      """Documents relatifs à un employé.
      """
      employe = models.ForeignKey('Employe', db_column='employe', 
                              related_name='+')
 -    nom = models.CharField(verbose_name = u"Nom", max_length=255)
 -    fichier = models.FileField(verbose_name = u"Fichier", 
 -                            upload_to=dossier_piece_dispatch, 
 +    nom = models.CharField(verbose_name="Nom", max_length=255)
 +    fichier = models.FileField(verbose_name="Fichier", 
 +                            upload_to=employe_piece_dispatch, 
                              storage=storage_prive)
  
      class Meta:
@@@ -439,16 -385,16 +439,16 @@@ class AyantDroit(AUFMetadata)
      # Identification
      nom = models.CharField(max_length=255)
      prenom = models.CharField(max_length=255,
-                             verbose_name="Prénom",)
+                             verbose_name = u"Prénom",)
      nom_affichage = models.CharField(max_length=255, 
-                             verbose_name="Nom d'affichage",
+                             verbose_name = u"Nom d'affichage",
                              null=True, blank=True)
      nationalite = models.ForeignKey(ref.Pays, to_field='code', 
                              db_column='nationalite',
                              related_name='ayantdroits_nationalite',
-                             verbose_name="Nationalité")
+                             verbose_name = u"Nationalité")
      date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
-                             verbose_name="Date de naissance",
+                             verbose_name = u"Date de naissance",
                              validators=[validate_date_passee],
                              null=True, blank=True)
      genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
      # Relation
      employe = models.ForeignKey('Employe', db_column='employe', 
                              related_name='ayantdroits',
-                             verbose_name="Employé")
+                             verbose_name = u"Employé")
      lien_parente = models.CharField(max_length=10, 
                              choices=LIEN_PARENTE_CHOICES,
-                             verbose_name="Lien de parenté",
+                             verbose_name = u"Lien de parenté",
                              null=True, blank=True)
  
      class Meta:
          ordering = ['nom_affichage']
-         verbose_name = "Ayant droit"
-         verbose_name_plural = "Ayants droit"
+         verbose_name = u"Ayant droit"
+         verbose_name_plural = u"Ayants droit"
          
      def __unicode__(self):
          return u'%s' % (self.get_nom())
@@@ -506,14 -452,14 +506,14 @@@ class Dossier_(AUFMetadata)
      # Identification
      employe = models.ForeignKey('Employe', db_column='employe', 
                              related_name='dossiers',
-                             verbose_name="Employé")
+                             verbose_name=u"Employé")
      poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
      statut = models.ForeignKey('Statut', related_name='+', default=3,
                              null=True)
      organisme_bstg = models.ForeignKey('OrganismeBstg', 
                              db_column='organisme_bstg',
                              related_name='+',
-                             verbose_name="Organisme", 
+                             verbose_name = u"Organisme", 
                              help_text="Si détaché (DET) ou \
                                      mis à disposition (MAD), \
                                      préciser l'organisme.",
      # Recrutement
      remplacement = models.BooleanField(default=False)
      statut_residence = models.CharField(max_length=10, default='local', 
-                             verbose_name="Statut", null=True,
+                             verbose_name = u"Statut", null=True,
                              choices=STATUT_RESIDENCE_CHOICES)
     
      # Rémunération
      regime_travail = models.DecimalField(max_digits=12, null=True,
                              decimal_places=2,
                              default=REGIME_TRAVAIL_DEFAULT,
-                             verbose_name="Régime de travail",
+                             verbose_name = u"Régime de travail",
                              help_text="% du temps complet")
      regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
                              decimal_places=2, null=True,
                              default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
-                             verbose_name="Nb. heures par semaine")
+                             verbose_name = u"Nb. heures par semaine")
  
      # Occupation du Poste par cet Employe (anciennement "mandat")
-     date_debut = models.DateField(verbose_name="Date de début d'occupation \
+     date_debut = models.DateField(verbose_name = u"Date de début d'occupation \
                              de poste",
                              help_text=HELP_TEXT_DATE)
-     date_fin = models.DateField(verbose_name="Date de fin d'occupation \
+     date_fin = models.DateField(verbose_name = u"Date de fin d'occupation \
                              de poste",
                              help_text=HELP_TEXT_DATE,
                              null=True, blank=True)
      class Meta:
          abstract = True
          ordering = ['employe__nom', ]
-         verbose_name = "Dossier"
+         verbose_name = u"Dossier"
          verbose_name_plural = "Dossiers"
          
 +    def salaire_theorique(self):
 +        annee = date.today().year
 +        coeff = self.classement.coefficient
 +        implantation = self.poste.implantation
 +        point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
 +        
 +        montant = coeff * point.valeur
 +        devise = point.devise
 +        return {'montant':montant, 'devise':devise}
 +        
      def __unicode__(self):
          poste = self.poste.nom
          if self.employe.genre == 'F':
@@@ -584,8 -520,8 +584,8 @@@ class DossierPiece(models.Model)
      """
      dossier = models.ForeignKey('Dossier', db_column='dossier', 
                              related_name='+')
-     nom = models.CharField(verbose_name="Nom", max_length=255)
-     fichier = models.FileField(verbose_name="Fichier", 
+     nom = models.CharField(verbose_name = u"Nom", max_length=255)
+     fichier = models.FileField(verbose_name = u"Fichier", 
                              upload_to=dossier_piece_dispatch, 
                              storage=storage_prive)
  
@@@ -629,11 -565,11 +629,11 @@@ class RemunerationMixin(AUFMetadata)
                          related_name='%(app_label)s_%(class)s_remunerations')
      type = models.ForeignKey('TypeRemuneration', db_column='type', 
                              related_name='+',
-                             verbose_name="Type de rémunération")
+                             verbose_name = u"Type de rémunération")
      type_revalorisation = models.ForeignKey('TypeRevalorisation', 
                              db_column='type_revalorisation', 
                              related_name='+',
-                             verbose_name="Type de revalorisation",
+                             verbose_name = u"Type de revalorisation",
                              null=True, blank=True)
      montant = models.FloatField(null=True, blank=True,
                              default=0)
      commentaire = models.CharField(max_length=255, null=True, blank=True)
      # date_debut = anciennement date_effectif
      date_debut = models.DateField(help_text=HELP_TEXT_DATE,
-                             verbose_name="Date de début",
+                             verbose_name = u"Date de début",
                              null=True, blank=True)
      date_fin = models.DateField(help_text=HELP_TEXT_DATE,
-                             verbose_name="Date de fin",
+                             verbose_name = u"Date de fin",
                              null=True, blank=True)
      
      class Meta: 
@@@ -685,8 -621,8 +685,8 @@@ class Remuneration_(RemunerationMixin)
  
      class Meta:
          abstract = True
-         verbose_name = "Rémunération"
-         verbose_name_plural = "Rémunérations"
+         verbose_name = u"Rémunération"
+         verbose_name_plural = u"Rémunérations"
  
  
  class Remuneration(Remuneration_):
@@@ -709,20 -645,20 +709,20 @@@ class Contrat(AUFMetadata)
      objects = ContratManager()
  
      dossier = models.ForeignKey('Dossier', db_column='dossier', 
 -                            related_name='+')
 +                            related_name='contrats')
      type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat', 
                              related_name='+',
-                             verbose_name="Type de contrat")
+                             verbose_name = u"Type de contrat")
      date_debut = models.DateField(help_text=HELP_TEXT_DATE,
-                             verbose_name="Date de début")
+                             verbose_name = u"Date de début")
      date_fin = models.DateField(help_text=HELP_TEXT_DATE,
-                             verbose_name="Date de fin",
+                             verbose_name = u"Date de fin",
                              null=True, blank=True)
  
      class Meta:
          ordering = ['dossier__employe__nom_affichage']
-         verbose_name = "Contrat"
-         verbose_name_plural = "Contrats"
+         verbose_name = u"Contrat"
+         verbose_name_plural = u"Contrats"
          
      def __unicode__(self):
          return u'%s - %s' % (self.dossier, self.id)
@@@ -748,16 -684,16 +748,16 @@@ class Evenement_(AUFMetadata)
                              related_name='+')
      nom = models.CharField(max_length=255)
      date_debut = models.DateField(help_text=HELP_TEXT_DATE,
-                             verbose_name="Date de début")
+                             verbose_name = u"Date de début")
      date_fin = models.DateField(help_text=HELP_TEXT_DATE,
-                             verbose_name="Date de fin",
+                             verbose_name = u"Date de fin",
                              null=True, blank=True)
  
      class Meta:
          abstract = True
          ordering = ['nom']
-         verbose_name = "Évènement"
-         verbose_name_plural = "Évènements"
+         verbose_name = u"Évènement"
+         verbose_name_plural = u"Évènements"
                              
      def __unicode__(self):
          return u'%s' % (self.nom)
@@@ -774,15 -710,15 +774,15 @@@ class EvenementRemuneration_(Remunerati
      """
      evenement = models.ForeignKey("Evenement", db_column='evenement',
                              related_name='+',
-                             verbose_name="Évènement")
+                             verbose_name = u"Évènement")
      # TODO : le champ dossier hérité de Remuneration doit être dérivé
      # de l'Evenement associé
  
      class Meta:
          abstract = True
          ordering = ['evenement', 'type__nom', '-date_fin']
-         verbose_name = "Évènement - rémunération"
-         verbose_name_plural = "Évènements - rémunérations"
+         verbose_name = u"Évènement - rémunération"
+         verbose_name_plural = u"Évènements - rémunérations"
  
  
  class EvenementRemuneration(EvenementRemuneration_):
@@@ -806,8 -742,8 +806,8 @@@ class FamilleEmploi(AUFMetadata)
      
      class Meta:
          ordering = ['nom']
-         verbose_name = "Famille d'emploi"
-         verbose_name_plural = "Familles d'emploi"
+         verbose_name = u"Famille d'emploi"
+         verbose_name_plural = u"Familles d'emploi"
      
      def __unicode__(self):
          return u'%s' % (self.nom)
@@@ -817,19 -753,19 +817,19 @@@ class TypePoste(AUFMetadata)
      """
      nom = models.CharField(max_length=255)
      nom_feminin = models.CharField(max_length=255,
-                             verbose_name="Nom féminin")
+                             verbose_name = u"Nom féminin")
      
      is_responsable = models.BooleanField(default=False,
-                             verbose_name="Poste de responsabilité")
+                             verbose_name = u"Poste de responsabilité")
      famille_emploi = models.ForeignKey('FamilleEmploi', 
                              db_column='famille_emploi',
                              related_name='+',
-                             verbose_name="Famille d'emploi")
+                             verbose_name = u"Famille d'emploi")
  
      class Meta:
          ordering = ['nom']
-         verbose_name = "Type de poste"
-         verbose_name_plural = "Types de poste"
+         verbose_name = u"Type de poste"
+         verbose_name_plural = u"Types de poste"
          
      def __unicode__(self):
          return u'%s' % (self.nom)
@@@ -854,15 -790,15 +854,15 @@@ class TypeRemuneration(AUFMetadata)
      nom = models.CharField(max_length=255)
      type_paiement = models.CharField(max_length=30, 
                              choices=TYPE_PAIEMENT_CHOICES,
-                             verbose_name="Type de paiement")
+                             verbose_name = u"Type de paiement")
      nature_remuneration = models.CharField(max_length=30, 
                              choices=NATURE_REMUNERATION_CHOICES,
-                             verbose_name="Nature de la rémunération")
+                             verbose_name = u"Nature de la rémunération")
                              
      class Meta:
          ordering = ['nom']
-         verbose_name = "Type de rémunération"
-         verbose_name_plural = "Types de rémunération"
+         verbose_name = u"Type de rémunération"
+         verbose_name_plural = u"Types de rémunération"
  
      def __unicode__(self):
          return u'%s' % (self.nom)
@@@ -875,8 -811,8 +875,8 @@@ class TypeRevalorisation(AUFMetadata)
      
      class Meta:
          ordering = ['nom']
-         verbose_name = "Type de revalorisation"
-         verbose_name_plural = "Types de revalorisation"
+         verbose_name = u"Type de revalorisation"
+         verbose_name_plural = u"Types de revalorisation"
  
      def __unicode__(self):
          return u'%s' % (self.nom)
@@@ -888,8 -824,8 +888,8 @@@ class Service(AUFMetadata)
          
      class Meta:
          ordering = ['nom']
-         verbose_name = "Service"
-         verbose_name_plural = "Services"
+         verbose_name = u"Service"
+         verbose_name_plural = u"Services"
  
      def __unicode__(self):
          return u'%s' % (self.nom)
@@@ -915,8 -851,8 +915,8 @@@ class OrganismeBstg(AUFMetadata)
  
      class Meta:
          ordering = ['type', 'nom']
-         verbose_name = "Organisme BSTG"
-         verbose_name_plural = "Organismes BSTG"
+         verbose_name = u"Organisme BSTG"
+         verbose_name_plural = u"Organismes BSTG"
  
      def __unicode__(self):
          return u'%s (%s)' % (self.nom, self.get_type_display())
@@@ -930,8 -866,8 +930,8 @@@ class Statut(AUFMetadata)
  
      class Meta:
          ordering = ['code']
-         verbose_name = "Statut d'employé"
-         verbose_name_plural = "Statuts d'employé"
+         verbose_name = u"Statut d'employé"
+         verbose_name_plural = u"Statuts d'employé"
          
      def __unicode__(self):
          return u'%s : %s' % (self.code, self.nom)
@@@ -959,9 -895,9 +959,9 @@@ class Classement_(AUFMetadata)
      """
      # Identification
      type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
-     echelon = models.IntegerField(verbose_name="Échelon")
-     degre = models.IntegerField(verbose_name="Degré")
-     coefficient = models.FloatField(default=0, verbose_name="Coéfficient",
+     echelon = models.IntegerField(verbose_name = u"Échelon")
+     degre = models.IntegerField(verbose_name = u"Degré")
+     coefficient = models.FloatField(default=0, verbose_name = u"Coéfficient",
                                      null=True)
      # Méta
      # annee # au lieu de date_debut et date_fin
      class Meta:
          abstract = True
          ordering = ['type','echelon','degre','coefficient']
-         verbose_name = "Classement"
-         verbose_name_plural = "Classements"
+         verbose_name = u"Classement"
+         verbose_name_plural = u"Classements"
  
      def __unicode__(self):
          return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
@@@ -988,14 -924,14 +988,14 @@@ class TauxChange_(AUFMetadata)
      # Identification
      devise = models.ForeignKey('Devise', db_column='devise',
                              related_name='+')
-     annee = models.IntegerField(verbose_name="Année")
-     taux = models.FloatField(verbose_name="Taux vers l'euro")
+     annee = models.IntegerField(verbose_name = u"Année")
+     taux = models.FloatField(verbose_name = u"Taux vers l'euro")
  
      class Meta:
          abstract = True
          ordering = ['-annee', 'devise__code']
-         verbose_name = "Taux de change"
-         verbose_name_plural = "Taux de change"
+         verbose_name = u"Taux de change"
+         verbose_name_plural = u"Taux de change"
      
      def __unicode__(self):
          return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
@@@ -1031,8 -967,8 +1031,8 @@@ class ValeurPoint_(AUFMetadata)
      class Meta:
          ordering = ['-annee', 'implantation__nom']
          abstract = True
-         verbose_name = "Valeur du point"
-         verbose_name_plural = "Valeurs du point"
+         verbose_name = u"Valeur du point"
+         verbose_name_plural = u"Valeurs du point"
  
      # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
      def get_tauxchange_courant(self):
@@@ -1063,8 -999,8 +1063,8 @@@ class Devise(AUFMetadata)
  
      class Meta:
          ordering = ['code']
-         verbose_name = "Devise"
-         verbose_name_plural = "Devises"
+         verbose_name = u"Devise"
+         verbose_name_plural = u"Devises"
          
      def __unicode__(self):
          return u'%s - %s' % (self.code, self.nom)
@@@ -1077,8 -1013,8 +1077,8 @@@ class TypeContrat(AUFMetadata)
  
      class Meta:
          ordering = ['nom']
-         verbose_name = "Type de contrat"
-         verbose_name_plural = "Types de contrat"
+         verbose_name = u"Type de contrat"
+         verbose_name_plural = u"Types de contrat"
  
      def __unicode__(self):
          return u'%s' % (self.nom)
@@@ -1102,9 -1038,5 +1102,9 @@@ class ResponsableImplantation(AUFMetada
          
      class Meta:
          ordering = ['implantation__nom']
 -        verbose_name = u"Responsable d'implantation"
 -        verbose_name_plural = u"Responsables d'implantation"
 +        verbose_name = "Responsable d'implantation"
 +        verbose_name_plural = "Responsables d'implantation"
 +
 +def dossier_piece_dispatch(instance, filename):
 +    path = "dossier/%s/%s" % (instance.dossier_id, filename)
 +    return path
diff --combined project/settings.py
@@@ -16,7 -16,7 +16,7 @@@ ADMINS = 
  MANAGERS = ADMINS
  
  TIME_ZONE = 'Canada/Montreal'
 -DATE_FORMAT = 'd F Y'
 +DATE_FORMAT = 'j F Y'
  LANGUAGE_CODE = 'fr-ca'
  
  # Absolute path to the directory that holds media.
@@@ -29,7 -29,6 +29,7 @@@ PRIVE_MEDIA_ROOT = os.path.join(os.path
  # Examples: "http://media.lawrence.com", "http://example.com/media/"
  MEDIA_URL = '/media/'
  PRIVE_MEDIA_URL = '/dae/prive/'
 +OE_PRIVE_MEDIA_URL = '/recrutement/prive/'
  
  
  # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
@@@ -62,20 -61,23 +62,21 @@@ INSTALLED_APPS = 
      'django.contrib.messages',
      'django.contrib.sessions',
      'django.contrib.admin',
+     'django_qbe',
      'auf.django.emploi',
      'auf.django.admingroup',
      'ajax_select',
      'south',
      'reversion',
 -    'project.rh', 
 +    'project.rh',
      'auf.django.workflow',
      'project.rh_v1',
      'project.dae',
 -    #'project.budget',
      'alphafilter',
      'project.recrutement',
      'form_utils',
      'tinymce',
      'captcha',
 -#    'private_files', 
  )
  
  TEMPLATE_CONTEXT_PROCESSORS = (
@@@ -141,6 -143,7 +142,6 @@@ TINYMCE_DEFAULT_CONFIG = 
  'content_css' : '/media/css/tinymce.css',
  }
  
 -EMAIL_HOST = 'localhost'
 -EMAIL_PORT = '1025'
 +# django-sendfile
  
 -#FILE_PROTECTION_METHOD = 'basic'
 +SENDFILE_BACKEND = 'sendfile.backends.simple'
diff --combined project/urls.py
@@@ -10,12 -10,15 +10,17 @@@ handler500 # Pyflake
  
  urlpatterns = patterns(
      '',
+     (r'^$', 'project.views.accueil'),
+     #url(r'^private_files/', include('private_files.urls')),
+     url(r'^captcha/', include('captcha.urls')),
+     url(r'^admin_tools/', include('admin_tools.urls')),
      # système
      url(r'^$', 'project.views.accueil', name='accueil'),
  
      (r'^admin/', include(admin.site.urls)),
 +    url(r'^api/(?P<method>[a-z_-]+)/(?P<offre_id>\d+)/$', 'recrutement.api.api', 
 +            name='recrutement_api'),
      url(r'^api/(?P<method>[a-z_-]+)/$', 'recrutement.api.api', 
              name='recrutement_api'),
      (r'^connexion/$', 'django.contrib.auth.views.login'),
@@@ -34,6 -37,7 +39,7 @@@
      (r'^dae/', include('project.dae.urls')),
      (r'^recrutement/', include('recrutement.urls')),
      (r'^', include('project.rh.urls')),
+     url(r'^qbe/', include('django_qbe.urls')),
  )
  
  if settings.DEBUG:
@@@ -4,8 -4,9 +4,8 @@@ import datetim
  from django.core.files.storage import FileSystemStorage
  from tinymce import models as tinymce_models
  from django.db import models
 -import settings
 -
  import datamaster_modeles.models as ref
 +import settings
  
  ### CONSTANTES ###
  # HELP_TEXT
@@@ -16,13 -17,11 +16,13 @@@ HELP_TEXT_TAGS_ACCEPTES = "Pour le text
                              {{ offre_emploi }}. Ces champs seront \
                              automatiquement remplacés par les informations de \
                              chaque candidat."
 -
 +HELP_TEXT_EN_AFFICHAGE = "En cochant cette case, l'offre d'emploi sera affichée\
 +                         sur le site institutionnel dès qu'elle aura le statut \
 +                        'En diffusion' et ce, jusqu'à la date limite."
  
  STATUT_OFFRE_EMPLOI_CHOICES = (
      ('NOUV', 'Nouveau'),
 -    ('AFFI', 'Offre d\'emploi en affichage'),
 +    ('AFFI', 'En diffusion'),
      ('EVAL', 'En évaluation des candidatures'),
      ('ENTR', 'En entrevue'),
      ('TERM', 'Terminé'),
@@@ -70,29 -69,28 +70,29 @@@ class Metadata(models.Model)
  
  class OffreEmploi(Metadata):
      est_affiche = models.BooleanField(default=False, 
-                         verbose_name="À afficher sur le site institutionnel",
 -                                    verbose_name=u"En affichage sur le site")
++                        verbose_name=u"À afficher sur le site institutionnel",
 +                        help_text=HELP_TEXT_EN_AFFICHAGE)
      statut = models.CharField(max_length=4, choices=STATUT_OFFRE_EMPLOI_CHOICES,
                                  default='NOUV')
-     date_limite = models.DateField(verbose_name="Date limite",
++    date_limite = models.DateField(verbose_name=u"Date limite",
 +                        help_text=HELP_TEXT_FORMAT_DATE,)  
      nom = models.CharField(max_length=255)
-     resume = models.TextField(verbose_name="Résumé", blank=True)
 -    resume = models.TextField(verbose_name=u"Résumé")
++    resume = models.TextField(verbose_name=u"Résumé", blank=True)
      description = models.TextField()
      poste = models.CharField(max_length=255)
      poste_nom = models.CharField(max_length=255)
 -    date_limite = models.DateField(verbose_name=u"Date limite",
 -                        help_text=HELP_TEXT_FORMAT_DATE,)  
      region = models.ForeignKey(ref.Region, db_column='region', 
-                 verbose_name="Région")
+                 verbose_name=u"Région")
      bureau = models.ForeignKey(ref.Bureau, db_column='bureau', )
      duree_affectation = models.CharField(max_length=255, 
-                         verbose_name="Durée de l'affectation")
+                         verbose_name=u"Durée de l'affectation")
      renumeration = models.CharField(max_length=255,
-                     verbose_name='Rénumération')
-     debut_affectation = models.DateField(verbose_name="Début de l'affectation",
+                     verbose_name=u'Rénumération')
+     debut_affectation = models.DateField(verbose_name=u"Début de l'affectation",
                          help_text=HELP_TEXT_FORMAT_DATE,)
      lieu_affectation = models.ForeignKey(ref.Implantation,  
                          db_column='implantation', 
-                         verbose_name="Lieu d'affectation")
+                         verbose_name=u"Lieu d'affectation")
  
      class Meta:
          db_table = 'emploi_offreemploi'
@@@ -106,30 -104,30 +106,30 @@@ class Candidat(Metadata)
                  default='NOUV')
      offre_emploi = models.ForeignKey('OffreEmploi', db_column='offre_emploi',
                      related_name='+')
-     prenom = models.CharField(max_length=255, verbose_name='Prénom', )
+     prenom = models.CharField(max_length=255, verbose_name=u'Prénom', )
      nom = models.CharField(max_length=255)
      genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
      nationalite = models.ForeignKey(ref.Pays, 
                      db_column='nationalite', related_name='+',
-                     verbose_name='Nationalité')
+                     verbose_name=u'Nationalité')
      situation_famille = models.CharField(max_length=1, 
                          choices=SITUATION_CHOICES, 
-                         verbose_name='Situation familiale', )
-     nombre_dependant = models.IntegerField(verbose_name='Nombre de dépendant',
+                         verbose_name=u'Situation familiale', )
+     nombre_dependant = models.IntegerField(verbose_name=u'Nombre de dépendant',
                          help_text=HELP_TEXT_NB_DEPENDANT, )
      niveau_diplome = models.CharField(max_length=255,
-                         verbose_name='Niveau du diplôme')
+                         verbose_name=u'Niveau du diplôme')
      employeur_actuel = models.CharField(max_length=255, )
      poste_actuel = models.CharField(max_length=255, )
      domaine_professionnel = models.CharField(max_length=255, )
-     telephone = models.CharField(max_length=255, verbose_name='Téléphone', )
-     email = models.EmailField(max_length=255, verbose_name = 'Courriel', )
+     telephone = models.CharField(max_length=255, verbose_name=u'Téléphone', )
+     email = models.EmailField(max_length=255, verbose_name = u'Courriel', )
  
      # Adresse
      adresse = models.CharField(max_length=255)
      ville = models.CharField(max_length=255)
      etat_province = models.CharField(max_length=255, 
-                     verbose_name="État/Province", blank=True)
 -                    verbose_name=u"État/Province")
++                    verbose_name=u"État/Province", blank=True)
      code_postal = models.CharField(max_length=255, blank=True)
      pays = models.ForeignKey(ref.Pays, db_column='pays',
              related_name='+')
  
  
  # Upload de fichiers
 -storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT, 
 -                            base_url=settings.PRIVE_MEDIA_URL)
 +storage_prive = FileSystemStorage(settings.OE_PRIVE_MEDIA_ROOT, 
 +                            base_url=settings.OE_PRIVE_MEDIA_URL)
  
  def candidat_piece_dispatch(instance, filename):
 -    path = u'%s/%s/%s_%s_%s/%s/%s' % ('emplois', instance.candidat.offre_emploi.id,
 -            instance.candidat.nom, instance.candidat.prenom, instance.candidat.id, 
 -            instance.nom, filename)
 +    path = u'%s/%s/%s' % ('emplois', instance.candidat.id, filename)
      return path
  
  class CandidatPiece(models.Model):
      candidat = models.ForeignKey(Candidat, db_column='candidat',
                  related_name='candidat_piece') 
      nom = models.CharField(max_length=3, choices=TYPE_PIECE_CHOICES)
-     path = models.FileField(verbose_name="Fichier", 
+     path = models.FileField(verbose_name=u"Fichier", 
                          upload_to=candidat_piece_dispatch, storage=storage_prive)
  
      class Meta:
          db_table = 'emploi_pieces'
-         verbose_name = "pièce jointe"
+         verbose_name = u"pièce jointe"
          verbose_name_plural = "pièces jointes"
  
      def __unicode__(self):