Augmenté la taille des FileField
[auf_rh_dae.git] / project / rh / models.py
index 09e6abe..3c7a538 100644 (file)
@@ -4,90 +4,151 @@ import datetime
 from datetime import date
 from decimal import Decimal
 
 from datetime import date
 from decimal import Decimal
 
+import reversion
+from auf.django.emploi.models import \
+        GENRE_CHOICES, SITUATION_CHOICES  # devrait plutot être dans references
+from auf.django.references import models as ref
+from django.contrib.auth.models import User
 from django.core.files.storage import FileSystemStorage
 from django.core.files.storage import FileSystemStorage
+from django.core.exceptions import MultipleObjectsReturned
 from django.db import models
 from django.db import models
+from django.db.models import Q
+from django.db.models.signals import post_save, pre_save
 from django.conf import settings
 
 from django.conf import settings
 
-from auf.django.emploi.models import GENRE_CHOICES, SITUATION_CHOICES # devrait plutot être dans references
-from auf.django.metadata.models import AUFMetadata
-from auf.django.metadata.managers import NoDeleteManager
-import auf.django.references.models as ref
-from validators import validate_date_passee
-from managers import PosteManager, DossierManager, DossierComparaisonManager, PosteComparaisonManager, DeviseManager
+from project.rh.change_list import \
+        RechercheTemporelle, KEY_STATUT, STATUT_ACTIF, STATUT_INACTIF, \
+        STATUT_FUTUR
+from project import groups
+from project.rh.managers import (
+    PosteManager,
+    DossierManager,
+    EmployeManager,
+    DossierComparaisonManager,
+    PosteComparaisonManager,
+    ContratManager,
+    RemunerationManager,
+    ArchivableManager,
+    )
 
 
 
 
-# Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
-# Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
-def app_context():
-    import inspect;
-    models_stack = [s[1].split('/')[-2] for s in inspect.stack() if s[1].endswith('models.py')]
-    return models_stack[-1]
+TWOPLACES = Decimal('0.01')
 
 
+from project.rh.validators import validate_date_passee
+
+# import pour relocaliser le modèle selon la convention (models.py pour
+# introspection)
+from project.rh.historique import ModificationTraite
 
 # Constantes
 HELP_TEXT_DATE = "format: jj-mm-aaaa"
 REGIME_TRAVAIL_DEFAULT = Decimal('100.00')
 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = Decimal('35.00')
 
 # Constantes
 HELP_TEXT_DATE = "format: jj-mm-aaaa"
 REGIME_TRAVAIL_DEFAULT = Decimal('100.00')
 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = Decimal('35.00')
-REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT = "Saisir le nombre d'heure de travail à temps complet (100%), sans tenir compte du régime de travail"
+REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT = \
+        "Saisir le nombre d'heure de travail à temps complet (100%), " \
+        "sans tenir compte du régime de travail"
 
 # Upload de fichiers
 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
                             base_url=settings.PRIVE_MEDIA_URL)
 
 
 # Upload de fichiers
 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
                             base_url=settings.PRIVE_MEDIA_URL)
 
+
+class RemunIntegrityException(Exception):
+    pass
+
 def poste_piece_dispatch(instance, filename):
 def poste_piece_dispatch(instance, filename):
-    path = "%s/poste/%s/%s" % (instance._meta.app_label, instance.poste_id, filename)
+    path = "%s/poste/%s/%s" % (
+        instance._meta.app_label, instance.poste_id, filename
+    )
     return path
 
     return path
 
+
 def dossier_piece_dispatch(instance, filename):
 def dossier_piece_dispatch(instance, filename):
-    path = "%s/dossier/%s/%s" % (instance._meta.app_label, instance.dossier_id, filename)
+    path = "%s/dossier/%s/%s" % (
+        instance._meta.app_label, instance.dossier_id, filename
+    )
     return path
 
     return path
 
+
 def employe_piece_dispatch(instance, filename):
 def employe_piece_dispatch(instance, filename):
-    path = "%s/employe/%s/%s" % (instance._meta.app_label, instance.employe_id, filename)
+    path = "%s/employe/%s/%s" % (
+        instance._meta.app_label, instance.employe_id, filename
+    )
     return path
 
     return path
 
+
 def contrat_dispatch(instance, filename):
 def contrat_dispatch(instance, filename):
-    path = "%s/contrat/%s/%s" % (instance._meta.app_label, instance.dossier_id, filename)
+    path = "%s/contrat/%s/%s" % (
+        instance._meta.app_label, instance.dossier_id, filename
+    )
     return path
 
 
     return path
 
 
+class DateActiviteMixin(models.Model):
+    """
+    Mixin pour mettre à jour l'activité d'un modèle
+    """
+    class Meta:
+        abstract = True
+    date_creation = models.DateTimeField(auto_now_add=True,
+            null=True, blank=True,
+            verbose_name=u"Date de création",)
+    date_modification = models.DateTimeField(auto_now=True,
+            null=True, blank=True,
+            verbose_name=u"Date de modification",)
+
+
+class Archivable(models.Model):
+    archive = models.BooleanField(u'archivé', default=False)
+
+    objects = ArchivableManager()
+    avec_archives = models.Manager()
+
+    class Meta:
+        abstract = True
+
+
 class DevisableMixin(object):
 
     def get_annee_pour_taux_devise(self):
 class DevisableMixin(object):
 
     def get_annee_pour_taux_devise(self):
-        raise NotImplementedError
+        return datetime.datetime.now().year
 
 
-    def taux_devise(self):
-        if self.devise is None:
+    def taux_devise(self, devise=None):
+        if devise is None:
+            devise = self.devise
+
+        if devise is None:
             return None
             return None
-        if self.devise.code == "EUR":
+        if devise.code == "EUR":
             return 1
 
         annee = self.get_annee_pour_taux_devise()
             return 1
 
         annee = self.get_annee_pour_taux_devise()
-        taux = [tc.taux for tc in TauxChange.objects.filter(devise=self.devise, annee=annee)]
-        taux = set(taux)
-
-        if len(taux) == 0:
-            raise Exception(u"Pas de taux pour %s en %s" % (self.devise.code, annee))
-            
-        if len(taux) > 1:
-            raise Exception(u"Il existe plusieurs taux de %s en %s" %
-                    (self.devise.code, annee))
-        else:
-            return list(taux)[0]
+        taux = TauxChange.objects.filter(devise=devise, annee__lte=annee) \
+                .order_by('-annee')
+        return taux[0].taux
 
 
-    def montant_euros(self):
+    def montant_euros_float(self):
         try:
             taux = self.taux_devise()
         except Exception, e:
             return e
         if not taux:
             return None
         try:
             taux = self.taux_devise()
         except Exception, e:
             return e
         if not taux:
             return None
-        return int(round(float(self.montant) * float(taux), 2))
+        return float(self.montant) * float(taux)
+
+    def montant_euros(self):
+        return int(round(self.montant_euros_float(), 2))
 
 
 
 
-class Commentaire(AUFMetadata):
+class Commentaire(models.Model):
     texte = models.TextField()
     texte = models.TextField()
-    owner = models.ForeignKey('auth.User', db_column='owner', related_name='+', verbose_name=u"Commentaire de")
+    owner = models.ForeignKey(
+        'auth.User', db_column='owner', related_name='+',
+        verbose_name=u"Commentaire de"
+    )
+    date_creation = models.DateTimeField(
+        u'date', auto_now_add=True, blank=True, null=True
+    )
 
     class Meta:
         abstract = True
 
     class Meta:
         abstract = True
@@ -104,8 +165,10 @@ POSTE_APPEL_CHOICES = (
     ('externe', 'Externe'),
 )
 
     ('externe', 'Externe'),
 )
 
-class Poste_(AUFMetadata):
-    """Un Poste est un emploi (job) à combler dans une implantation.
+
+class Poste_( DateActiviteMixin, models.Model,):
+    """
+    Un Poste est un emploi (job) à combler dans une implantation.
     Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
     Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
     """
     Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
     Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
     """
@@ -113,114 +176,152 @@ class Poste_(AUFMetadata):
     objects = PosteManager()
 
     # Identification
     objects = PosteManager()
 
     # Identification
-    nom = models.CharField(max_length=255,
-                            verbose_name = u"Titre du poste", )
-    nom_feminin = models.CharField(max_length=255,
-                            verbose_name = u"Titre du poste (au féminin)",
-                            null=True)
-    implantation = models.ForeignKey(ref.Implantation, help_text=u"Taper le nom de l'implantation ou sa région", 
-                            db_column='implantation', related_name='+')
-    type_poste = models.ForeignKey('TypePoste', db_column='type_poste', help_text=u"Taper le nom du type de poste",
-                            related_name='+',
-                            null=True,
-                            verbose_name=u"type de poste")
-    service = models.ForeignKey('Service', db_column='service',
-                            related_name='+',
-                            verbose_name = u"direction/service/pôle support",
-                            null=True,)
-    responsable = models.ForeignKey('Poste', db_column='responsable', 
-                            related_name='+',
-                            null=True,
-                            help_text=u"Taper le nom du poste ou du type de poste", 
-                            verbose_name = u"Poste du responsable", )
-                                
+    nom = models.CharField(u"Titre du poste", max_length=255)
+    nom_feminin = models.CharField(
+         u"Titre du poste (au féminin)", max_length=255, null=True
+    )
+    implantation = models.ForeignKey(
+        ref.Implantation,
+        help_text=u"Taper le nom de l'implantation ou sa région",
+        db_column='implantation', related_name='+'
+    )
+    type_poste = models.ForeignKey(
+        'TypePoste', db_column='type_poste',
+        help_text=u"Taper le nom du type de poste", related_name='+',
+        null=True, verbose_name=u"type de poste"
+    )
+    service = models.ForeignKey(
+        'Service', db_column='service', related_name='%(app_label)s_postes',
+        verbose_name=u"direction/service/pôle support", null=True
+    )
+    responsable = models.ForeignKey(
+        'Poste', db_column='responsable',
+        related_name='+', null=True,
+        help_text=u"Taper le nom du poste ou du type de poste",
+        verbose_name=u"Poste du responsable"
+    )
+
     # Contrat
     # Contrat
-    regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
-                            default=REGIME_TRAVAIL_DEFAULT, null=True,
-                            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= u"Nb. heures par semaine",
-                            help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT)
+    regime_travail = models.DecimalField(
+        u"temps de travail", max_digits=12, decimal_places=2,
+        default=REGIME_TRAVAIL_DEFAULT, null=True,
+        help_text="% du temps complet"
+    )
+    regime_travail_nb_heure_semaine = models.DecimalField(
+        u"nb. heures par semaine", max_digits=12, decimal_places=2,
+        null=True, default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
+        help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
+    )
 
     # Recrutement
 
     # Recrutement
-    local = models.NullBooleanField(verbose_name = u"Local", default=True,
-                            null=True, blank=True)
-    expatrie = models.NullBooleanField(verbose_name = u"Expatrié", default=False,
-                            null=True, blank=True)
+    local = models.NullBooleanField(
+        u"local", default=True, null=True, blank=True
+    )
+    expatrie = models.NullBooleanField(
+        u"expatrié", default=False, null=True, blank=True
+    )
     mise_a_disposition = models.NullBooleanField(
     mise_a_disposition = models.NullBooleanField(
-                            verbose_name = u"Mise à disposition",
-                            null=True, default=False)
-    appel = models.CharField(max_length=10, null=True,
-                            verbose_name = u"Appel à candidature",
-                            choices=POSTE_APPEL_CHOICES,
-                            default='interne')
+        u"mise à disposition", null=True, default=False
+    )
+    appel = models.CharField(
+        u"Appel à candidature", max_length=10, null=True,
+        choices=POSTE_APPEL_CHOICES, default='interne'
+    )
 
     # Rémunération
 
     # Rémunération
-    classement_min = models.ForeignKey('Classement',
-                            db_column='classement_min', related_name='+',
-                            null=True, blank=True)
-    classement_max = models.ForeignKey('Classement',
-                            db_column='classement_max', related_name='+',
-                            null=True, blank=True)
-    valeur_point_min = models.ForeignKey('ValeurPoint', help_text=u"Taper le code ou le nom de l'implantation",
-                            db_column='valeur_point_min', related_name='+',
-                            null=True, blank=True)
-    valeur_point_max = models.ForeignKey('ValeurPoint', help_text=u"Taper le code ou le nom de l'implantation",
-                            db_column='valeur_point_max', related_name='+',
-                            null=True, blank=True)
-    devise_min = models.ForeignKey('Devise', db_column='devise_min', null=True,
-                            related_name='+',)
-    devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
-                            related_name='+',)
-    salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, default=0)
-    salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, default=0)
-    indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, default=0)
-    indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, default=0)
-    autre_min = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, default=0)
-    autre_max = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, default=0)
+    classement_min = models.ForeignKey(
+        'Classement', db_column='classement_min', related_name='+',
+        null=True, blank=True
+    )
+    classement_max = models.ForeignKey(
+        'Classement', db_column='classement_max', related_name='+',
+        null=True, blank=True
+    )
+    valeur_point_min = models.ForeignKey(
+        'ValeurPoint',
+        help_text=u"Taper le code ou le nom de l'implantation",
+        db_column='valeur_point_min', related_name='+', null=True,
+        blank=True
+    )
+    valeur_point_max = models.ForeignKey(
+        'ValeurPoint',
+        help_text=u"Taper le code ou le nom de l'implantation",
+        db_column='valeur_point_max', related_name='+', null=True,
+        blank=True
+    )
+    devise_min = models.ForeignKey(
+        'Devise', db_column='devise_min', null=True, related_name='+'
+    )
+    devise_max = models.ForeignKey(
+        'Devise', db_column='devise_max', null=True, related_name='+'
+    )
+    salaire_min = models.DecimalField(
+        max_digits=12, decimal_places=2, default=0,
+    )
+    salaire_max = models.DecimalField(
+        max_digits=12, decimal_places=2, default=0,
+    )
+    indemn_min = models.DecimalField(
+        max_digits=12, decimal_places=2, default=0,
+    )
+    indemn_max = models.DecimalField(
+        max_digits=12, decimal_places=2, default=0,
+    )
+    autre_min = models.DecimalField(
+        max_digits=12, decimal_places=2, default=0,
+    )
+    autre_max = models.DecimalField(
+        max_digits=12, decimal_places=2, default=0,
+    )
 
     # Comparatifs de rémunération
 
     # Comparatifs de rémunération
-    devise_comparaison = models.ForeignKey('Devise', null=True, blank=True,
-                            db_column='devise_comparaison',
-                            related_name='+', )
-    comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, blank=True)
-    comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, blank=True)
-    comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, blank=True)
-    comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, blank=True)
-    comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, blank=True)
-    comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, blank=True)
-    comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, blank=True)
-    comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, blank=True)
-    comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, blank=True)
-    comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, blank=True)
+    devise_comparaison = models.ForeignKey(
+        'Devise', null=True, blank=True, db_column='devise_comparaison',
+        related_name='+'
+    )
+    comp_locale_min = models.DecimalField(
+        max_digits=12, decimal_places=2, null=True, blank=True
+    )
+    comp_locale_max = models.DecimalField(
+        max_digits=12, decimal_places=2, null=True, blank=True
+    )
+    comp_universite_min = models.DecimalField(
+        max_digits=12, decimal_places=2, null=True, blank=True
+    )
+    comp_universite_max = models.DecimalField(
+        max_digits=12, decimal_places=2, null=True, blank=True
+    )
+    comp_fonctionpub_min = models.DecimalField(
+        max_digits=12, decimal_places=2, null=True, blank=True
+    )
+    comp_fonctionpub_max = models.DecimalField(
+        max_digits=12, decimal_places=2, null=True, blank=True
+    )
+    comp_ong_min = models.DecimalField(
+        max_digits=12, decimal_places=2, null=True, blank=True
+    )
+    comp_ong_max = models.DecimalField(
+        max_digits=12, decimal_places=2, null=True, blank=True
+    )
+    comp_autre_min = models.DecimalField(
+        max_digits=12, decimal_places=2, null=True, blank=True
+    )
+    comp_autre_max = models.DecimalField(
+        max_digits=12, decimal_places=2, null=True, blank=True
+    )
 
     # Justification
     justification = models.TextField(null=True, blank=True)
 
     # Autres Metadata
 
     # Justification
     justification = models.TextField(null=True, blank=True)
 
     # Autres Metadata
-    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=u"Date de fin", help_text=HELP_TEXT_DATE,
-                            null=True, blank=True)
+    date_debut = models.DateField(
+        u"date de début", help_text=HELP_TEXT_DATE, null=True, blank=True,
+        db_index=True
+    )
+    date_fin = models.DateField(
+        u"date de fin", help_text=HELP_TEXT_DATE, null=True, blank=True,
+        db_index=True
+    )
 
     class Meta:
         abstract = True
 
     class Meta:
         abstract = True
@@ -230,21 +331,31 @@ class Poste_(AUFMetadata):
         ordering = ["nom"]
 
     def __unicode__(self):
         ordering = ["nom"]
 
     def __unicode__(self):
-        representation = u'%s - %s [%s]' % (self.implantation, self.nom,
-                            self.id)
+        representation = u'%s - %s [%s]' % (
+            self.implantation, self.nom, self.id
+        )
         return representation
 
         return representation
 
+    prefix_implantation = "implantation__zone_administrative"
+
+    def get_zones_administratives(self):
+        return [self.implantation.zone_administrative]
 
 
-    prefix_implantation = "implantation__region"
-    def get_regions(self):
-        return [self.implantation.region]
+    def get_devise(self):
+        vp = ValeurPoint.objects.filter(
+            implantation=self.implantation, devise__archive=False
+        ).order_by('annee')
+        if len(vp) > 0:
+            return vp[0].devise
+        else:
+            return Devise.objects.get(code='EUR')
 
 
 class Poste(Poste_):
     __doc__ = Poste_.__doc__
 
     # meta dématérialisation :  pour permettre le filtrage
 
 
 class Poste(Poste_):
     __doc__ = Poste_.__doc__
 
     # meta dématérialisation :  pour permettre le filtrage
-    vacant = models.NullBooleanField(verbose_name = u"vacant", null=True, blank=True)
+    vacant = models.NullBooleanField(u"vacant", null=True, blank=True)
 
     def is_vacant(self):
         vacant = True
 
     def is_vacant(self):
         vacant = True
@@ -253,12 +364,21 @@ class Poste(Poste_):
         return vacant
 
     def occupe_par(self):
         return vacant
 
     def occupe_par(self):
-        """Retourne la liste d'employé occupant ce poste.
+        """
+        Retourne la liste d'employé occupant ce poste.
         Généralement, retourne une liste d'un élément.
         Si poste inoccupé, retourne liste vide.
         UTILISE pour mettre a jour le flag vacant
         """
         Généralement, retourne une liste d'un élément.
         Si poste inoccupé, retourne liste vide.
         UTILISE pour mettre a jour le flag vacant
         """
-        return [d.employe for d in self.rh_dossiers.filter(supprime=False).exclude(date_fin__lt=date.today())]
+        return [
+            d.employe
+            for d in self.rh_dossiers.exclude(date_fin__lt=date.today())
+        ]
+
+reversion.register(Poste, format='xml', follow=[
+    'rh_financements', 'rh_pieces', 'rh_comparaisons_internes',
+    'commentaires'
+])
 
 
 POSTE_FINANCEMENT_CHOICES = (
 
 
 POSTE_FINANCEMENT_CHOICES = (
@@ -269,15 +389,18 @@ POSTE_FINANCEMENT_CHOICES = (
 
 
 class PosteFinancement_(models.Model):
 
 
 class PosteFinancement_(models.Model):
-    """Pour un Poste, structure d'informations décrivant comment on prévoit
+    """
+    Pour un Poste, structure d'informations décrivant comment on prévoit
     financer ce Poste.
     """
     financer ce Poste.
     """
-    poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='%(app_label)s_financements')
     type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
     type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
-    pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
-            help_text="ex.: 33.33 % (décimale avec point)")
+    pourcentage = models.DecimalField(
+        max_digits=12, decimal_places=2,
+        help_text="ex.: 33.33 % (décimale avec point)"
+    )
     commentaire = models.TextField(
     commentaire = models.TextField(
-            help_text="Spécifiez la source de financement.")
+        help_text="Spécifiez la source de financement."
+    )
 
     class Meta:
         abstract = True
 
     class Meta:
         abstract = True
@@ -291,18 +414,23 @@ class PosteFinancement_(models.Model):
 
 
 class PosteFinancement(PosteFinancement_):
 
 
 class PosteFinancement(PosteFinancement_):
-    pass
+    poste = models.ForeignKey(
+        Poste, db_column='poste', related_name='rh_financements'
+    )
+
+reversion.register(PosteFinancement, format='xml')
 
 
 class PostePiece_(models.Model):
 
 
 class PostePiece_(models.Model):
-    """Documents relatifs au Poste.
+    """
+    Documents relatifs au Poste.
     Ex.: Description de poste
     """
     Ex.: Description de poste
     """
-    poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='%(app_label)s_pieces')
-    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)
+    nom = models.CharField(u"Nom", max_length=255)
+    fichier = models.FileField(
+        u"Fichier", upload_to=poste_piece_dispatch, storage=storage_prive,
+        max_length=255
+    )
 
     class Meta:
         abstract = True
 
     class Meta:
         abstract = True
@@ -311,104 +439,135 @@ class PostePiece_(models.Model):
     def __unicode__(self):
         return u'%s' % (self.nom)
 
     def __unicode__(self):
         return u'%s' % (self.nom)
 
+
 class PostePiece(PostePiece_):
 class PostePiece(PostePiece_):
-    pass
+    poste = models.ForeignKey(
+        Poste, db_column='poste', related_name='rh_pieces'
+    )
+
+reversion.register(PostePiece, format='xml')
+
 
 
-class PosteComparaison_(AUFMetadata, DevisableMixin):
+class PosteComparaison_(models.Model, DevisableMixin):
     """
     """
-    De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
+    De la même manière qu'un dossier, un poste peut-être comparé à un autre
+    poste.
     """
     """
-    poste = models.ForeignKey('%s.Poste' % app_context(), related_name='%(app_label)s_comparaisons_internes')
     objects = PosteComparaisonManager()
 
     objects = PosteComparaisonManager()
 
-    implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
-    nom = models.CharField(verbose_name = u"Poste", max_length=255, null=True, blank=True)
+    implantation = models.ForeignKey(
+        ref.Implantation, null=True, blank=True, related_name="+"
+    )
+    nom = models.CharField(u"Poste", max_length=255, null=True, blank=True)
     montant = models.IntegerField(null=True)
     montant = models.IntegerField(null=True)
-    devise = models.ForeignKey("Devise", related_name='+', null=True, blank=True)
+    devise = models.ForeignKey(
+        "Devise", related_name='+', null=True, blank=True
+    )
 
     class Meta:
         abstract = True
 
 
     class Meta:
         abstract = True
 
-
-    def get_annee_pour_taux_devise(self):
-        return self.poste.date_debut.year
-
     def __unicode__(self):
         return self.nom
 
     def __unicode__(self):
         return self.nom
 
+
 class PosteComparaison(PosteComparaison_):
 class PosteComparaison(PosteComparaison_):
-    pass
+    poste = models.ForeignKey(
+        Poste, related_name='rh_comparaisons_internes'
+    )
 
 
-class PosteCommentaire_(Commentaire):
-    poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='+')
+reversion.register(PosteComparaison, format='xml')
 
 
-    class Meta:
-        abstract = True
 
 
-class PosteCommentaire(PosteCommentaire_):
-    pass
+class PosteCommentaire(Commentaire):
+    poste = models.ForeignKey(
+        Poste, db_column='poste', related_name='commentaires'
+    )
+
+reversion.register(PosteCommentaire, format='xml')
 
 ### EMPLOYÉ/PERSONNE
 
 
 ### EMPLOYÉ/PERSONNE
 
-class Employe(AUFMetadata):
-    """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
+class Employe(models.Model):
+    """
+    Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
     Dossiers qu'il occupe ou a occupé de Postes.
 
     Cette classe aurait pu avantageusement s'appeler Personne car la notion
     d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
     """
     Dossiers qu'il occupe ou a occupé de Postes.
 
     Cette classe aurait pu avantageusement s'appeler Personne car la notion
     d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
     """
+
+    objects = EmployeManager()
+
     # Identification
     nom = models.CharField(max_length=255)
     # Identification
     nom = models.CharField(max_length=255)
-    prenom = models.CharField(max_length=255, verbose_name = u"Prénom")
-    nom_affichage = models.CharField(max_length=255,
-                            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 = u"Nationalité",
-                            blank=True, null=True)
-    date_naissance = models.DateField(verbose_name = u"Date de naissance",
-                            help_text=HELP_TEXT_DATE,
-                            validators=[validate_date_passee],
-                            null=True, blank=True)
+    prenom = models.CharField(u"prénom", max_length=255)
+    nom_affichage = models.CharField(
+        u"nom d'affichage", max_length=255, null=True, blank=True
+    )
+    nationalite = models.ForeignKey(
+        ref.Pays, to_field='code', db_column='nationalite',
+        related_name='employes_nationalite', verbose_name=u"nationalité",
+        blank=True, null=True
+    )
+    date_naissance = models.DateField(
+        u"date de naissance", help_text=HELP_TEXT_DATE,
+        validators=[validate_date_passee], null=True, blank=True
+    )
     genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
 
     # Infos personnelles
     genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
 
     # Infos personnelles
-    situation_famille = models.CharField(max_length=1,
-                            choices=SITUATION_CHOICES,
-                            verbose_name = u"Situation familiale",
-                            null=True, blank=True)
-    date_entree = models.DateField(verbose_name = u"Date d'entrée à l'AUF",
-                            help_text=HELP_TEXT_DATE,
-                            null=True, blank=True)
+    situation_famille = models.CharField(
+        u"situation familiale", max_length=1, choices=SITUATION_CHOICES,
+        null=True, blank=True
+    )
+    date_entree = models.DateField(
+        u"date d'entrée à l'AUF", help_text=HELP_TEXT_DATE, null=True,
+        blank=True
+    )
 
     # Coordonnées
 
     # Coordonnées
-    tel_domicile = models.CharField(max_length=255,
-                            verbose_name = u"Tél. domicile",
-                            null=True, blank=True)
-    tel_cellulaire = models.CharField(max_length=255,
-                            verbose_name = u"Tél. cellulaire",
-                            null=True, blank=True)
+    tel_domicile = models.CharField(
+        u"tél. domicile", max_length=255, null=True, blank=True
+    )
+    tel_cellulaire = models.CharField(
+        u"tél. cellulaire", max_length=255, null=True, blank=True
+    )
     adresse = models.CharField(max_length=255, null=True, blank=True)
     ville = models.CharField(max_length=255, null=True, blank=True)
     province = models.CharField(max_length=255, null=True, blank=True)
     code_postal = models.CharField(max_length=255, null=True, blank=True)
     adresse = models.CharField(max_length=255, null=True, blank=True)
     ville = models.CharField(max_length=255, null=True, blank=True)
     province = models.CharField(max_length=255, null=True, blank=True)
     code_postal = models.CharField(max_length=255, null=True, blank=True)
-    pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
-                            related_name='employes',
-                            null=True, blank=True)
+    pays = models.ForeignKey(
+        ref.Pays, to_field='code', db_column='pays',
+        related_name='employes', null=True, blank=True
+    )
+    courriel_perso = models.EmailField(
+        u'adresse courriel personnelle', blank=True
+    )
 
     # meta dématérialisation :  pour permettre le filtrage
 
     # meta dématérialisation :  pour permettre le filtrage
-    nb_postes = models.IntegerField(verbose_name = u"nombre de postes", null=True, blank=True)
+    nb_postes = models.IntegerField(u"nombre de postes", null=True, blank=True)
 
     class Meta:
 
     class Meta:
-        ordering = ['nom','prenom']
+        ordering = ['nom', 'prenom']
         verbose_name = u"Employé"
         verbose_name_plural = u"Employés"
 
     def __unicode__(self):
         return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
 
         verbose_name = u"Employé"
         verbose_name_plural = u"Employés"
 
     def __unicode__(self):
         return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
 
+    def get_latest_dossier_ordered_by_date_fin_and_principal(self):
+        res = self.rh_dossiers.order_by(
+            '-principal', 'date_fin')
+
+        # Retourne en le premier du queryset si la date de fin est None
+        # Sinon, retourne le plus récent selon la date de fin.
+        first = res[0]
+        if first.date_fin == None:
+            return first
+        else:
+            return res.order_by('-principal', '-date_fin')[0]
+
     def civilite(self):
         civilite = u''
         if self.genre.upper() == u'M':
     def civilite(self):
         civilite = u''
         if self.genre.upper() == u'M':
@@ -418,32 +577,78 @@ class Employe(AUFMetadata):
         return civilite
 
     def url_photo(self):
         return civilite
 
     def url_photo(self):
-        """Retourne l'URL du service retournant la photo de l'Employe.
+        """
+        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
         Équivalent reverse url 'rh_photo' avec id en param.
         """
         from django.core.urlresolvers import reverse
-        return reverse('rh_photo', kwargs={'id':self.id})
+        return reverse('rh_photo', kwargs={'id': self.id})
 
     def dossiers_passes(self):
 
     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
+        params = {KEY_STATUT: STATUT_INACTIF, }
+        search = RechercheTemporelle(params, Dossier)
+        search.purge_params(params)
+        q = search.get_q_temporel(self.rh_dossiers)
+        return self.rh_dossiers.filter(q)
 
     def dossiers_futurs(self):
 
     def dossiers_futurs(self):
-        today = date.today()
-        return self.dossiers.filter(date_debut__gt=today).order_by('-date_fin')
+        params = {KEY_STATUT: STATUT_FUTUR, }
+        search = RechercheTemporelle(params, Dossier)
+        search.purge_params(params)
+        q = search.get_q_temporel(self.rh_dossiers)
+        return self.rh_dossiers.filter(q)
 
     def dossiers_encours(self):
 
     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')
+        params = {KEY_STATUT: STATUT_ACTIF, }
+        search = RechercheTemporelle(params, Dossier)
+        search.purge_params(params)
+        q = search.get_q_temporel(self.rh_dossiers)
+        return self.rh_dossiers.filter(q)
+
+    def dossier_principal_pour_annee(self):
+        return self.dossier_principal(pour_annee=True)
+
+    def dossier_principal(self, pour_annee=False):
+        """
+        Retourne le dossier principal (ou le plus ancien si il y en a
+        plusieurs)
 
 
-        # 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
+        Si pour_annee == True, retourne le ou les dossiers principaux
+        pour l'annee en cours, sinon, le ou les dossiers principaux
+        pour la journee en cours.
+
+        TODO: (Refactoring possible): Utiliser meme logique dans
+        dae/templatetags/dae.py
+        """
+        
+        today = date.today()
+        if pour_annee:
+            year = today.year
+            year_start = date(year, 1, 1)
+            year_end = date(year, 12, 31)
+            
+            try:
+                dossier = self.rh_dossiers.filter(
+                    (Q(date_debut__lte=year_end, date_fin__isnull=True) |
+                     Q(date_debut__isnull=True, date_fin__gte=year_start) |
+                     Q(date_debut__lte=year_end, date_fin__gte=year_start) |
+                     Q(date_debut__isnull=True, date_fin__isnull=True)) &
+                    Q(principal=True)).order_by('date_debut')[0]
+            except IndexError, Dossier.DoesNotExist:
+                dossier = None
+            return dossier
+        else:
+            try:
+                dossier = self.rh_dossiers.filter(
+                    (Q(date_debut__lte=today, date_fin__isnull=True) |
+                     Q(date_debut__isnull=True, date_fin__gte=today) |
+                     Q(date_debut__lte=today, date_fin__gte=today) |
+                     Q(date_debut__isnull=True, date_fin__isnull=True)) &
+                    Q(principal=True)).order_by('date_debut')[0]
+            except IndexError, Dossier.DoesNotExist:
+                dossier = None
+            return dossier
+                
 
     def postes_encours(self):
         postes_encours = set()
 
     def postes_encours(self):
         postes_encours = set()
@@ -457,6 +662,7 @@ class Employe(AUFMetadata):
         Idée derrière :
         si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
         """
         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
         poste = Poste.objects.none()
         try:
             poste = self.dossiers_encours().order_by('date_debut')[0].poste
@@ -464,23 +670,34 @@ class Employe(AUFMetadata):
             pass
         return poste
 
             pass
         return poste
 
-    prefix_implantation = "rh_dossiers__poste__implantation__region"
-    def get_regions(self):
-        regions = []
-        for d in self.dossiers.all():
-            regions.append(d.poste.implantation.region)
-        return regions
+    prefix_implantation = \
+            "rh_dossiers__poste__implantation__zone_administrative"
+
+    def get_zones_administratives(self):
+        return [
+            d.poste.implantation.zone_administrative
+            for d in self.dossiers.all()
+        ]
+
+reversion.register(Employe, format='xml', follow=[
+    'pieces', 'commentaires', 'ayantdroits'
+])
 
 
 class EmployePiece(models.Model):
 
 
 class EmployePiece(models.Model):
-    """Documents relatifs à un employé.
+    """
+    Documents relatifs à un employé.
     Ex.: CV...
     """
     Ex.: CV...
     """
-    employe = models.ForeignKey('Employe', db_column='employe')
-    nom = models.CharField(verbose_name="Nom", max_length=255)
-    fichier = models.FileField(verbose_name="Fichier",
-                            upload_to=employe_piece_dispatch,
-                            storage=storage_prive)
+    employe = models.ForeignKey(
+        'Employe', db_column='employe', related_name="pieces",
+        verbose_name=u"employé"
+    )
+    nom = models.CharField(max_length=255)
+    fichier = models.FileField(
+        u"fichier", upload_to=employe_piece_dispatch, storage=storage_prive,
+        max_length=255
+    )
 
     class Meta:
         ordering = ['nom']
 
     class Meta:
         ordering = ['nom']
@@ -490,14 +707,20 @@ class EmployePiece(models.Model):
     def __unicode__(self):
         return u'%s' % (self.nom)
 
     def __unicode__(self):
         return u'%s' % (self.nom)
 
+reversion.register(EmployePiece, format='xml')
+
+
 class EmployeCommentaire(Commentaire):
 class EmployeCommentaire(Commentaire):
-    employe = models.ForeignKey('Employe', db_column='employe',
-                            related_name='+')
+    employe = models.ForeignKey(
+        'Employe', db_column='employe', related_name='commentaires'
+    )
 
     class Meta:
         verbose_name = u"Employé commentaire"
         verbose_name_plural = u"Employé commentaires"
 
 
     class Meta:
         verbose_name = u"Employé commentaire"
         verbose_name_plural = u"Employé commentaires"
 
+reversion.register(EmployeCommentaire, format='xml')
+
 
 LIEN_PARENTE_CHOICES = (
     ('Conjoint', 'Conjoint'),
 
 LIEN_PARENTE_CHOICES = (
     ('Conjoint', 'Conjoint'),
@@ -506,35 +729,37 @@ LIEN_PARENTE_CHOICES = (
     ('Fils', 'Fils'),
 )
 
     ('Fils', 'Fils'),
 )
 
-class AyantDroit(AUFMetadata):
-    """Personne en relation avec un Employe.
+
+class AyantDroit(models.Model):
+    """
+    Personne en relation avec un Employe.
     """
     # Identification
     nom = models.CharField(max_length=255)
     """
     # Identification
     nom = models.CharField(max_length=255)
-    prenom = models.CharField(max_length=255,
-                            verbose_name = u"Prénom",)
-    nom_affichage = models.CharField(max_length=255,
-                            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 = u"Nationalité",
-                            null=True, blank=True)
-    date_naissance = models.DateField(verbose_name = u"Date de naissance",
-                            help_text=HELP_TEXT_DATE,
-                            validators=[validate_date_passee],
-                            null=True, blank=True)
+    prenom = models.CharField(u"prénom", max_length=255)
+    nom_affichage = models.CharField(
+        u"nom d'affichage", max_length=255, null=True, blank=True
+    )
+    nationalite = models.ForeignKey(
+        ref.Pays, to_field='code', db_column='nationalite',
+        related_name='ayantdroits_nationalite',
+        verbose_name=u"nationalité", null=True, blank=True
+    )
+    date_naissance = models.DateField(
+        u"Date de naissance", help_text=HELP_TEXT_DATE,
+        validators=[validate_date_passee], null=True, blank=True
+    )
     genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
 
     # Relation
     genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
 
     # Relation
-    employe = models.ForeignKey('Employe', db_column='employe',
-                            related_name='ayantdroits',
-                            verbose_name = u"Employé")
-    lien_parente = models.CharField(max_length=10,
-                            choices=LIEN_PARENTE_CHOICES,
-                            verbose_name = u"Lien de parenté",
-                            null=True, blank=True)
+    employe = models.ForeignKey(
+        'Employe', db_column='employe', related_name='ayantdroits',
+        verbose_name=u"Employé"
+    )
+    lien_parente = models.CharField(
+        u"lien de parenté", max_length=10, choices=LIEN_PARENTE_CHOICES,
+        null=True, blank=True
+    )
 
     class Meta:
         ordering = ['nom', ]
 
     class Meta:
         ordering = ['nom', ]
@@ -542,19 +767,26 @@ class AyantDroit(AUFMetadata):
         verbose_name_plural = u"Ayants droit"
 
     def __unicode__(self):
         verbose_name_plural = u"Ayants droit"
 
     def __unicode__(self):
-        return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
+        return u'%s %s' % (self.nom.upper(), self.prenom, )
+
+    prefix_implantation = \
+            "employe__dossiers__poste__implantation__zone_administrative"
 
 
-    prefix_implantation = "employe__dossiers__poste__implantation__region"
-    def get_regions(self):
-        regions = []
-        for d in self.employe.dossiers.all():
-            regions.append(d.poste.implantation.region)
-        return regions
+    def get_zones_administratives(self):
+        return [
+            d.poste.implantation.zone_administrative
+            for d in self.employe.dossiers.all()
+        ]
+
+reversion.register(AyantDroit, format='xml', follow=['commentaires'])
 
 
 class AyantDroitCommentaire(Commentaire):
 
 
 class AyantDroitCommentaire(Commentaire):
-    ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
-                            related_name='+')
+    ayant_droit = models.ForeignKey(
+        'AyantDroit', db_column='ayant_droit', related_name='commentaires'
+    )
+
+reversion.register(AyantDroitCommentaire, format='xml')
 
 
 ### DOSSIER
 
 
 ### DOSSIER
@@ -570,8 +802,10 @@ COMPTE_COMPTA_CHOICES = (
     ('aucun', 'Aucun'),
 )
 
     ('aucun', 'Aucun'),
 )
 
-class Dossier_(AUFMetadata):
-    """Le Dossier regroupe les informations relatives à l'occupation
+
+class Dossier_(DateActiviteMixin, models.Model, DevisableMixin,):
+    """
+    Le Dossier regroupe les informations relatives à l'occupation
     d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
     par un Employe.
 
     d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
     par un Employe.
 
@@ -584,48 +818,62 @@ class Dossier_(AUFMetadata):
 
     # TODO: OneToOne ??
     statut = models.ForeignKey('Statut', related_name='+', null=True)
 
     # TODO: OneToOne ??
     statut = models.ForeignKey('Statut', related_name='+', null=True)
-    organisme_bstg = models.ForeignKey('OrganismeBstg',
-                            db_column='organisme_bstg',
-                            related_name='+',
-                            verbose_name = u"Organisme",
-                            help_text="Si détaché (DET) ou \
-                                    mis à disposition (MAD), \
-                                    préciser l'organisme.",
-                            null=True, blank=True)
+    organisme_bstg = models.ForeignKey(
+        'OrganismeBstg', db_column='organisme_bstg', related_name='+',
+        verbose_name=u"organisme",
+        help_text=(
+            u"Si détaché (DET) ou mis à disposition (MAD), "
+            u"préciser l'organisme."
+        ), null=True, blank=True
+    )
 
     # Recrutement
     remplacement = models.BooleanField(default=False)
 
     # Recrutement
     remplacement = models.BooleanField(default=False)
-    remplacement_de = models.ForeignKey('self', related_name='+',
-                            help_text=u"Taper le nom de l'employé",
-                            null=True, blank=True)
-    statut_residence = models.CharField(max_length=10, default='local',
-                            verbose_name = u"Statut", null=True,
-                            choices=STATUT_RESIDENCE_CHOICES)
+    remplacement_de = models.ForeignKey(
+        'self', related_name='+', help_text=u"Taper le nom de l'employé",
+        null=True, blank=True
+    )
+    statut_residence = models.CharField(
+        u"statut", max_length=10, default='local', null=True,
+        choices=STATUT_RESIDENCE_CHOICES
+    )
 
     # Rémunération
 
     # Rémunération
-    classement = models.ForeignKey('Classement', db_column='classement',
-                            related_name='+',
-                            null=True, blank=True)
-    regime_travail = models.DecimalField(max_digits=12, null=True,
-                            decimal_places=2,
-                            default=REGIME_TRAVAIL_DEFAULT,
-                            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=u"Nb. heures par semaine",
-                            help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT)
+    classement = models.ForeignKey(
+        'Classement', db_column='classement', related_name='+', null=True,
+        blank=True
+    )
+    regime_travail = models.DecimalField(
+        u"régime de travail", max_digits=12, null=True, decimal_places=2,
+        default=REGIME_TRAVAIL_DEFAULT, help_text="% du temps complet"
+    )
+    regime_travail_nb_heure_semaine = models.DecimalField(
+        u"nb. heures par semaine", max_digits=12,
+        decimal_places=2, null=True,
+        default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
+        help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
+    )
 
     # Occupation du Poste par cet Employe (anciennement "mandat")
 
     # Occupation du Poste par cet Employe (anciennement "mandat")
-    date_debut = models.DateField(verbose_name = u"Date de début d'occupation \
-                            de poste",)
-    date_fin = models.DateField(verbose_name = u"Date de fin d'occupation \
-                            de poste",
-                            null=True, blank=True)
+    date_debut = models.DateField(
+        u"date de début d'occupation de poste", db_index=True
+    )
+    date_fin = models.DateField(
+        u"Date de fin d'occupation de poste", null=True, blank=True,
+        db_index=True
+    )
+
+    # Meta-data:
+    est_cadre = models.BooleanField(
+        u"Est un cadre?",
+        default=False,
+        )
 
     # Comptes
 
     # Comptes
-    # TODO?
+    compte_compta = models.CharField(max_length=10, default='aucun',
+                                    verbose_name=u'Compte comptabilité',
+                                    choices=COMPTE_COMPTA_CHOICES)
+    compte_courriel = models.BooleanField()
 
     class Meta:
         abstract = True
 
     class Meta:
         abstract = True
@@ -641,7 +889,7 @@ class Dossier_(AUFMetadata):
 
         montant = coeff * point.valeur
         devise = point.devise
 
         montant = coeff * point.valeur
         devise = point.devise
-        return {'montant':montant, 'devise':devise}
+        return {'montant': montant, 'devise': devise}
 
     def __unicode__(self):
         poste = self.poste.nom
 
     def __unicode__(self):
         poste = self.poste.nom
@@ -649,46 +897,369 @@ class Dossier_(AUFMetadata):
             poste = self.poste.nom_feminin
         return u'%s - %s' % (self.employe, poste)
 
             poste = self.poste.nom_feminin
         return u'%s - %s' % (self.employe, poste)
 
-    prefix_implantation = "poste__implantation__region"
-    def get_regions(self):
-        return [self.poste.implantation.region]
+    prefix_implantation = "poste__implantation__zone_administrative"
 
 
+    def get_zones_administratives(self):
+        return [self.poste.implantation.zone_administrative]
 
     def remunerations(self):
 
     def remunerations(self):
-        return self.rh_remunerations.all().order_by('date_debut')
+        key = "%s_remunerations" % self._meta.app_label
+        remunerations = getattr(self, key)
+        return remunerations.all().order_by('-date_debut')
 
     def remunerations_en_cours(self):
 
     def remunerations_en_cours(self):
-        return self.rh_remunerations.all().filter(date_fin__exact=None).order_by('date_debut')
+        q = Q(date_fin__exact=None) | Q(date_fin__gt=datetime.date.today())
+        return self.remunerations().all().filter(q).order_by('date_debut')
 
     def get_salaire(self):
         try:
 
     def get_salaire(self):
         try:
-            return [r for r in self.remunerations().order_by('-date_debut') if r.type_id == 1][0]
+            return [r for r in self.remunerations().order_by('-date_debut')
+                    if r.type_id == 1][0]
         except:
             return None
 
         except:
             return None
 
+    def get_salaire_euros(self):
+        tx = self.taux_devise()
+        return (float)(tx) * (float)(self.salaire)
+
+    def get_remunerations_brutes(self):
+        """
+        1   Salaire de base
+        3   Indemnité de base
+        4   Indemnité d'expatriation
+        5   Indemnité pour frais
+        6   Indemnité de logement
+        7   Indemnité de fonction
+        8   Indemnité de responsabilité
+        9   Indemnité de transport
+        10  Indemnité compensatrice
+        11  Indemnité de subsistance
+        12  Indemnité différentielle
+        13  Prime d'installation
+        14  Billet d'avion
+        15  Déménagement
+        16  Indemnité de départ
+        18  Prime de 13ième mois
+        19  Prime d'intérim
+        """
+        ids = [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
+        return [r for r in self.remunerations_en_cours().all()
+                if r.type_id in ids]
+
+    def get_charges_salariales(self):
+        """
+        20 Charges salariales ?
+        """
+        ids = [20]
+        return [r for r in self.remunerations_en_cours().all()
+                if r.type_id in ids]
+
+    def get_charges_patronales(self):
+        """
+        17  Charges patronales
+        """
+        ids = [17]
+        return [r for r in self.remunerations_en_cours().all()
+                if r.type_id in ids]
+
+    def get_remunerations_tierces(self):
+        """
+        2   Salaire MAD
+        """
+        return [r for r in self.remunerations_en_cours().all()
+                if r.type_id in (2,)]
+
+    # DEVISE LOCALE
+
+    def get_total_local_charges_salariales(self):
+        devise = self.poste.get_devise()
+        total = 0.0
+        for r in self.get_charges_salariales():
+            if r.devise != devise:
+                return None
+            total += float(r.montant)
+        return total
+
+    def get_total_local_charges_patronales(self):
+        devise = self.poste.get_devise()
+        total = 0.0
+        for r in self.get_charges_patronales():
+            if r.devise != devise:
+                return None
+            total += float(r.montant)
+        return total
+
+    def get_local_salaire_brut(self):
+        """
+        somme des rémuérations brutes
+        """
+        devise = self.poste.get_devise()
+        total = 0.0
+        for r in self.get_remunerations_brutes():
+            if r.devise != devise:
+                return None
+            total += float(r.montant)
+        return total
+
+    def get_local_salaire_net(self):
+        """
+        salaire brut - charges salariales
+        """
+        devise = self.poste.get_devise()
+        total_charges = 0.0
+        for r in self.get_charges_salariales():
+            if r.devise != devise:
+                return None
+            total_charges += float(r.montant)
+        return self.get_local_salaire_brut() - total_charges
+
+    def get_local_couts_auf(self):
+        """
+        salaire net + charges patronales
+        """
+        devise = self.poste.get_devise()
+        total_charges = 0.0
+        for r in self.get_charges_patronales():
+            if r.devise != devise:
+                return None
+            total_charges += float(r.montant)
+        return self.get_local_salaire_net() + total_charges
+
+    def get_total_local_remunerations_tierces(self):
+        devise = self.poste.get_devise()
+        total = 0.0
+        for r in self.get_remunerations_tierces():
+            if r.devise != devise:
+                return None
+            total += float(r.montant)
+        return total
+
+    # DEVISE EURO
+
+    def get_total_charges_salariales(self):
+        total = 0.0
+        for r in self.get_charges_salariales():
+            total += r.montant_euros()
+        return total
+
+    def get_total_charges_patronales(self):
+        total = 0.0
+        for r in self.get_charges_patronales():
+            total += r.montant_euros()
+        return total
+
+    def get_salaire_brut(self):
+        """
+        somme des rémuérations brutes
+        """
+        total = 0.0
+        for r in self.get_remunerations_brutes():
+            total += r.montant_euros()
+        return total
+
+    def get_salaire_net(self):
+        """
+        salaire brut - charges salariales
+        """
+        total_charges = 0.0
+        for r in self.get_charges_salariales():
+            total_charges += r.montant_euros()
+        return self.get_salaire_brut() - total_charges
+
+    def get_couts_auf(self):
+        """
+        salaire net + charges patronales
+        """
+        total_charges = 0.0
+        for r in self.get_charges_patronales():
+            total_charges += r.montant_euros()
+        return self.get_salaire_net() + total_charges
+
+    def get_total_remunerations_tierces(self):
+        total = 0.0
+        for r in self.get_remunerations_tierces():
+            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) \
+                and (self.date_fin is None or self.date_fin >= today) \
+                and not (self.date_fin is None and self.date_debut is None)
+
 
 class Dossier(Dossier_):
     __doc__ = Dossier_.__doc__
 
 class Dossier(Dossier_):
     __doc__ = Dossier_.__doc__
-    poste = models.ForeignKey('%s.Poste' % app_context(),
-        db_column='poste',
-        related_name='%(app_label)s_dossiers',
+    poste = models.ForeignKey(
+        Poste, db_column='poste', related_name='rh_dossiers',
         help_text=u"Taper le nom du poste ou du type de poste",
         help_text=u"Taper le nom du poste ou du type de poste",
+    )
+    employe = models.ForeignKey(
+        'Employe', db_column='employe',
+        help_text=u"Taper le nom de l'employé",
+        related_name='rh_dossiers', verbose_name=u"employé"
+    )
+    principal = models.BooleanField(
+        u"dossier principal", default=True,
+        help_text=(
+            u"Ce dossier est pour le principal poste occupé par l'employé"
+        )
+    )
+    
+
+reversion.register(Dossier, format='xml', follow=[
+    'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
+    'rh_contrats', 'commentaires'
+])
+
+
+class RHDossierClassementRecord(models.Model):
+    classement = models.ForeignKey(
+        'Classement',
+        related_name='classement_records',
+        )
+    dossier = models.ForeignKey(
+        'Dossier',
+        related_name='classement_records',
         )
         )
-    employe = models.ForeignKey('Employe', db_column='employe',
-                            help_text=u"Taper le nom de l'employé",
-                            related_name='%(app_label)s_dossiers',
-                            verbose_name=u"Employé")
+    date_debut = models.DateField(
+        u"date de début",
+        help_text=HELP_TEXT_DATE,
+        null=True,
+        blank=True,
+        db_index=True
+    )
+    date_fin = models.DateField(
+        u"date de fin",
+        help_text=HELP_TEXT_DATE,
+        null=True,
+        blank=True,
+        db_index=True
+    )
+    commentaire = models.CharField(
+        max_length=2048,
+        blank=True,
+        null=True,
+        default='',
+        )
+
+    def __unicode__(self):
+        return self.classement.__unicode__()
+
+    class Meta:
+        verbose_name = u"Element d'historique de classement"
+        verbose_name_plural = u"Historique de classement"
+
+    @classmethod
+    def post_save_handler(cls,
+                          sender,
+                          instance,
+                          created,
+                          using,
+                          **kw):
+
+        today = date.today()
+        previous_record = None
+        previous_classement = None
+        has_changed = False
+
+        # Premièrement, pour les nouvelles instances:
+        if created:
+            if not instance.classement:
+                return
+            else:
+                cls.objects.create(
+                    date_debut=instance.date_debut,
+                    classement=instance.classement,
+                    dossier=instance,
+                    )
+                return
+
+        # Deuxièmement, pour les instances existantes:
+
+        # Détermine si:
+        # 1. Est-ce que le classement a changé?
+        # 2. Est-ce qu'une historique de classement existe déjà
+        try:
+            previous_record = cls.objects.get(
+                dossier=instance,
+                classement=instance.before_save.classement,
+                date_fin=None,
+                )
+        except cls.DoesNotExist:
+            if instance.before_save.classement:
+                # Il était censé avoir une historique de classement
+                # donc on le créé.
+                previous_record = cls.objects.create(
+                    date_debut=instance.before_save.date_debut,
+                    classement=instance.before_save.classement,
+                    dossier=instance,
+                    )
+                previous_classement = instance.before_save.classement
+        except MultipleObjectsReturned:
+            qs = cls.objects.filter(
+                dossier=instance,
+                classement=instance.before_save.classement,
+                date_fin=None,
+                )
+            latest = qs.latest('date_debut')
+            qs.exclude(id=latest.id).update(date_fin=today)
+            previous_record = latest
+            previous_classement = latest.classement
+        else:
+            previous_classement = previous_record.classement
+
+        has_changed = (
+            instance.classement !=
+            previous_classement
+            )
+
+        # Cas aucun changement:
+        if not has_changed:
+            return
+
+        else:
+            # Classement a changé
+            if previous_record:
+                previous_record.date_fin = today
+                previous_record.save()
+                
+            if instance.classement:
+                cls.objects.create(
+                    date_debut=today,
+                    classement=instance.classement,
+                    dossier=instance,
+                    )
 
 
 class DossierPiece_(models.Model):
 
 
 class DossierPiece_(models.Model):
-    """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
+    """
+    Documents relatifs au Dossier (à l'occupation de ce poste par employé).
     Ex.: Lettre de motivation.
     """
     Ex.: Lettre de motivation.
     """
-    dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_dossierpieces')
-    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(max_length=255)
+    fichier = models.FileField(
+        upload_to=dossier_piece_dispatch, storage=storage_prive,
+        max_length=255
+    )
 
     class Meta:
         abstract = True
 
     class Meta:
         abstract = True
@@ -697,64 +1268,82 @@ class DossierPiece_(models.Model):
     def __unicode__(self):
         return u'%s' % (self.nom)
 
     def __unicode__(self):
         return u'%s' % (self.nom)
 
+
 class DossierPiece(DossierPiece_):
 class DossierPiece(DossierPiece_):
-    pass
+    dossier = models.ForeignKey(
+        Dossier, db_column='dossier', related_name='rh_dossierpieces'
+    )
 
 
-class DossierCommentaire_(Commentaire):
-    dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
-    class Meta:
-        abstract = True
+reversion.register(DossierPiece, format='xml')
+
+class DossierCommentaire(Commentaire):
+    dossier = models.ForeignKey(
+        Dossier, db_column='dossier', related_name='commentaires'
+    )
+
+reversion.register(DossierCommentaire, format='xml')
 
 
-class DossierCommentaire(DossierCommentaire_):
-    pass
 
 class DossierComparaison_(models.Model, DevisableMixin):
     """
     Photo d'une comparaison salariale au moment de l'embauche.
     """
 
 class DossierComparaison_(models.Model, DevisableMixin):
     """
     Photo d'une comparaison salariale au moment de l'embauche.
     """
-    dossier = models.ForeignKey('%s.Dossier' % app_context(), related_name='%(app_label)s_comparaisons')
     objects = DossierComparaisonManager()
 
     objects = DossierComparaisonManager()
 
-    implantation = models.ForeignKey(ref.Implantation, related_name="+", 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)
     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('Devise', related_name='+', null=True, blank=True)
+    devise = models.ForeignKey(
+        'Devise', related_name='+', null=True, blank=True
+    )
 
     class Meta:
         abstract = True
 
 
     class Meta:
         abstract = True
 
-    def get_annee_pour_taux_devise(self):
-        return self.dossier.contrat_date_debut.year
+    def __unicode__(self):
+        return "%s (%s)" % (self.poste, self.personne)
 
 
 class DossierComparaison(DossierComparaison_):
 
 
 class DossierComparaison(DossierComparaison_):
-    pass
+    dossier = models.ForeignKey(
+        Dossier, related_name='rh_comparaisons'
+    )
+
+reversion.register(DossierComparaison, format='xml')
+
 
 ### RÉMUNÉRATION
 
 
 ### RÉMUNÉRATION
 
-class RemunerationMixin(AUFMetadata):
-    dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_remunerations')
+class RemunerationMixin(models.Model):
+
     # Identification
     # Identification
-    type = models.ForeignKey('TypeRemuneration', db_column='type', 
-                            related_name='+',
-                            verbose_name = u"Type de rémunération")
-    type_revalorisation = models.ForeignKey('TypeRevalorisation',
-                            db_column='type_revalorisation',
-                            related_name='+',
-                            verbose_name = u"Type de revalorisation",
-                            null=True, blank=True)
-    montant = models.DecimalField(null=True, blank=True,
-                            default=0, max_digits=12, decimal_places=2)
-                            # Annuel (12 mois, 52 semaines, 364 jours?)
-    devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
+    type = models.ForeignKey(
+        'TypeRemuneration', db_column='type', related_name='+',
+        verbose_name=u"type de rémunération"
+    )
+    type_revalorisation = models.ForeignKey(
+        'TypeRevalorisation', db_column='type_revalorisation',
+        related_name='+', verbose_name=u"type de revalorisation",
+        null=True, blank=True
+    )
+    montant = models.DecimalField(
+        null=True, blank=True, max_digits=12, decimal_places=2
+    )  # Annuel (12 mois, 52 semaines, 364 jours?)
+    devise = models.ForeignKey('Devise', db_column='devise', related_name='+')
+
     # commentaire = precision
     commentaire = models.CharField(max_length=255, null=True, blank=True)
     # commentaire = precision
     commentaire = models.CharField(max_length=255, null=True, blank=True)
+
     # date_debut = anciennement date_effectif
     # date_debut = anciennement date_effectif
-    date_debut = models.DateField(verbose_name = u"Date de début",
-                            null=True, blank=True)
-    date_fin = models.DateField(verbose_name = u"Date de fin",
-                            null=True, blank=True)
+    date_debut = models.DateField(
+        u"date de début", null=True, blank=True, db_index=True
+    )
+    date_fin = models.DateField(
+        u"date de fin", null=True, blank=True, db_index=True
+    )
 
     class Meta:
         abstract = True
 
     class Meta:
         abstract = True
@@ -763,27 +1352,78 @@ class RemunerationMixin(AUFMetadata):
     def __unicode__(self):
         return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
 
     def __unicode__(self):
         return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
 
+
 class Remuneration_(RemunerationMixin, DevisableMixin):
 class Remuneration_(RemunerationMixin, DevisableMixin):
-    """Structure de rémunération (données budgétaires) en situation normale
+    """
+    Structure de rémunération (données budgétaires) en situation normale
     pour un Dossier. Si un Evenement existe, utiliser la structure de
     rémunération EvenementRemuneration de cet événement.
     """
     pour un Dossier. Si un Evenement existe, utiliser la structure de
     rémunération EvenementRemuneration de cet événement.
     """
+    objects = RemunerationManager()
 
 
+    @staticmethod
+    def find_yearly_range(from_date, to_date, year):
+        today = date.today()
+        year = year or date.today().year
+        year_start = date(year, 1, 1)
+        year_end = date(year, 12, 31)
+
+        def constrain_to_year(*dates):
+            """
+            S'assure que les dates soient dans le range year_start a
+            year_end
+            """
+            return [min(max(year_start, d), year_end)
+                    for d in dates]
+
+        start_date = max(
+                from_date or year_start, year_start)
+        end_date = min(
+                to_date or year_end, year_end)
+        
+        start_date, end_date = constrain_to_year(start_date, end_date)
+
+        jours_annee = (year_end - year_start).days
+        jours_dates = (end_date - start_date).days
+        factor = Decimal(str(jours_dates)) / Decimal(str(jours_annee))
+        
+        return start_date, end_date, factor
+        
+
+    def montant_ajuste_euros(self, annee=None):
+        """
+        Le montant ajusté représente le montant annuel, ajusté sur la
+        période de temps travaillée, multipliée par le ratio de temps
+        travaillé (en rapport au temps plein).
+        """
+        date_debut, date_fin, factor = self.find_yearly_range(
+            self.date_debut,
+            self.date_fin,
+            annee,
+            )
+
+        montant_euros = Decimal(str(self.montant_euros_float()) or '0')
+        
+        if self.type.nature_remuneration != u'Accessoire':
+            dossier = getattr(self, 'dossier', None)
+            if not dossier:
+                """
+                Dans le cas d'un DossierComparaisonRemuneration, il
+                n'y a plus de reference au dossier.
+                """
+                regime_travail = REGIME_TRAVAIL_DEFAULT
+            else:
+                regime_travail = self.dossier.regime_travail
+            return (montant_euros * factor *
+                    regime_travail / 100)
+        else:
+            return montant_euros
+        
     def montant_mois(self):
         return round(self.montant / 12, 2)
 
     def montant_avec_regime(self):
     def montant_mois(self):
         return round(self.montant / 12, 2)
 
     def montant_avec_regime(self):
-        return round(self.montant * (self.dossier.regime_travail/100), 2)
-
-    def get_annee_pour_taux_devise(self):
-        annee = datetime.datetime.now().year
-        if self.dossier.poste.date_debut is not None:
-            annee = self.dossier.poste.date_debut.year
-        if self.dossier.date_debut is not None:
-            annee = self.dossier.date_debut.year
-        if self.date_debut is not None:
-            annee = self.date_debut.year
-        return annee
+        return round(self.montant * (self.dossier.regime_travail / 100), 2)
 
     def montant_euro_mois(self):
         return round(self.montant_euros() / 12, 2)
 
     def montant_euro_mois(self):
         return round(self.montant_euros() / 12, 2)
@@ -802,33 +1442,36 @@ class Remuneration_(RemunerationMixin, DevisableMixin):
 
 
 class Remuneration(Remuneration_):
 
 
 class Remuneration(Remuneration_):
-    pass
+    dossier = models.ForeignKey(
+        Dossier, db_column='dossier', related_name='rh_remunerations'
+    )
 
 
+reversion.register(Remuneration, format='xml')
 
 
-### CONTRATS
 
 
-class ContratManager(NoDeleteManager):
-    def get_query_set(self):
-        return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
+### CONTRATS
 
 
-        
-class Contrat_(AUFMetadata):
-    """Document juridique qui encadre la relation de travail d'un Employe
+class Contrat_(models.Model):
+    """
+    Document juridique qui encadre la relation de travail d'un Employe
     pour un Poste particulier. Pour un Dossier (qui documente cette
     relation de travail) plusieurs contrats peuvent être associés.
     """
     objects = ContratManager()
     pour un Poste particulier. Pour un Dossier (qui documente cette
     relation de travail) plusieurs contrats peuvent être associés.
     """
     objects = ContratManager()
-    dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_contrats')
-    type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat', 
-                            related_name='+',
-                            verbose_name = u"type de contrat")
-    date_debut = models.DateField(verbose_name = u"Date de début")
-    date_fin = models.DateField(verbose_name = u"Date de fin",
-                            null=True, blank=True)
-    fichier = models.FileField(verbose_name = u"Fichier",
-                            upload_to=contrat_dispatch,
-                            storage=storage_prive,
-                            null=True, blank=True)
+    type_contrat = models.ForeignKey(
+        'TypeContrat', db_column='type_contrat',
+        verbose_name=u'type de contrat', related_name='+'
+    )
+    date_debut = models.DateField(
+        u"date de début", db_index=True
+    )
+    date_fin = models.DateField(
+        u"date de fin", null=True, blank=True, db_index=True
+    )
+    fichier = models.FileField(
+        upload_to=contrat_dispatch, storage=storage_prive, null=True,
+        blank=True, max_length=255
+    )
 
     class Meta:
         abstract = True
 
     class Meta:
         abstract = True
@@ -839,104 +1482,69 @@ class Contrat_(AUFMetadata):
     def __unicode__(self):
         return u'%s - %s' % (self.dossier, self.id)
 
     def __unicode__(self):
         return u'%s - %s' % (self.dossier, self.id)
 
+
 class Contrat(Contrat_):
 class Contrat(Contrat_):
-    pass
-        
+    dossier = models.ForeignKey(
+        Dossier, db_column='dossier', related_name='rh_contrats'
+    )
 
 
-### ÉVÉNEMENTS
-
-#class Evenement_(AUFMetadata):
-#    """Un Evenement sert à déclarer une situation temporaire (exceptionnelle) 
-#    d'un Dossier qui vient altérer des informations normales liées à un Dossier 
-#    (ex.: la Remuneration).
-#    
-#    Ex.: congé de maternité, maladie...
-#    
-#    Lors de ces situations exceptionnelles, l'Employe a un régime de travail
-#    différent et une rémunération en conséquence. On souhaite toutefois
-#    conserver le Dossier intact afin d'éviter une re-saisie des données lors
-#    du retour à la normale.
-#    """
-#    dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', 
-#                            related_name='+')
-#    nom = models.CharField(max_length=255)
-#    date_debut = models.DateField(verbose_name = u"Date de début")
-#    date_fin = models.DateField(verbose_name = u"Date de fin",
-#                            null=True, blank=True)
-#
-#    class Meta:
-#        abstract = True
-#        ordering = ['nom']
-#        verbose_name = u"Évènement"
-#        verbose_name_plural = u"Évènements"
-#                            
-#    def __unicode__(self):
-#        return u'%s' % (self.nom)
-#
-#
-#class Evenement(Evenement_):
-#    __doc__ = Evenement_.__doc__
-#
-#    
-#class EvenementRemuneration_(RemunerationMixin):
-#    """Structure de rémunération liée à un Evenement qui remplace 
-#    temporairement la Remuneration normale d'un Dossier, pour toute la durée
-#    de l'Evenement.
-#    """
-#    evenement = models.ForeignKey("Evenement", db_column='evenement',
-#                            related_name='+',
-#                            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 = u"Évènement - rémunération"
-#        verbose_name_plural = u"Évènements - rémunérations"
-#
-#
-#class EvenementRemuneration(EvenementRemuneration_):
-#    __doc__ = EvenementRemuneration_.__doc__
-#
-#    class Meta:
-#        abstract = True
-#
-#
-#class EvenementRemuneration(EvenementRemuneration_):
-#    __doc__ = EvenementRemuneration_.__doc__
-# TODO? class ContratPiece(models.Model):
+reversion.register(Contrat, format='xml')
 
 
 ### RÉFÉRENCES RH
 
 
 
 ### RÉFÉRENCES RH
 
-class FamilleEmploi(AUFMetadata):
-    """Catégorie utilisée dans la gestion des Postes.
+class CategorieEmploi(models.Model):
+    """
+    Catégorie utilisée dans la gestion des Postes.
     Catégorie supérieure à TypePoste.
     """
     nom = models.CharField(max_length=255)
 
     class Meta:
     Catégorie supérieure à TypePoste.
     """
     nom = models.CharField(max_length=255)
 
     class Meta:
-        ordering = ['nom']
-        verbose_name = u"Famille d'emploi"
-        verbose_name_plural = u"Familles d'emploi"
+        ordering = ('nom',)
+        verbose_name = u"catégorie d'emploi"
+        verbose_name_plural = u"catégories d'emploi"
 
     def __unicode__(self):
 
     def __unicode__(self):
-        return u'%s' % (self.nom)
+        return self.nom
+
+reversion.register(CategorieEmploi, format='xml')
+
 
 
-class TypePoste(AUFMetadata):
-    """Catégorie de Poste.
+class FamilleProfessionnelle(models.Model):
     """
     """
-    nom = models.CharField(max_length=255)
-    nom_feminin = models.CharField(max_length=255,
-                            verbose_name = u"Nom féminin")
+    Famille professionnelle d'un poste.
+    """
+    nom = models.CharField(max_length=100)
 
 
-    is_responsable = models.BooleanField(default=False,
-                            verbose_name = u"Poste de responsabilité")
-    famille_emploi = models.ForeignKey('FamilleEmploi',
-                            db_column='famille_emploi',
-                            related_name='+',
-                            verbose_name = u"famille d'emploi")
+    class Meta:
+        ordering = ('nom',)
+        verbose_name = u'famille professionnelle'
+        verbose_name_plural = u'familles professionnelles'
+
+    def __unicode__(self):
+        return self.nom
+
+reversion.register(FamilleProfessionnelle, format='xml')
+
+
+class TypePoste(Archivable):
+    """
+    Catégorie de Poste.
+    """
+    nom = models.CharField(max_length=255)
+    nom_feminin = models.CharField(u"nom féminin", max_length=255)
+    is_responsable = models.BooleanField(
+        u"poste de responsabilité", default=False
+    )
+    categorie_emploi = models.ForeignKey(
+        CategorieEmploi, db_column='categorie_emploi', related_name='+',
+        verbose_name=u"catégorie d'emploi"
+    )
+    famille_professionnelle = models.ForeignKey(
+        FamilleProfessionnelle, related_name='types_de_poste',
+        verbose_name=u"famille professionnelle", blank=True, null=True
+    )
 
     class Meta:
         ordering = ['nom']
 
     class Meta:
         ordering = ['nom']
@@ -946,6 +1554,8 @@ class TypePoste(AUFMetadata):
     def __unicode__(self):
         return u'%s' % (self.nom)
 
     def __unicode__(self):
         return u'%s' % (self.nom)
 
+reversion.register(TypePoste, format='xml')
+
 
 TYPE_PAIEMENT_CHOICES = (
     (u'Régulier', u'Régulier'),
 
 TYPE_PAIEMENT_CHOICES = (
     (u'Régulier', u'Régulier'),
@@ -953,23 +1563,31 @@ TYPE_PAIEMENT_CHOICES = (
 )
 
 NATURE_REMUNERATION_CHOICES = (
 )
 
 NATURE_REMUNERATION_CHOICES = (
-    (u'Accessoire', u'Accessoire'),
-    (u'Charges', u'Charges'),
-    (u'Indemnité', u'Indemnité'),
-    (u'RAS', u'Rémunération autre source'),
     (u'Traitement', u'Traitement'),
     (u'Traitement', u'Traitement'),
+    (u'Indemnité', u'Indemnités autres'),
+    (u'Charges', u'Charges patronales'),
+    (u'Accessoire', u'Accessoires'),
+    (u'RAS', u'Rémunération autre source'),
 )
 
 )
 
-class TypeRemuneration(AUFMetadata):
-    """Catégorie de Remuneration.
+
+class TypeRemuneration(Archivable):
+    """
+    Catégorie de Remuneration.
     """
     """
+
+    objects = models.Manager()
+    sans_archives = ArchivableManager()
+
     nom = models.CharField(max_length=255)
     nom = models.CharField(max_length=255)
-    type_paiement = models.CharField(max_length=30,
-                            choices=TYPE_PAIEMENT_CHOICES,
-                            verbose_name = u"Type de paiement")
-    nature_remuneration = models.CharField(max_length=30,
-                            choices=NATURE_REMUNERATION_CHOICES,
-                            verbose_name = u"Nature de la rémunération")
+    type_paiement = models.CharField(
+        u"type de paiement", max_length=30, choices=TYPE_PAIEMENT_CHOICES
+    )
+    
+    nature_remuneration = models.CharField(
+        u"nature de la rémunération", max_length=30,
+        choices=NATURE_REMUNERATION_CHOICES
+    )
 
     class Meta:
         ordering = ['nom']
 
     class Meta:
         ordering = ['nom']
@@ -977,10 +1595,14 @@ class TypeRemuneration(AUFMetadata):
         verbose_name_plural = u"Types de rémunération"
 
     def __unicode__(self):
         verbose_name_plural = u"Types de rémunération"
 
     def __unicode__(self):
-        return u'%s' % (self.nom)
+        return self.nom
+
+reversion.register(TypeRemuneration, format='xml')
 
 
-class TypeRevalorisation(AUFMetadata):
-    """Justification du changement de la Remuneration.
+
+class TypeRevalorisation(Archivable):
+    """
+    Justification du changement de la Remuneration.
     (Actuellement utilisé dans aucun traitement informatique.)
     """
     nom = models.CharField(max_length=255)
     (Actuellement utilisé dans aucun traitement informatique.)
     """
     nom = models.CharField(max_length=255)
@@ -993,23 +1615,24 @@ class TypeRevalorisation(AUFMetadata):
     def __unicode__(self):
         return u'%s' % (self.nom)
 
     def __unicode__(self):
         return u'%s' % (self.nom)
 
-class Service(AUFMetadata):
-    """Unité administrative où les Postes sont rattachés.
+reversion.register(TypeRevalorisation, format='xml')
+
+
+class Service(Archivable):
+    """
+    Unité administrative où les Postes sont rattachés.
     """
     """
-    archive = models.BooleanField(verbose_name=u"Archivé", default=False)
     nom = models.CharField(max_length=255)
 
     class Meta:
         ordering = ['nom']
     nom = models.CharField(max_length=255)
 
     class Meta:
         ordering = ['nom']
-        verbose_name = u"Service"
-        verbose_name_plural = u"Services"
+        verbose_name = u"service"
+        verbose_name_plural = u"services"
 
     def __unicode__(self):
 
     def __unicode__(self):
-        if self.archive:
-            archive = u"(archivé)"
-        else:
-            archive = "" 
-        return u'%s %s' % (self.nom, archive)
+        return self.nom
+
+reversion.register(Service, format='xml')
 
 
 TYPE_ORGANISME_CHOICES = (
 
 
 TYPE_ORGANISME_CHOICES = (
@@ -1017,8 +1640,10 @@ TYPE_ORGANISME_CHOICES = (
     ('DET', 'Détachement'),
 )
 
     ('DET', 'Détachement'),
 )
 
-class OrganismeBstg(AUFMetadata):
-    """Organisation d'où provient un Employe mis à disposition (MAD) de
+
+class OrganismeBstg(models.Model):
+    """
+    Organisation d'où provient un Employe mis à disposition (MAD) de
     ou détaché (DET) à l'AUF à titre gratuit.
 
     (BSTG = bien et service à titre gratuit.)
     ou détaché (DET) à l'AUF à titre gratuit.
 
     (BSTG = bien et service à titre gratuit.)
@@ -1038,16 +1663,23 @@ class OrganismeBstg(AUFMetadata):
     def __unicode__(self):
         return u'%s (%s)' % (self.nom, self.get_type_display())
 
     def __unicode__(self):
         return u'%s (%s)' % (self.nom, self.get_type_display())
 
-    prefix_implantation = "pays__region"
-    def get_regions(self):
-        return [self.pays.region]
+reversion.register(OrganismeBstg, format='xml')
 
 
 
 
-class Statut(AUFMetadata):
-    """Statut de l'Employe dans le cadre d'un Dossier particulier.
+class Statut(Archivable):
+    """
+    Statut de l'Employe dans le cadre d'un Dossier particulier.
     """
     # Identification
     """
     # Identification
-    code = models.CharField(max_length=25, unique=True, help_text="Saisir un code court mais lisible pour ce statut : le code est utilisé pour associer les statuts aux autres données tout en demeurant plus lisible qu'un identifiant numérique.")
+    code = models.CharField(
+        max_length=25, unique=True,
+        help_text=(
+            u"Saisir un code court mais lisible pour ce statut : "
+            u"le code est utilisé pour associer les statuts aux autres "
+            u"données tout en demeurant plus lisible qu'un identifiant "
+            u"numérique."
+        )
+    )
     nom = models.CharField(max_length=255)
 
     class Meta:
     nom = models.CharField(max_length=255)
 
     class Meta:
@@ -1058,6 +1690,8 @@ class Statut(AUFMetadata):
     def __unicode__(self):
         return u'%s : %s' % (self.code, self.nom)
 
     def __unicode__(self):
         return u'%s : %s' % (self.code, self.nom)
 
+reversion.register(Statut, format='xml')
+
 
 TYPE_CLASSEMENT_CHOICES = (
     ('S', 'S -Soutien'),
 
 TYPE_CLASSEMENT_CHOICES = (
     ('S', 'S -Soutien'),
@@ -1069,19 +1703,28 @@ TYPE_CLASSEMENT_CHOICES = (
     ('HG', 'HG - Hors grille [direction]'),
 )
 
     ('HG', 'HG - Hors grille [direction]'),
 )
 
+
 class ClassementManager(models.Manager):
     """
     Ordonner les spcéfiquement les classements.
     """
     def get_query_set(self):
 class ClassementManager(models.Manager):
     """
     Ordonner les spcéfiquement les classements.
     """
     def get_query_set(self):
-        qs = super(self.__class__, self).get_query_set()
-        qs = qs.extra(select={'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'})
+        qs = super(ClassementManager, self).get_query_set()
+        qs = qs.extra(select={
+            'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
+        })
         qs = qs.extra(order_by=('ponderation', 'echelon',  'degre', ))
         return qs.all()
 
 
         qs = qs.extra(order_by=('ponderation', 'echelon',  'degre', ))
         return qs.all()
 
 
-class Classement_(AUFMetadata):
-    """Éléments de classement de la
+class ClassementArchivableManager(ClassementManager,
+                                  ArchivableManager):
+    pass
+
+
+class Classement_(Archivable):
+    """
+    Éléments de classement de la
     "Grille générique de classement hiérarchique".
 
     Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
     "Grille générique de classement hiérarchique".
 
     Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
@@ -1090,44 +1733,50 @@ class Classement_(AUFMetadata):
     salaire de base = coefficient * valeur du point de l'Implantation du Poste
     """
     objects = ClassementManager()
     salaire de base = coefficient * valeur du point de l'Implantation du Poste
     """
     objects = ClassementManager()
+    sans_archives = ClassementArchivableManager()
 
     # Identification
     type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
 
     # Identification
     type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
-    echelon = models.IntegerField(verbose_name=u"Échelon", blank=True, default=0)
-    degre = models.IntegerField(verbose_name=u"Degré", blank=True, default=0)
-    coefficient = models.FloatField(default=0, verbose_name=u"Coefficient",
-                                    null=True)
+    echelon = models.IntegerField(u"échelon", blank=True, default=0)
+    degre = models.IntegerField(u"degré", blank=True, default=0)
+    coefficient = models.FloatField(u"coefficient", blank=True, null=True)
+
     # Méta
     # annee # au lieu de date_debut et date_fin
     commentaire = models.TextField(null=True, blank=True)
 
     class Meta:
         abstract = True
     # Méta
     # annee # au lieu de date_debut et date_fin
     commentaire = models.TextField(null=True, blank=True)
 
     class Meta:
         abstract = True
-        ordering = ['type','echelon','degre','coefficient']
+        ordering = ['type', 'echelon', 'degre', 'coefficient']
         verbose_name = u"Classement"
         verbose_name_plural = u"Classements"
 
     def __unicode__(self):
         return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
 
         verbose_name = u"Classement"
         verbose_name_plural = u"Classements"
 
     def __unicode__(self):
         return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
 
+
 class Classement(Classement_):
     __doc__ = Classement_.__doc__
 
 class Classement(Classement_):
     __doc__ = Classement_.__doc__
 
+reversion.register(Classement, format='xml')
+
 
 
-class TauxChange_(AUFMetadata):
-    """Taux de change de la devise vers l'euro (EUR)
+class TauxChange_(models.Model):
+    """
+    Taux de change de la devise vers l'euro (EUR)
     pour chaque année budgétaire.
     """
     # Identification
     devise = models.ForeignKey('Devise', db_column='devise')
     pour chaque année budgétaire.
     """
     # Identification
     devise = models.ForeignKey('Devise', db_column='devise')
-    annee = models.IntegerField(verbose_name = u"Année")
-    taux = models.FloatField(verbose_name = u"Taux vers l'euro")
+    annee = models.IntegerField(u"année")
+    taux = models.FloatField(u"taux vers l'euro")
 
     class Meta:
         abstract = True
         ordering = ['-annee', 'devise__code']
         verbose_name = u"Taux de change"
         verbose_name_plural = u"Taux de change"
 
     class Meta:
         abstract = True
         ordering = ['-annee', 'devise__code']
         verbose_name = u"Taux de change"
         verbose_name_plural = u"Taux de change"
+        unique_together = ('devise', 'annee')
 
     def __unicode__(self):
         return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
 
     def __unicode__(self):
         return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
@@ -1136,20 +1785,26 @@ class TauxChange_(AUFMetadata):
 class TauxChange(TauxChange_):
     __doc__ = TauxChange_.__doc__
 
 class TauxChange(TauxChange_):
     __doc__ = TauxChange_.__doc__
 
-class ValeurPointManager(NoDeleteManager):
+reversion.register(TauxChange, format='xml')
+
+
+class ValeurPointManager(models.Manager):
 
     def get_query_set(self):
 
     def get_query_set(self):
-        return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
+        return super(ValeurPointManager, self).get_query_set() \
+            .select_related('devise', 'implantation')
 
 
 
 
-class ValeurPoint_(AUFMetadata):
-    """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
+class ValeurPoint_(models.Model):
+    """
+    Utile pour connaître, pour un Dossier, le salaire de base théorique lié
     au classement dans la grille. La ValeurPoint s'obtient par l'implantation
     du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
 
     salaire de base = coefficient * valeur du point de l'Implantation du Poste
     """
 
     au classement dans la grille. La ValeurPoint s'obtient par l'implantation
     du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
 
     salaire de base = coefficient * valeur du point de l'Implantation du Poste
     """
 
+    objects = models.Manager()
     actuelles = ValeurPointManager()
 
     valeur = models.FloatField(null=True)
     actuelles = ValeurPointManager()
 
     valeur = models.FloatField(null=True)
@@ -1165,36 +1820,42 @@ class ValeurPoint_(AUFMetadata):
         abstract = True
         verbose_name = u"Valeur du point"
         verbose_name_plural = u"Valeurs du point"
         abstract = True
         verbose_name = u"Valeur du point"
         verbose_name_plural = u"Valeurs du point"
+        unique_together = ('implantation', 'annee')
 
     def __unicode__(self):
 
     def __unicode__(self):
-        return u'%s %s %s [%s] %s' % (self.devise.code, self.annee, self.valeur, self.implantation.nom_court, self.devise.nom)
+        return u'%s %s %s [%s] %s' % (
+            self.devise.code, self.annee, self.valeur,
+            self.implantation.nom_court, self.devise.nom
+        )
 
 
 class ValeurPoint(ValeurPoint_):
     __doc__ = ValeurPoint_.__doc__
 
 
 
 class ValeurPoint(ValeurPoint_):
     __doc__ = ValeurPoint_.__doc__
 
+reversion.register(ValeurPoint, format='xml')
 
 
 
 
-class Devise(AUFMetadata):
-    """Devise monétaire.
+class Devise(Archivable):
     """
     """
-    
-    objects = DeviseManager()
-
-    archive = models.BooleanField(verbose_name=u"Archivé", default=False)
-    code =  models.CharField(max_length=10, unique=True)
+    Devise monétaire.
+    """
+    code = models.CharField(max_length=10, unique=True)
     nom = models.CharField(max_length=255)
 
     class Meta:
         ordering = ['code']
     nom = models.CharField(max_length=255)
 
     class Meta:
         ordering = ['code']
-        verbose_name = u"Devise"
-        verbose_name_plural = u"Devises"
+        verbose_name = u"devise"
+        verbose_name_plural = u"devises"
 
     def __unicode__(self):
         return u'%s - %s' % (self.code, self.nom)
 
 
     def __unicode__(self):
         return u'%s - %s' % (self.code, self.nom)
 
-class TypeContrat(AUFMetadata):
-    """Type de contrat.
+reversion.register(Devise, format='xml')
+
+
+class TypeContrat(Archivable):
+    """
+    Type de contrat.
     """
     nom = models.CharField(max_length=255)
     nom_long = models.CharField(max_length=255)
     """
     nom = models.CharField(max_length=255)
     nom_long = models.CharField(max_length=255)
@@ -1207,19 +1868,36 @@ class TypeContrat(AUFMetadata):
     def __unicode__(self):
         return u'%s' % (self.nom)
 
     def __unicode__(self):
         return u'%s' % (self.nom)
 
+reversion.register(TypeContrat, format='xml')
+
 
 ### AUTRES
 
 
 ### AUTRES
 
-class ResponsableImplantation(AUFMetadata):
-    """Le responsable d'une implantation.
+class ResponsableImplantationProxy(ref.Implantation):
+
+    def save(self):
+        pass
+
+    class Meta:
+        managed = False
+        proxy = True
+        verbose_name = u"Responsable d'implantation"
+        verbose_name_plural = u"Responsables d'implantation"
+
+
+class ResponsableImplantation(models.Model):
+    """
+    Le responsable d'une implantation.
     Anciennement géré sur le Dossier du responsable.
     """
     Anciennement géré sur le Dossier du responsable.
     """
-    employe = models.ForeignKey('Employe', db_column='employe',
-                            related_name='+',
-                            null=True, blank=True)
-    implantation = models.ForeignKey(ref.Implantation,
-                            db_column='implantation', related_name='+',
-                            unique=True)
+    employe = models.ForeignKey(
+        'Employe', db_column='employe', related_name='+', null=True,
+        blank=True
+    )
+    implantation = models.OneToOneField(
+        "ResponsableImplantationProxy", db_column='implantation',
+        related_name='responsable', unique=True
+    )
 
     def __unicode__(self):
         return u'%s : %s' % (self.implantation, self.employe)
 
     def __unicode__(self):
         return u'%s : %s' % (self.implantation, self.employe)
@@ -1229,3 +1907,222 @@ class ResponsableImplantation(AUFMetadata):
         verbose_name = "Responsable d'implantation"
         verbose_name_plural = "Responsables d'implantation"
 
         verbose_name = "Responsable d'implantation"
         verbose_name_plural = "Responsables d'implantation"
 
+reversion.register(ResponsableImplantation, format='xml')
+
+
+class UserProfile(models.Model):
+    user = models.OneToOneField(User, related_name='profile')
+    zones_administratives = models.ManyToManyField(
+        ref.ZoneAdministrative,
+        related_name='profiles'
+        )
+    class Meta:
+        verbose_name = "Permissions sur zones administratives"
+        verbose_name_plural = "Permissions sur zones administratives"
+
+    def __unicode__(self):
+        return self.user.__unicode__()
+
+reversion.register(UserProfile, format='xml')
+
+
+
+TYPES_CHANGEMENT = (
+    ('NO', 'Arrivée'),
+    ('MO', 'Mobilité'),
+    ('DE', 'Départ'),
+    )
+
+
+class ChangementPersonnelNotifications(models.Model):
+    class Meta:
+        verbose_name = u"Destinataire pour notices de mouvement de personnel"
+        verbose_name_plural = u"Destinataires pour notices de mouvement de personnel"
+
+    type = models.CharField(
+        max_length=2,
+        choices = TYPES_CHANGEMENT,
+        unique=True,
+        )
+
+    destinataires = models.ManyToManyField(
+        ref.Employe,
+        related_name='changement_notifications',
+        )
+
+    def __unicode__(self):
+        return '%s: %s' % (
+            self.get_type_display(), ','.join(
+                self.destinataires.all().values_list(
+                    'courriel', flat=True))
+            )
+    
+
+class ChangementPersonnel(models.Model):
+    """
+    Une notice qui enregistre un changement de personnel, incluant:
+
+    * Nouveaux employés
+    * Mouvement de personnel
+    * Départ d'employé
+    """
+
+    class Meta:
+        verbose_name = u"Mouvement de personnel"
+        verbose_name_plural = u"Mouvements de personnel"
+
+    def __unicode__(self):
+        return '%s: %s' % (self.dossier.__unicode__(),
+                           self.get_type_display())
+
+    @classmethod
+    def create_changement(cls, dossier, type):
+        # If this employe has existing Changement, set them to invalid.
+        cls.objects.filter(dossier__employe=dossier.employe).update(valide=False)
+
+        # Create a new one.
+        cls.objects.create(
+            dossier=dossier,
+            type=type,
+            valide=True,
+            communique=False,
+            )
+
+
+    @classmethod
+    def post_save_handler(cls,
+                          sender,
+                          instance,
+                          created,
+                          using,
+                          **kw):
+
+        # This defines the time limit used when checking in previous
+        # files to see if an employee if new. Basically, if emloyee
+        # left his position new_file.date_debut -
+        # NEW_EMPLOYE_THRESHOLD +1 ago (compared to date_debut), then
+        # if a  new file is created for this employee, he will bec
+        # onsidered "NEW" and a notice will be created to this effect.
+        NEW_EMPLOYE_THRESHOLD = datetime.timedelta(7)  # 7 days.
+
+        other_dossier_qs = instance.employe.rh_dossiers.exclude(
+            id=instance.id)
+        dd = instance.date_debut
+        df = instance.date_fin
+        today = date.today()
+       
+        # Here, verify differences between the instance, before and
+        # after the save.
+        df_has_changed = False
+
+        if created:
+            if df != None:
+                df_has_changed = True
+        else:
+            df_has_changed = (df != instance.before_save.date_fin and
+                              df != None)
+
+
+        # VERIFICATIONS:
+
+        # Date de fin est None et c'est une nouvelle instance de
+        # Dossier
+        if not df and created:
+            # QS for finding other dossiers with a date_fin of None OR
+            # with a date_fin >= to this dossier's date_debut
+            exists_recent_file_qs = other_dossier_qs.filter(
+                Q(date_fin__isnull=True) |
+                Q(date_fin__gte=dd - NEW_EMPLOYE_THRESHOLD)
+                )
+
+            # 1. If existe un Dossier récent
+            if exists_recent_file_qs.count() > 0:
+                cls.create_changement(
+                    instance,
+                    'MO',
+                    )
+            # 2. Il n'existe un Dossier récent, et c'est une nouvelle
+            # instance de Dossier:
+            else:
+                cls.create_changement(
+                    instance,
+                    'NO',
+                    )
+
+        elif not df and not created and cls.objects.filter(
+            valide=True,
+            date_creation__gte=today - NEW_EMPLOYE_THRESHOLD,
+            type='DE',
+            ).count() > 0:
+            cls.create_changement(
+                instance,
+                'MO',
+                )
+
+        # Date de fin a été modifiée:
+        if df_has_changed:
+            # QS for other active files (date_fin == None), excludes
+            # instance.
+            exists_active_files_qs = other_dossier_qs.filter(
+                Q(date_fin__isnull=True))
+
+            # 3. Date de fin a été modifiée et il n'existe aucun autre
+            # dossier actifs: Depart
+            if exists_active_files_qs.count() == 0:
+                cls.create_changement(
+                    instance,
+                    'DE',
+                    )
+            # 4. Dossier a une nouvelle date de fin par contre
+            # d'autres dossiers actifs existent déjà: Mouvement
+            else:
+                cls.create_changement(
+                    instance,
+                    'MO',
+                    )
+            
+
+    dossier = models.ForeignKey(
+        Dossier,
+        related_name='mouvements',
+        )
+
+    valide = models.BooleanField(default=True)
+    date_creation = models.DateTimeField(
+        auto_now_add=True)
+    communique = models.BooleanField(
+        u'Communiqué',
+        default=False,
+        )
+    date_communication = models.DateTimeField(
+                null=True,
+                blank=True,
+                )
+
+    type = models.CharField(
+        max_length=2,
+        choices = TYPES_CHANGEMENT,
+        )
+
+reversion.register(ChangementPersonnel, format='xml')
+
+
+def dossier_pre_save_handler(sender,
+                     instance,
+                     using,
+                     **kw):
+    # Store a copy of the model before save is called.
+    if instance.pk is not None:
+        instance.before_save = Dossier.objects.get(pk=instance.pk)
+    else:
+        instance.before_save = None
+
+
+# Connect a pre_save handler that assigns a copy of the model as an
+# attribute in order to compare it in post_save.
+pre_save.connect(dossier_pre_save_handler, sender=Dossier)
+
+post_save.connect(ChangementPersonnel.post_save_handler, sender=Dossier)
+post_save.connect(RHDossierClassementRecord.post_save_handler, sender=Dossier)
+
+