merge recrutement + bypass rh.Poste dependancy
[auf_rh_dae.git] / project / rh / models.py
index 59196a2..3ac9c10 100644 (file)
@@ -1,15 +1,13 @@
 # -=- encoding: utf-8 -=-
 
-from datetime import date
+import datetime
 
 from django.core.files.storage import FileSystemStorage
 from django.db import models
-from django.conf import settings
+import settings
 
-from auf.django.metadata.models import AUFMetadata
-from auf.django.metadata.managers import NoDeleteManager
 import datamaster_modeles.models as ref
-from validators import validate_date_passee
+
 
 # Constantes
 HELP_TEXT_DATE = "format: aaaa-mm-jj"
@@ -29,12 +27,30 @@ def dossier_piece_dispatch(instance, filename):
     path = "dossier/%s/%s" % (instance.dossier_id, filename)
     return path
 
-def employe_piece_dispatch(instance, filename):
-    path = "employe/%s/%s" % (instance.employe_id, filename)
-    return path
-
+# Abstracts
+class Metadata(models.Model):
+    """Méta-données AUF.
+    Metadata.actif = flag remplaçant la suppression.
+    actif == False : objet réputé supprimé.
+    """
+    actif = models.BooleanField(default=True)
+    date_creation = models.DateField(auto_now_add=True)
+    user_creation = models.ForeignKey('auth.User', 
+                            db_column='user_creation', related_name='+',
+                            null=True, blank=True)
+    date_modification = models.DateField(auto_now=True)
+    user_modification = models.ForeignKey('auth.User', 
+                            db_column='user_modification', related_name='+',
+                            null=True, blank=True)
+    date_desactivation = models.DateField(null=True, blank=True)
+    user_desactivation = models.ForeignKey('auth.User', 
+                            db_column='user_desactivation', related_name='+',
+                            null=True, blank=True)
+    
+    class Meta:
+        abstract = True
 
-class Commentaire(AUFMetadata):
+class Commentaire(Metadata):
     texte = models.TextField()
     owner = models.ForeignKey('auth.User', db_column='owner', related_name='+')
     
@@ -53,18 +69,11 @@ POSTE_APPEL_CHOICES = (
     ('externe', 'Externe'),
 )
 
-class PosteManager(NoDeleteManager):
-    def get_query_set(self):
-        return super(PosteManager, self).get_query_set().select_related('implantation')
-
-class Poste_(AUFMetadata):
+class Poste_(Metadata):
     """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.
     """
-
-    objects = PosteManager()
-
     # Identification
     nom = models.CharField(max_length=255, 
                             verbose_name="Titre du poste", )
@@ -76,34 +85,34 @@ class Poste_(AUFMetadata):
     type_poste = models.ForeignKey('TypePoste', db_column='type_poste',
                             related_name='+',
                             null=True)
-    service = models.ForeignKey('Service', db_column='service', null=True,
+    service = models.ForeignKey('Service', db_column='service', 
                             related_name='+',
                             verbose_name="Direction/Service/Pôle support",
                             default=1)  # default = Rectorat
     responsable = models.ForeignKey('Poste', db_column='responsable', 
-                            related_name='+', null=True,
+                            related_name='+',
                             verbose_name="Poste du responsable",
                             default=149)    # default = Recteur
                                 
     # Contrat
     regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
-                            default=REGIME_TRAVAIL_DEFAULT, null=True,
+                            default=REGIME_TRAVAIL_DEFAULT, 
                             verbose_name="Temps de travail", 
                             help_text="% du temps complet")
     regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
-                            decimal_places=2, null=True,
+                            decimal_places=2,
                             default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
                             verbose_name="Nb. heures par semaine")
 
     # Recrutement
-    local = models.NullBooleanField(verbose_name="Local", default=True, 
-                            null=True, blank=True)
-    expatrie = models.NullBooleanField(verbose_name="Expatrié", default=False, 
-                            null=True, blank=True)
-    mise_a_disposition = models.NullBooleanField(
+    local = models.BooleanField(verbose_name="Local", default=True, 
+                            blank=True)
+    expatrie = models.BooleanField(verbose_name="Expatrié", default=False, 
+                            blank=True)
+    mise_a_disposition = models.BooleanField(
                             verbose_name="Mise à disposition",
-                            null=True, default=False)
-    appel = models.CharField(max_length=10, null=True,
+                            default=False)
+    appel = models.CharField(max_length=10, 
                             verbose_name="Appel à candidature",
                             choices=POSTE_APPEL_CHOICES,
                             default='interne')
@@ -121,25 +130,25 @@ class Poste_(AUFMetadata):
     valeur_point_max = models.ForeignKey('ValeurPoint', 
                             db_column='valeur_point_max', related_name='+', 
                             null=True, blank=True)
-    devise_min = models.ForeignKey('Devise', db_column='devise_min', null=True,
+    devise_min = models.ForeignKey('Devise', db_column='devise_min', 
                             related_name='+', default=5)
-    devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
+    devise_max = models.ForeignKey('Devise', db_column='devise_max', 
                             related_name='+', default=5)
     salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, default=0)
+                            default=0)
     salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, default=0)
+                            default=0)
     indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, default=0)
+                            default=0)
     indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, default=0)
+                            default=0)
     autre_min = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, default=0)
+                            default=0)
     autre_max = models.DecimalField(max_digits=12, decimal_places=2,
-                            null=True, default=0)
+                            default=0)
 
     # Comparatifs de rémunération
-    devise_comparaison = models.ForeignKey('Devise', null=True,
+    devise_comparaison = models.ForeignKey('Devise', 
                             db_column='devise_comparaison', 
                             related_name='+',
                             default=5)
@@ -199,10 +208,6 @@ class Poste(Poste_):
     __doc__ = Poste_.__doc__
 
 
-class Poste(Poste_):
-    __doc__ = Poste_.__doc__
-
-
 POSTE_FINANCEMENT_CHOICES = (
     ('A', 'A - Frais de personnel'),
     ('B', 'B - Projet(s)-Titre(s)'),
@@ -256,13 +261,13 @@ class PosteComparaison(models.Model):
     De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
     """
     poste = models.ForeignKey('Poste', related_name='comparaisons_internes')
-    implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
+    implantation = models.ForeignKey(ref.Implantation, related_name='+', null=True, blank=True)
     nom = models.CharField(verbose_name="Poste", max_length=255, null=True, blank=True)
     montant = models.IntegerField(null=True)
     devise = models.ForeignKey("Devise", default=5, related_name='+', null=True, blank=True)
 
     def taux_devise(self):
-        liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
+        liste_taux = self.devise.tauxchange_set.order_by('-annee')
         if len(liste_taux) == 0:
             raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
         else:
@@ -288,7 +293,7 @@ SITUATION_CHOICES = (
     ('M', 'Marié'),
 )
 
-class Employe(AUFMetadata):
+class Employe(Metadata):
     """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de 
     Dossiers qu'il occupe ou a occupé de Postes.
     
@@ -307,7 +312,6 @@ class Employe(AUFMetadata):
                             verbose_name="Nationalité")
     date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
                             verbose_name="Date de naissance",
-                            validators=[validate_date_passee],
                             null=True, blank=True)
     genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
     
@@ -348,43 +352,6 @@ class Employe(AUFMetadata):
         if not nom_affichage:
             nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
         return nom_affichage
-        
-    def civilite(self):
-        civilite = u''
-        if self.genre.upper() == u'M':
-            civilite = u'M.'
-        elif self.genre.upper() == u'F':
-            civilite = u'Mme'
-        return civilite
-        
-    def url_photo(self):
-        """Retourne l'URL du service retournant la photo de l'Employe.
-        Équivalent reverse url 'rh_photo' avec id en param.
-        """
-        from django.core.urlresolvers import reverse
-        return reverse('rh_photo', kwargs={'id':self.id})
-               
-    def dossiers_passes(self):
-        today = date.today()
-        return self.dossiers.filter(date_fin__lt=today).order_by('-date_fin')
-        
-    def dossiers_futurs(self):
-        today = date.today()
-        return self.dossiers.filter(date_debut__gt=today).order_by('-date_fin')
-        
-    def dossiers_encours(self):
-        dossiers_p_f = self.dossiers_passes() | self.dossiers_futurs()
-        ids_dossiers_p_f = [d.id for d in dossiers_p_f]
-        return self.dossiers.exclude(id__in=ids_dossiers_p_f).order_by('-date_fin')
-        
-    def postes_encours(self):
-        postes_encours = set()
-        for d in self.dossiers_encours():
-            postes_encours.add(d.poste)
-        return postes_encours
-        
-    def poste_principal(self):
-        return self.dossiers_encours()[0].poste
 
 class EmployePiece(models.Model):
     """Documents relatifs à un employé.
@@ -394,7 +361,7 @@ class EmployePiece(models.Model):
                             related_name='+')
     nom = models.CharField(verbose_name="Nom", max_length=255)
     fichier = models.FileField(verbose_name="Fichier", 
-                            upload_to=employe_piece_dispatch, 
+                            upload_to=dossier_piece_dispatch, 
                             storage=storage_prive)
 
     class Meta:
@@ -415,7 +382,7 @@ LIEN_PARENTE_CHOICES = (
     ('Fils', 'Fils'),
 )
 
-class AyantDroit(AUFMetadata):
+class AyantDroit(Metadata):
     """Personne en relation avec un Employe.
     """
     # Identification
@@ -431,7 +398,6 @@ class AyantDroit(AUFMetadata):
                             verbose_name="Nationalité")
     date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
                             verbose_name="Date de naissance",
-                            validators=[validate_date_passee],
                             null=True, blank=True)
     genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
     
@@ -476,7 +442,8 @@ COMPTE_COMPTA_CHOICES = (
     ('aucun', 'Aucun'),
 )
 
-class Dossier_(AUFMetadata):
+
+class Dossier_(Metadata):
     """Le Dossier regroupe les informations relatives à l'occupation
     d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
     par un Employe.
@@ -487,11 +454,11 @@ class Dossier_(AUFMetadata):
     """
     # Identification
     employe = models.ForeignKey('Employe', db_column='employe', 
-                            related_name='dossiers',
+                            related_name='+',
                             verbose_name="Employé")
-    poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
-    statut = models.ForeignKey('Statut', related_name='+', default=3,
-                            null=True)
+    poste = models.ForeignKey('Poste', db_column='poste', 
+                            related_name='+', editable=False)
+    statut = models.ForeignKey('Statut', related_name='+', default=3)
     organisme_bstg = models.ForeignKey('OrganismeBstg', 
                             db_column='organisme_bstg',
                             related_name='+',
@@ -504,20 +471,20 @@ class Dossier_(AUFMetadata):
     # Recrutement
     remplacement = models.BooleanField(default=False)
     statut_residence = models.CharField(max_length=10, default='local', 
-                            verbose_name="Statut", null=True,
+                            verbose_name="Statut",
                             choices=STATUT_RESIDENCE_CHOICES)
    
     # 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,
+    regime_travail = models.DecimalField(max_digits=12, 
                             decimal_places=2,
                             default=REGIME_TRAVAIL_DEFAULT,
                             verbose_name="Régime de travail",
                             help_text="% du temps complet")
     regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
-                            decimal_places=2, null=True,
+                            decimal_places=2, 
                             default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
                             verbose_name="Nb. heures par semaine")
 
@@ -535,7 +502,7 @@ class Dossier_(AUFMetadata):
     
     class Meta:
         abstract = True
-        ordering = ['employe__nom', ]
+        ordering = ['employe__nom_affichage', 'employe__nom', 'poste__nom']
         verbose_name = "Dossier"
         verbose_name_plural = "Dossiers"
         
@@ -576,14 +543,14 @@ class DossierComparaison(models.Model):
     Photo d'une comparaison salariale au moment de l'embauche.
     """
     dossier = models.ForeignKey('Dossier', related_name='comparaisons')
-    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)
     devise = models.ForeignKey('Devise', default=5, related_name='+', null=True, blank=True)
 
     def taux_devise(self):
-        liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
+        liste_taux = self.devise.tauxchange_set.order_by('-annee')
         if len(liste_taux) == 0:
             raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
         else:
@@ -595,7 +562,7 @@ class DossierComparaison(models.Model):
 
 ### RÉMUNÉRATION
     
-class RemunerationMixin(AUFMetadata):
+class RemunerationMixin(Metadata):
     # Identification
     dossier = models.ForeignKey('Dossier', db_column='dossier',
                         related_name='%(app_label)s_%(class)s_remunerations')
@@ -666,20 +633,12 @@ class Remuneration(Remuneration_):
 
 
 ### CONTRATS
-
-class ContratManager(NoDeleteManager):
-    def get_query_set(self):
-        return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
-
         
-class Contrat(AUFMetadata):
+class Contrat(Metadata):
     """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('Dossier', db_column='dossier', 
                             related_name='+')
     type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat', 
@@ -704,7 +663,7 @@ class Contrat(AUFMetadata):
 
 ### ÉVÉNEMENTS
 
-class Evenement_(AUFMetadata):
+class Evenement_(Metadata):
     """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).
@@ -760,17 +719,10 @@ class EvenementRemuneration_(RemunerationMixin):
 class EvenementRemuneration(EvenementRemuneration_):
     __doc__ = EvenementRemuneration_.__doc__
 
-    class Meta:
-        abstract = True
-
-
-class EvenementRemuneration(EvenementRemuneration_):
-    __doc__ = EvenementRemuneration_.__doc__
-
 
 ### RÉFÉRENCES RH 
 
-class FamilleEmploi(AUFMetadata):
+class FamilleEmploi(Metadata):
     """Catégorie utilisée dans la gestion des Postes.
     Catégorie supérieure à TypePoste.
     """
@@ -784,7 +736,7 @@ class FamilleEmploi(AUFMetadata):
     def __unicode__(self):
         return u'%s' % (self.nom)
 
-class TypePoste(AUFMetadata):
+class TypePoste(Metadata):
     """Catégorie de Poste.
     """
     nom = models.CharField(max_length=255)
@@ -820,7 +772,7 @@ NATURE_REMUNERATION_CHOICES = (
     ('Traitement', 'Traitement'),
 )
 
-class TypeRemuneration(AUFMetadata):
+class TypeRemuneration(Metadata):
     """Catégorie de Remuneration.
     """
     nom = models.CharField(max_length=255)
@@ -839,7 +791,7 @@ class TypeRemuneration(AUFMetadata):
     def __unicode__(self):
         return u'%s' % (self.nom)
         
-class TypeRevalorisation(AUFMetadata):
+class TypeRevalorisation(Metadata):
     """Justification du changement de la Remuneration.
     (Actuellement utilisé dans aucun traitement informatique.)
     """
@@ -853,7 +805,7 @@ class TypeRevalorisation(AUFMetadata):
     def __unicode__(self):
         return u'%s' % (self.nom)
     
-class Service(AUFMetadata):
+class Service(Metadata):
     """Unité administrative où les Postes sont rattachés.
     """
     nom = models.CharField(max_length=255)
@@ -872,7 +824,7 @@ TYPE_ORGANISME_CHOICES = (
     ('DET', 'Détachement'),
 )
 
-class OrganismeBstg(AUFMetadata):
+class OrganismeBstg(Metadata):
     """Organisation d'où provient un Employe mis à disposition (MAD) de 
     ou détaché (DET) à l'AUF à titre gratuit.
     
@@ -893,7 +845,7 @@ class OrganismeBstg(AUFMetadata):
     def __unicode__(self):
         return u'%s (%s)' % (self.nom, self.get_type_display())
 
-class Statut(AUFMetadata):
+class Statut(Metadata):
     """Statut de l'Employe dans le cadre d'un Dossier particulier.
     """
     # Identification
@@ -920,7 +872,7 @@ TYPE_CLASSEMENT_CHOICES = (
 )
 
 
-class Classement_(AUFMetadata):
+class Classement_(Metadata):
     """Éléments de classement de la 
     "Grille générique de classement hiérarchique".
     
@@ -933,8 +885,7 @@ class Classement_(AUFMetadata):
     type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
     echelon = models.IntegerField(verbose_name="Échelon")
     degre = models.IntegerField(verbose_name="Degré")
-    coefficient = models.FloatField(default=0, verbose_name="Coéfficient",
-                                    null=True)
+    coefficient = models.FloatField(default=0, verbose_name="Coéfficient")
     # Méta
     # annee # au lieu de date_debut et date_fin
     commentaire = models.TextField(null=True, blank=True)
@@ -953,7 +904,7 @@ class Classement(Classement_):
     __doc__ = Classement_.__doc__
 
 
-class TauxChange_(AUFMetadata):
+class TauxChange_(Metadata):
     """Taux de change de la devise vers l'euro (EUR) 
     pour chaque année budgétaire.
     """
@@ -976,23 +927,16 @@ class TauxChange_(AUFMetadata):
 class TauxChange(TauxChange_):
     __doc__ = TauxChange_.__doc__
 
-class ValeurPointManager(NoDeleteManager):
-    def get_query_set(self):
-        return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
-
 
-class ValeurPoint_(AUFMetadata):
+class ValeurPoint_(Metadata):
     """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
     """
-    
-    objects = ValeurPointManager()
-
-    valeur = models.FloatField(null=True)
-    devise = models.ForeignKey('Devise', db_column='devise', null=True,
+    valeur = models.FloatField()
+    devise = models.ForeignKey('Devise', db_column='devise', 
                             related_name='+', default=5)
     implantation = models.ForeignKey(ref.Implantation, 
                             db_column='implantation',
@@ -1001,24 +945,11 @@ class ValeurPoint_(AUFMetadata):
     annee = models.IntegerField()
 
     class Meta:
-        ordering = ['-annee', 'implantation__nom']
         abstract = True
+        ordering = ['annee']
         verbose_name = "Valeur du point"
         verbose_name_plural = "Valeurs du point"
 
-    # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
-    def get_tauxchange_courant(self):
-        """
-        Recherche le taux courant associé à la valeur d'un point.
-        Tous les taux de l'année courante sont chargés, pour optimiser un
-        affichage en liste. (On pourrait probablement améliorer le manager pour
-        lui greffer le taux courant sous forme de JOIN)
-        """
-        for tauxchange in self.tauxchange:
-            if tauxchange.implantation_id == self.implantation_id:
-                return tauxchange
-        return None
-
     def __unicode__(self):
         return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
 
@@ -1027,7 +958,7 @@ class ValeurPoint(ValeurPoint_):
     __doc__ = ValeurPoint_.__doc__
 
 
-class Devise(AUFMetadata):
+class Devise(Metadata):
     """Devise monétaire.
     """
     code =  models.CharField(max_length=10, unique=True)
@@ -1041,7 +972,7 @@ class Devise(AUFMetadata):
     def __unicode__(self):
         return u'%s - %s' % (self.code, self.nom)
 
-class TypeContrat(AUFMetadata):
+class TypeContrat(Metadata):
     """Type de contrat.
     """
     nom = models.CharField(max_length=255)
@@ -1058,7 +989,7 @@ class TypeContrat(AUFMetadata):
         
 ### AUTRES
 
-class ResponsableImplantation(AUFMetadata):
+class ResponsableImplantation(Metadata):
     """Le responsable d'une implantation. 
     Anciennement géré sur le Dossier du responsable.
     """
@@ -1076,7 +1007,3 @@ class ResponsableImplantation(AUFMetadata):
         ordering = ['implantation__nom']
         verbose_name = "Responsable d'implantation"
         verbose_name_plural = "Responsables d'implantation"
-
-def dossier_piece_dispatch(instance, filename):
-    path = "dossier/%s/%s" % (instance.dossier_id, filename)
-    return path