Augmenté la taille des FileField
[auf_rh_dae.git] / project / rh / models.py
index 48c1cf1..3c7a538 100644 (file)
@@ -4,36 +4,41 @@ 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.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
 from auf.django.references import models as ref
 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.models import Q
 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 change_list import \
+from project.rh.change_list import \
         RechercheTemporelle, KEY_STATUT, STATUT_ACTIF, STATUT_INACTIF, \
         STATUT_FUTUR
         RechercheTemporelle, KEY_STATUT, STATUT_ACTIF, STATUT_INACTIF, \
         STATUT_FUTUR
-from managers import \
-        PosteManager, DossierManager, DossierComparaisonManager, \
-        PosteComparaisonManager, DeviseManager, ServiceManager, \
-        TypeRemunerationManager
-from validators import validate_date_passee
+from project import groups
+from project.rh.managers import (
+    PosteManager,
+    DossierManager,
+    EmployeManager,
+    DossierComparaisonManager,
+    PosteComparaisonManager,
+    ContratManager,
+    RemunerationManager,
+    ArchivableManager,
+    )
+
 
 
+TWOPLACES = Decimal('0.01')
 
 
-# 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]
+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"
 
 # Constantes
 HELP_TEXT_DATE = "format: jj-mm-aaaa"
@@ -48,6 +53,9 @@ storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
                             base_url=settings.PRIVE_MEDIA_URL)
 
 
                             base_url=settings.PRIVE_MEDIA_URL)
 
 
+class RemunIntegrityException(Exception):
+    pass
+
 def poste_piece_dispatch(instance, filename):
     path = "%s/poste/%s/%s" % (
         instance._meta.app_label, instance.poste_id, filename
 def poste_piece_dispatch(instance, filename):
     path = "%s/poste/%s/%s" % (
         instance._meta.app_label, instance.poste_id, filename
@@ -76,6 +84,30 @@ def contrat_dispatch(instance, 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):
@@ -91,39 +123,32 @@ class DevisableMixin(object):
             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=devise, annee=annee)
-        ]
-        taux = set(taux)
-
-        if len(taux) == 0:
-            raise Exception(
-                u"Pas de taux pour %s en %s" % (devise.code, annee)
-            )
+        taux = TauxChange.objects.filter(devise=devise, annee__lte=annee) \
+                .order_by('-annee')
+        return taux[0].taux
 
 
-        if len(taux) > 1:
-            raise Exception(u"Il existe plusieurs taux de %s en %s" %
-                    (devise.code, annee))
-        else:
-            return list(taux)[0]
-
-    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()
     owner = models.ForeignKey(
         'auth.User', db_column='owner', related_name='+',
         verbose_name=u"Commentaire de"
     )
     texte = models.TextField()
     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
@@ -141,7 +166,7 @@ POSTE_APPEL_CHOICES = (
 )
 
 
 )
 
 
-class Poste_(AUFMetadata):
+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éé.
     """
     Un Poste est un emploi (job) à combler dans une implantation.
     Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
@@ -166,7 +191,7 @@ class Poste_(AUFMetadata):
         null=True, verbose_name=u"type de poste"
     )
     service = models.ForeignKey(
         null=True, verbose_name=u"type de poste"
     )
     service = models.ForeignKey(
-        'Service', db_column='service', related_name='+',
+        'Service', db_column='service', related_name='%(app_label)s_postes',
         verbose_name=u"direction/service/pôle support", null=True
     )
     responsable = models.ForeignKey(
         verbose_name=u"direction/service/pôle support", null=True
     )
     responsable = models.ForeignKey(
@@ -231,22 +256,22 @@ class Poste_(AUFMetadata):
         'Devise', db_column='devise_max', null=True, related_name='+'
     )
     salaire_min = models.DecimalField(
         'Devise', db_column='devise_max', null=True, related_name='+'
     )
     salaire_min = models.DecimalField(
-        max_digits=12, decimal_places=2, null=True, default=0
+        max_digits=12, decimal_places=2, default=0,
     )
     salaire_max = models.DecimalField(
     )
     salaire_max = models.DecimalField(
-        max_digits=12, decimal_places=2, null=True, default=0
+        max_digits=12, decimal_places=2, default=0,
     )
     indemn_min = models.DecimalField(
     )
     indemn_min = models.DecimalField(
-        max_digits=12, decimal_places=2, null=True, default=0
+        max_digits=12, decimal_places=2, default=0,
     )
     indemn_max = models.DecimalField(
     )
     indemn_max = models.DecimalField(
-        max_digits=12, decimal_places=2, null=True, default=0
+        max_digits=12, decimal_places=2, default=0,
     )
     autre_min = models.DecimalField(
     )
     autre_min = models.DecimalField(
-        max_digits=12, decimal_places=2, null=True, default=0
+        max_digits=12, decimal_places=2, default=0,
     )
     autre_max = models.DecimalField(
     )
     autre_max = models.DecimalField(
-        max_digits=12, decimal_places=2, null=True, default=0
+        max_digits=12, decimal_places=2, default=0,
     )
 
     # Comparatifs de rémunération
     )
 
     # Comparatifs de rémunération
@@ -290,10 +315,12 @@ class Poste_(AUFMetadata):
 
     # Autres Metadata
     date_debut = models.DateField(
 
     # Autres Metadata
     date_debut = models.DateField(
-        u"date de début", help_text=HELP_TEXT_DATE, null=True, blank=True
+        u"date de début", help_text=HELP_TEXT_DATE, null=True, blank=True,
+        db_index=True
     )
     date_fin = models.DateField(
     )
     date_fin = models.DateField(
-        u"date de fin", help_text=HELP_TEXT_DATE, null=True, blank=True
+        u"date de fin", help_text=HELP_TEXT_DATE, null=True, blank=True,
+        db_index=True
     )
 
     class Meta:
     )
 
     class Meta:
@@ -309,10 +336,10 @@ class Poste_(AUFMetadata):
         )
         return representation
 
         )
         return representation
 
-    prefix_implantation = "implantation__region"
+    prefix_implantation = "implantation__zone_administrative"
 
 
-    def get_regions(self):
-        return [self.implantation.region]
+    def get_zones_administratives(self):
+        return [self.implantation.zone_administrative]
 
     def get_devise(self):
         vp = ValeurPoint.objects.filter(
 
     def get_devise(self):
         vp = ValeurPoint.objects.filter(
@@ -344,11 +371,15 @@ class Poste(Poste_):
         UTILISE pour mettre a jour le flag vacant
         """
         return [
         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())
+            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 = (
     ('A', 'A - Frais de personnel'),
 
 POSTE_FINANCEMENT_CHOICES = (
     ('A', 'A - Frais de personnel'),
@@ -362,10 +393,6 @@ class PosteFinancement_(models.Model):
     Pour un Poste, structure d'informations décrivant comment on prévoit
     financer ce Poste.
     """
     Pour un Poste, structure d'informations décrivant comment on prévoit
     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)
     pourcentage = models.DecimalField(
         max_digits=12, decimal_places=2,
     type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
     pourcentage = models.DecimalField(
         max_digits=12, decimal_places=2,
@@ -387,7 +414,11 @@ 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):
@@ -395,13 +426,10 @@ class PostePiece_(models.Model):
     Documents relatifs au Poste.
     Ex.: Description de poste
     """
     Documents relatifs au Poste.
     Ex.: Description de poste
     """
-    poste = models.ForeignKey(
-        '%s.Poste' % app_context(), db_column='poste',
-        related_name='%(app_label)s_pieces'
-    )
     nom = models.CharField(u"Nom", max_length=255)
     fichier = models.FileField(
     nom = models.CharField(u"Nom", max_length=255)
     fichier = models.FileField(
-        u"Fichier", upload_to=poste_piece_dispatch, storage=storage_prive
+        u"Fichier", upload_to=poste_piece_dispatch, storage=storage_prive,
+        max_length=255
     )
 
     class Meta:
     )
 
     class Meta:
@@ -413,18 +441,18 @@ class PostePiece_(models.Model):
 
 
 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()
 
     implantation = models.ForeignKey(
     objects = PosteComparaisonManager()
 
     implantation = models.ForeignKey(
@@ -444,25 +472,23 @@ class PosteComparaison_(AUFMetadata, DevisableMixin):
 
 
 class PosteComparaison(PosteComparaison_):
 
 
 class PosteComparaison(PosteComparaison_):
-    objects = NoDeleteManager()
-
-
-class PosteCommentaire_(Commentaire):
     poste = models.ForeignKey(
     poste = models.ForeignKey(
-        '%s.Poste' % app_context(), db_column='poste', related_name='+'
+        Poste, related_name='rh_comparaisons_internes'
     )
 
     )
 
-    class Meta:
-        abstract = True
+reversion.register(PosteComparaison, format='xml')
 
 
 
 
-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):
+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.
     """
     Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
     Dossiers qu'il occupe ou a occupé de Postes.
@@ -470,6 +496,9 @@ class Employe(AUFMetadata):
     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.
     """
     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)
     prenom = models.CharField(u"prénom", max_length=255)
     # Identification
     nom = models.CharField(max_length=255)
     prenom = models.CharField(u"prénom", max_length=255)
@@ -512,6 +541,9 @@ class Employe(AUFMetadata):
         ref.Pays, to_field='code', db_column='pays',
         related_name='employes', null=True, blank=True
     )
         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
     nb_postes = models.IntegerField(u"nombre de postes", null=True, blank=True)
 
     # meta dématérialisation :  pour permettre le filtrage
     nb_postes = models.IntegerField(u"nombre de postes", null=True, blank=True)
@@ -524,6 +556,18 @@ class Employe(AUFMetadata):
     def __unicode__(self):
         return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
 
     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':
@@ -542,25 +586,70 @@ class Employe(AUFMetadata):
 
     def dossiers_passes(self):
         params = {KEY_STATUT: STATUT_INACTIF, }
 
     def dossiers_passes(self):
         params = {KEY_STATUT: STATUT_INACTIF, }
-        search = RechercheTemporelle(params, self.__class__)
+        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):
         params = {KEY_STATUT: STATUT_FUTUR, }
         search.purge_params(params)
         q = search.get_q_temporel(self.rh_dossiers)
         return self.rh_dossiers.filter(q)
 
     def dossiers_futurs(self):
         params = {KEY_STATUT: STATUT_FUTUR, }
-        search = RechercheTemporelle(params, self.__class__)
+        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):
         params = {KEY_STATUT: STATUT_ACTIF, }
         search.purge_params(params)
         q = search.get_q_temporel(self.rh_dossiers)
         return self.rh_dossiers.filter(q)
 
     def dossiers_encours(self):
         params = {KEY_STATUT: STATUT_ACTIF, }
-        search = RechercheTemporelle(params, self.__class__)
+        search = RechercheTemporelle(params, Dossier)
         search.purge_params(params)
         q = search.get_q_temporel(self.rh_dossiers)
         return self.rh_dossiers.filter(q)
 
         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)
+
+        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()
         for d in self.dossiers_encours():
     def postes_encours(self):
         postes_encours = set()
         for d in self.dossiers_encours():
@@ -573,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
@@ -580,13 +670,18 @@ class Employe(AUFMetadata):
             pass
         return poste
 
             pass
         return poste
 
-    prefix_implantation = "rh_dossiers__poste__implantation__region"
+    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()
+        ]
 
 
-    def get_regions(self):
-        regions = []
-        for d in self.dossiers.all():
-            regions.append(d.poste.implantation.region)
-        return regions
+reversion.register(Employe, format='xml', follow=[
+    'pieces', 'commentaires', 'ayantdroits'
+])
 
 
 class EmployePiece(models.Model):
 
 
 class EmployePiece(models.Model):
@@ -600,7 +695,8 @@ class EmployePiece(models.Model):
     )
     nom = models.CharField(max_length=255)
     fichier = models.FileField(
     )
     nom = models.CharField(max_length=255)
     fichier = models.FileField(
-        u"fichier", upload_to=employe_piece_dispatch, storage=storage_prive
+        u"fichier", upload_to=employe_piece_dispatch, storage=storage_prive,
+        max_length=255
     )
 
     class Meta:
     )
 
     class Meta:
@@ -611,16 +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):
     employe = models.ForeignKey(
 
 class EmployeCommentaire(Commentaire):
     employe = models.ForeignKey(
-        'Employe', db_column='employe', related_name='+'
+        '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'),
@@ -630,7 +730,7 @@ LIEN_PARENTE_CHOICES = (
 )
 
 
 )
 
 
-class AyantDroit(AUFMetadata):
+class AyantDroit(models.Model):
     """
     Personne en relation avec un Employe.
     """
     """
     Personne en relation avec un Employe.
     """
@@ -669,20 +769,25 @@ class AyantDroit(AUFMetadata):
     def __unicode__(self):
         return u'%s %s' % (self.nom.upper(), self.prenom, )
 
     def __unicode__(self):
         return u'%s %s' % (self.nom.upper(), self.prenom, )
 
-    prefix_implantation = "employe__dossiers__poste__implantation__region"
+    prefix_implantation = \
+            "employe__dossiers__poste__implantation__zone_administrative"
 
 
-    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):
     ayant_droit = models.ForeignKey(
 
 
 class AyantDroitCommentaire(Commentaire):
     ayant_droit = models.ForeignKey(
-        'AyantDroit', db_column='ayant_droit', related_name='+'
+        'AyantDroit', db_column='ayant_droit', related_name='commentaires'
     )
 
     )
 
+reversion.register(AyantDroitCommentaire, format='xml')
+
 
 ### DOSSIER
 
 
 ### DOSSIER
 
@@ -698,7 +803,7 @@ COMPTE_COMPTA_CHOICES = (
 )
 
 
 )
 
 
-class Dossier_(AUFMetadata, DevisableMixin):
+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é
     """
     Le Dossier regroupe les informations relatives à l'occupation
     d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
@@ -750,13 +855,25 @@ class Dossier_(AUFMetadata, DevisableMixin):
     )
 
     # Occupation du Poste par cet Employe (anciennement "mandat")
     )
 
     # Occupation du Poste par cet Employe (anciennement "mandat")
-    date_debut = models.DateField(u"date de début d'occupation de poste")
+    date_debut = models.DateField(
+        u"date de début d'occupation de poste", db_index=True
+    )
     date_fin = models.DateField(
     date_fin = models.DateField(
-        u"Date de fin d'occupation de poste", null=True, blank=True
+        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
@@ -780,10 +897,10 @@ class Dossier_(AUFMetadata, DevisableMixin):
             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"
+    prefix_implantation = "poste__implantation__zone_administrative"
 
 
-    def get_regions(self):
-        return [self.poste.implantation.region]
+    def get_zones_administratives(self):
+        return [self.poste.implantation.zone_administrative]
 
     def remunerations(self):
         key = "%s_remunerations" % self._meta.app_label
 
     def remunerations(self):
         key = "%s_remunerations" % self._meta.app_label
@@ -964,24 +1081,173 @@ class Dossier_(AUFMetadata, DevisableMixin):
             total += r.montant_euros()
         return total
 
             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é",
     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é")
+        related_name='rh_dossiers', verbose_name=u"employé"
+    )
     principal = models.BooleanField(
     principal = models.BooleanField(
-        u"Principal?", default=True,
+        u"dossier principal", default=True,
         help_text=(
             u"Ce dossier est pour le principal poste occupé par l'employé"
         )
     )
         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',
+        )
+    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):
@@ -989,13 +1255,10 @@ class DossierPiece_(models.Model):
     Documents relatifs au Dossier (à l'occupation de ce poste par employé).
     Ex.: Lettre de motivation.
     """
     Documents relatifs au Dossier (à l'occupation de ce poste par employé).
     Ex.: Lettre de motivation.
     """
-    dossier = models.ForeignKey(
-        '%s.Dossier' % app_context(),
-        db_column='dossier', related_name='%(app_label)s_dossierpieces'
-    )
     nom = models.CharField(max_length=255)
     fichier = models.FileField(
     nom = models.CharField(max_length=255)
     fichier = models.FileField(
-        upload_to=dossier_piece_dispatch, storage=storage_prive
+        upload_to=dossier_piece_dispatch, storage=storage_prive,
+        max_length=255
     )
 
     class Meta:
     )
 
     class Meta:
@@ -1007,29 +1270,24 @@ class DossierPiece_(models.Model):
 
 
 class DossierPiece(DossierPiece_):
 
 
 class DossierPiece(DossierPiece_):
-    pass
-
-
-class DossierCommentaire_(Commentaire):
     dossier = models.ForeignKey(
     dossier = models.ForeignKey(
-        '%s.Dossier' % app_context(), db_column='dossier', related_name='+'
+        Dossier, db_column='dossier', related_name='rh_dossierpieces'
     )
 
     )
 
-    class Meta:
-        abstract = True
+reversion.register(DossierPiece, format='xml')
 
 
+class DossierCommentaire(Commentaire):
+    dossier = models.ForeignKey(
+        Dossier, db_column='dossier', related_name='commentaires'
+    )
 
 
-class DossierCommentaire(DossierCommentaire_):
-    pass
+reversion.register(DossierCommentaire, format='xml')
 
 
 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()
 
     implantation = models.ForeignKey(
     objects = DossierComparaisonManager()
 
     implantation = models.ForeignKey(
@@ -1050,16 +1308,16 @@ class DossierComparaison_(models.Model, DevisableMixin):
 
 
 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
     type = models.ForeignKey(
 
     # Identification
     type = models.ForeignKey(
@@ -1072,8 +1330,7 @@ class RemunerationMixin(AUFMetadata):
         null=True, blank=True
     )
     montant = models.DecimalField(
         null=True, blank=True
     )
     montant = models.DecimalField(
-        null=True, blank=True,
-        default=0, max_digits=12, decimal_places=2
+        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='+')
 
     )  # Annuel (12 mois, 52 semaines, 364 jours?)
     devise = models.ForeignKey('Devise', db_column='devise', related_name='+')
 
@@ -1081,8 +1338,12 @@ class RemunerationMixin(AUFMetadata):
     commentaire = models.CharField(max_length=255, null=True, blank=True)
 
     # date_debut = anciennement date_effectif
     commentaire = models.CharField(max_length=255, null=True, blank=True)
 
     # date_debut = anciennement date_effectif
-    date_debut = models.DateField(u"date de début", null=True, blank=True)
-    date_fin = models.DateField(u"date de fin", null=True, blank=True)
+    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
@@ -1098,7 +1359,66 @@ class Remuneration_(RemunerationMixin, DevisableMixin):
     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_mois(self):
         return round(self.montant / 12, 2)
 
@@ -1122,37 +1442,35 @@ class Remuneration_(RemunerationMixin, DevisableMixin):
 
 
 class Remuneration(Remuneration_):
 
 
 class Remuneration(Remuneration_):
-    pass
-
+    dossier = models.ForeignKey(
+        Dossier, db_column='dossier', related_name='rh_remunerations'
+    )
 
 
-### CONTRATS
+reversion.register(Remuneration, format='xml')
 
 
-class ContratManager(NoDeleteManager):
-    def get_query_set(self):
-        return super(ContratManager, self).get_query_set() \
-                .select_related('dossier', 'dossier__poste')
 
 
+### CONTRATS
 
 
-class Contrat_(AUFMetadata):
+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()
     """
     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()
-    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',
         verbose_name=u'type de contrat', related_name='+'
     )
     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")
-    date_fin = models.DateField(u"date de fin", null=True, blank=True)
+    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,
     fichier = models.FileField(
         upload_to=contrat_dispatch, storage=storage_prive, null=True,
-        blank=True
+        blank=True, max_length=255
     )
 
     class Meta:
     )
 
     class Meta:
@@ -1166,80 +1484,16 @@ class Contrat_(AUFMetadata):
 
 
 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 CategorieEmploi(AUFMetadata):
+class CategorieEmploi(models.Model):
     """
     Catégorie utilisée dans la gestion des Postes.
     Catégorie supérieure à TypePoste.
     """
     Catégorie utilisée dans la gestion des Postes.
     Catégorie supérieure à TypePoste.
@@ -1247,15 +1501,34 @@ class CategorieEmploi(AUFMetadata):
     nom = models.CharField(max_length=255)
 
     class Meta:
     nom = models.CharField(max_length=255)
 
     class Meta:
-        ordering = ['nom']
+        ordering = ('nom',)
         verbose_name = u"catégorie d'emploi"
         verbose_name_plural = u"catégories d'emploi"
 
     def __unicode__(self):
         verbose_name = u"catégorie d'emploi"
         verbose_name_plural = u"catégories d'emploi"
 
     def __unicode__(self):
-        return u'%s' % (self.nom)
+        return self.nom
 
 
+reversion.register(CategorieEmploi, format='xml')
 
 
-class TypePoste(AUFMetadata):
+
+class FamilleProfessionnelle(models.Model):
+    """
+    Famille professionnelle d'un poste.
+    """
+    nom = models.CharField(max_length=100)
+
+    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.
     """
     """
     Catégorie de Poste.
     """
@@ -1268,6 +1541,10 @@ class TypePoste(AUFMetadata):
         CategorieEmploi, db_column='categorie_emploi', related_name='+',
         verbose_name=u"catégorie d'emploi"
     )
         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']
@@ -1277,35 +1554,40 @@ 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'),
     (u'Ponctuel', u'Ponctuel'),
 )
 
 NATURE_REMUNERATION_CHOICES = (
 TYPE_PAIEMENT_CHOICES = (
     (u'Régulier', u'Régulier'),
     (u'Ponctuel', u'Ponctuel'),
 )
 
 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):
+class TypeRemuneration(Archivable):
     """
     Catégorie de Remuneration.
     """
     """
     Catégorie de Remuneration.
     """
-    objects = TypeRemunerationManager()
+
+    objects = models.Manager()
+    sans_archives = ArchivableManager()
 
     nom = models.CharField(max_length=255)
     type_paiement = models.CharField(
         u"type de paiement", max_length=30, choices=TYPE_PAIEMENT_CHOICES
     )
 
     nom = models.CharField(max_length=255)
     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
     )
     nature_remuneration = models.CharField(
         u"nature de la rémunération", max_length=30,
         choices=NATURE_REMUNERATION_CHOICES
     )
-    archive = models.BooleanField(verbose_name=u"Archivé", default=False)
 
     class Meta:
         ordering = ['nom']
 
     class Meta:
         ordering = ['nom']
@@ -1313,14 +1595,12 @@ 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):
-        if self.archive:
-            archive = u"(archivé)"
-        else:
-            archive = ""
-        return u'%s %s' % (self.nom, archive)
+        return self.nom
+
+reversion.register(TypeRemuneration, format='xml')
 
 
 
 
-class TypeRevalorisation(AUFMetadata):
+class TypeRevalorisation(Archivable):
     """
     Justification du changement de la Remuneration.
     (Actuellement utilisé dans aucun traitement informatique.)
     """
     Justification du changement de la Remuneration.
     (Actuellement utilisé dans aucun traitement informatique.)
@@ -1335,27 +1615,24 @@ class TypeRevalorisation(AUFMetadata):
     def __unicode__(self):
         return u'%s' % (self.nom)
 
     def __unicode__(self):
         return u'%s' % (self.nom)
 
+reversion.register(TypeRevalorisation, format='xml')
 
 
-class Service(AUFMetadata):
+
+class Service(Archivable):
     """
     Unité administrative où les Postes sont rattachés.
     """
     """
     Unité administrative où les Postes sont rattachés.
     """
-    objects = ServiceManager()
-
-    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 = (
@@ -1364,7 +1641,7 @@ TYPE_ORGANISME_CHOICES = (
 )
 
 
 )
 
 
-class OrganismeBstg(AUFMetadata):
+class OrganismeBstg(models.Model):
     """
     Organisation d'où provient un Employe mis à disposition (MAD) de
     ou détaché (DET) à l'AUF à titre gratuit.
     """
     Organisation d'où provient un Employe mis à disposition (MAD) de
     ou détaché (DET) à l'AUF à titre gratuit.
@@ -1386,13 +1663,10 @@ 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):
+class Statut(Archivable):
     """
     Statut de l'Employe dans le cadre d'un Dossier particulier.
     """
     """
     Statut de l'Employe dans le cadre d'un Dossier particulier.
     """
@@ -1416,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'),
@@ -1433,7 +1709,7 @@ class ClassementManager(models.Manager):
     Ordonner les spcéfiquement les classements.
     """
     def get_query_set(self):
     Ordonner les spcéfiquement les classements.
     """
     def get_query_set(self):
-        qs = super(self.__class__, self).get_query_set()
+        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(select={
             'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
         })
@@ -1441,7 +1717,12 @@ class ClassementManager(models.Manager):
         return qs.all()
 
 
         return qs.all()
 
 
-class Classement_(AUFMetadata):
+class ClassementArchivableManager(ClassementManager,
+                                  ArchivableManager):
+    pass
+
+
+class Classement_(Archivable):
     """
     Éléments de classement de la
     "Grille générique de classement hiérarchique".
     """
     Éléments de classement de la
     "Grille générique de classement hiérarchique".
@@ -1452,12 +1733,13 @@ 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)
     echelon = models.IntegerField(u"échelon", blank=True, default=0)
     degre = models.IntegerField(u"degré", blank=True, default=0)
 
     # Identification
     type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
     echelon = models.IntegerField(u"échelon", blank=True, default=0)
     degre = models.IntegerField(u"degré", blank=True, default=0)
-    coefficient = models.FloatField(u"coefficient", default=0, null=True)
+    coefficient = models.FloatField(u"coefficient", blank=True, null=True)
 
     # Méta
     # annee # au lieu de date_debut et date_fin
 
     # Méta
     # annee # au lieu de date_debut et date_fin
@@ -1476,8 +1758,10 @@ class Classement_(AUFMetadata):
 class Classement(Classement_):
     __doc__ = Classement_.__doc__
 
 class Classement(Classement_):
     __doc__ = Classement_.__doc__
 
+reversion.register(Classement, format='xml')
+
 
 
-class TauxChange_(AUFMetadata):
+class TauxChange_(models.Model):
     """
     Taux de change de la devise vers l'euro (EUR)
     pour chaque année budgétaire.
     """
     Taux de change de la devise vers l'euro (EUR)
     pour chaque année budgétaire.
@@ -1492,6 +1776,7 @@ class TauxChange_(AUFMetadata):
         ordering = ['-annee', 'devise__code']
         verbose_name = u"Taux de change"
         verbose_name_plural = u"Taux de change"
         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)
@@ -1500,15 +1785,17 @@ class TauxChange_(AUFMetadata):
 class TauxChange(TauxChange_):
     __doc__ = TauxChange_.__doc__
 
 class TauxChange(TauxChange_):
     __doc__ = TauxChange_.__doc__
 
+reversion.register(TauxChange, format='xml')
 
 
-class ValeurPointManager(NoDeleteManager):
+
+class ValeurPointManager(models.Manager):
 
     def get_query_set(self):
         return super(ValeurPointManager, self).get_query_set() \
 
     def get_query_set(self):
         return super(ValeurPointManager, self).get_query_set() \
-                .select_related('devise', 'implantation')
+            .select_related('devise', 'implantation')
 
 
 
 
-class ValeurPoint_(AUFMetadata):
+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
     """
     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
@@ -1517,6 +1804,7 @@ class ValeurPoint_(AUFMetadata):
     salaire de base = coefficient * valeur du point de l'Implantation du Poste
     """
 
     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)
@@ -1532,6 +1820,7 @@ 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):
         return u'%s %s %s [%s] %s' % (
 
     def __unicode__(self):
         return u'%s %s %s [%s] %s' % (
@@ -1543,27 +1832,28 @@ class ValeurPoint_(AUFMetadata):
 class ValeurPoint(ValeurPoint_):
     __doc__ = ValeurPoint_.__doc__
 
 class ValeurPoint(ValeurPoint_):
     __doc__ = ValeurPoint_.__doc__
 
+reversion.register(ValeurPoint, format='xml')
+
 
 
-class Devise(AUFMetadata):
+class Devise(Archivable):
     """
     Devise monétaire.
     """
     """
     Devise monétaire.
     """
-    objects = DeviseManager()
-
-    archive = models.BooleanField(verbose_name=u"Archivé", default=False)
     code = models.CharField(max_length=10, unique=True)
     nom = models.CharField(max_length=255)
 
     class Meta:
         ordering = ['code']
     code = models.CharField(max_length=10, unique=True)
     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)
 
+reversion.register(Devise, format='xml')
+
 
 
-class TypeContrat(AUFMetadata):
+class TypeContrat(Archivable):
     """
     Type de contrat.
     """
     """
     Type de contrat.
     """
@@ -1578,12 +1868,18 @@ 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
 
 class ResponsableImplantationProxy(ref.Implantation):
 
 
 ### AUTRES
 
 class ResponsableImplantationProxy(ref.Implantation):
 
+    def save(self):
+        pass
+
     class Meta:
     class Meta:
+        managed = False
         proxy = True
         verbose_name = u"Responsable d'implantation"
         verbose_name_plural = u"Responsables d'implantation"
         proxy = True
         verbose_name = u"Responsable d'implantation"
         verbose_name_plural = u"Responsables d'implantation"
@@ -1610,3 +1906,223 @@ class ResponsableImplantation(models.Model):
         ordering = ['implantation__nom']
         verbose_name = "Responsable d'implantation"
         verbose_name_plural = "Responsables d'implantation"
         ordering = ['implantation__nom']
         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)
+
+