merge dev with import RH
[auf_rh_dae.git] / project / rh / models.py
index aea60a2..b914e84 100644 (file)
@@ -1,5 +1,6 @@
 # -=- encoding: utf-8 -=-
 
+import datetime
 from datetime import date
 from decimal import Decimal
 
@@ -11,13 +12,22 @@ 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 dae.managers import SecurityManager
+from managers import PosteManager, DossierManager, DossierComparaisonManager, PosteComparaisonManager
+
+
+
+# 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]
+
 
 # Constantes
 REGIME_TRAVAIL_DEFAULT = Decimal('100.00')
 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = Decimal('35.00')
 
-
 # Upload de fichiers
 storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
                             base_url=settings.PRIVE_MEDIA_URL)
@@ -54,19 +64,6 @@ POSTE_APPEL_CHOICES = (
     ('externe', 'Externe'),
 )
 
-class PosteManager(SecurityManager):
-    """
-    Chargement de tous les objets FK existants sur chaque QuerySet.
-    """
-    prefixe_implantation = "implantation__region"
-
-    def get_query_set(self):
-        fkeys = (
-            'implantation',
-            'type_poste',
-        )
-        return super(PosteManager, self).get_query_set().select_related(*fkeys).all()
-
 class Poste_(AUFMetadata):
     """Un Poste est un emploi (job) à combler dans une implantation.
     Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
@@ -88,13 +85,11 @@ class Poste_(AUFMetadata):
                             null=True)
     service = models.ForeignKey('Service', db_column='service',
                             related_name='+',
-                            verbose_name = u"Direction/Service/Pôle support",
-                            default=1)  # default = Rectorat
-    responsable = models.ForeignKey('Poste', db_column='responsable',
-                            related_name='+',
-                            verbose_name = u"Poste du responsable",
-                            default=149)    # default = Recteur
-
+                            verbose_name = u"Direction/Service/Pôle support", )
+    responsable = models.ForeignKey('Poste', db_column='responsable', 
+                            related_name='+', null=True,
+                            verbose_name = u"Poste du responsable", )
+                                
     # Contrat
     regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
                             default=REGIME_TRAVAIL_DEFAULT, null=True,
@@ -208,7 +203,7 @@ class Poste_(AUFMetadata):
         Généralement, retourne une liste d'un élément.
         Si poste inoccupé, retourne liste vide.
         """
-        return [d.employe for d in self.dossiers.filter(actif=True, supprime=False) \
+        return [d.employe for d in self.rh_dossiers.filter(actif=True, supprime=False) \
             .exclude(date_fin__lt=date.today())]
 
     prefix_implantation = "implantation__region"
@@ -231,8 +226,7 @@ class PosteFinancement_(models.Model):
     """Pour un Poste, structure d'informations décrivant comment on prévoit
     financer ce Poste.
     """
-    poste = models.ForeignKey('Poste', db_column='poste',
-                            related_name='%(app_label)s_financements')
+    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,
             help_text="ex.: 33.33 % (décimale avec point)")
@@ -244,54 +238,73 @@ class PosteFinancement_(models.Model):
         ordering = ['type']
 
     def __unicode__(self):
-        return u'%s : %s %' % (self.type, self.pourcentage)
+        return u'%s : %s %%' % (self.type, self.pourcentage)
 
 
 class PosteFinancement(PosteFinancement_):
-    __doc__ = PosteFinancement_.__doc__
+    pass
 
 
-class PostePiece(models.Model):
+class PostePiece_(models.Model):
     """Documents relatifs au Poste.
     Ex.: Description de poste
     """
-    poste = models.ForeignKey('Poste', db_column='poste',
-                            related_name='pieces')
+    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)
 
     class Meta:
+        abstract = True
         ordering = ['nom']
 
     def __unicode__(self):
         return u'%s' % (self.nom)
 
-class PosteComparaison(models.Model):
+class PostePiece(PostePiece_):
+    pass
+
+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')
+    poste = models.ForeignKey('%s.Poste' % app_context(), related_name='%(app_label)s_comparaisons_internes')
+    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)
     montant = models.IntegerField(null=True)
     devise = models.ForeignKey("Devise", default=5, related_name='+', null=True, blank=True)
 
+    class Meta:
+        abstract = True
+
     def taux_devise(self):
-        liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.implantation)
-        if len(liste_taux) == 0:
-            raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.implantation))
+        if self.devise.code == "EUR":
+            return 1
+        annee = self.poste.date_debut.year
+        taux = [tc.taux for tc in TauxChange.objects.filter(devise=self.devise, annee=annee)]
+        taux = set(taux)
+        if len(taux) != 1:
+            raise Exception(u"Le taux de la devise %s n'a pas n'existe pas pour %s ou il existe plusieurs taux pour la même année" % (self.devise.id, annee))
         else:
-            return liste_taux[0].taux
+            return list(taux)[0]
 
     def montant_euros(self):
         return round(float(self.montant) * float(self.taux_devise()), 2)
 
+class PosteComparaison(PosteComparaison_):
+    pass
+
+class PosteCommentaire_(Commentaire):
+    poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='+')
 
-class PosteCommentaire(Commentaire):
-    poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
+    class Meta:
+        abstract = True
 
+class PosteCommentaire(PosteCommentaire_):
+    pass
 
 ### EMPLOYÉ/PERSONNE
 
@@ -540,12 +553,10 @@ class Dossier_(AUFMetadata):
     Une structure de Remuneration est rattachée au Dossier. Un Poste pour
     lequel aucun Dossier n'existe est un poste vacant.
     """
-    # Identification
-    employe = models.ForeignKey('Employe', db_column='employe',
-                            related_name='dossiers',
-                            verbose_name=u"Employé")
+
+    objects = DossierManager()
+
     # TODO: OneToOne ??
-    poste = models.ForeignKey('Poste', db_column='poste', related_name='dossiers')
     statut = models.ForeignKey('Statut', related_name='+', default=3,
                             null=True)
     organisme_bstg = models.ForeignKey('OrganismeBstg',
@@ -617,7 +628,7 @@ class Dossier_(AUFMetadata):
 
 
     def remunerations(self):
-        return self.rh_remuneration_remunerations.all().order_by('date_debut')
+        return self.rh_remunerations.all().order_by('date_debut')
 
     def get_salaire(self):
         try:
@@ -627,58 +638,77 @@ class Dossier_(AUFMetadata):
 
 class Dossier(Dossier_):
     __doc__ = Dossier_.__doc__
+    poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='%(app_label)s_dossiers')
+    employe = models.ForeignKey('Employe', db_column='employe', 
+                            related_name='%(app_label)s_dossiers',
+                            verbose_name=u"Employé")
 
 
-class DossierPiece(models.Model):
+class DossierPiece_(models.Model):
     """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
     Ex.: Lettre de motivation.
     """
-    dossier = models.ForeignKey('Dossier', db_column='dossier',
-                            related_name='+')
+    dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
     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)
 
     class Meta:
+        abstract = True
         ordering = ['nom']
 
     def __unicode__(self):
         return u'%s' % (self.nom)
 
-class DossierCommentaire(Commentaire):
-    dossier = models.ForeignKey('Dossier', db_column='dossier',
-                            related_name='+')
+class DossierPiece(DossierPiece_):
+    pass
+
+class DossierCommentaire_(Commentaire):
+    dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
+    class Meta:
+        abstract = True
+
+class DossierCommentaire(DossierCommentaire_):
+    pass
 
-class DossierComparaison(models.Model):
+class DossierComparaison_(models.Model):
     """
     Photo d'une comparaison salariale au moment de l'embauche.
     """
-    dossier = models.ForeignKey('Dossier', related_name='comparaisons')
+    dossier = models.ForeignKey('%s.Dossier' % app_context(), related_name='%(app_label)s_comparaisons')
+    objects = DossierComparaisonManager()
+
     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)
 
+    class Meta:
+        abstract = True
+
     def taux_devise(self):
-        liste_taux = self.devise.tauxchange_set.order_by('-annee').filter(implantation=self.dossier.poste.implantation)
-        if len(liste_taux) == 0:
-            raise Exception(u"La devise %s n'a pas de taux pour l'implantation %s" % (self.devise, self.dossier.poste.implantation))
+        annee = self.dossier.poste.date_debut.year
+        taux = [tc.taux for tc in TauxChange.objects.filter(devise=self.devise, annee=annee)]
+        taux = set(taux)
+        if len(taux) != 1:
+            raise Exception(u"Le taux de la devise %s n'a pas n'existe pas pour %s ou il existe plusieurs taux pour la même année" % (self.devise.id, annee))
         else:
-            return liste_taux[0].taux
+            return list(taux)[0]
 
     def montant_euros(self):
         return round(float(self.montant) * float(self.taux_devise()), 2)
 
+class DossierComparaison(DossierComparaison_):
+    pass
 
 ### RÉMUNÉRATION
 
 class RemunerationMixin(AUFMetadata):
+    dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_remunerations')
     # Identification
-    dossier = models.ForeignKey('Dossier', db_column='dossier',
-                        related_name='%(app_label)s_%(class)s_remunerations')
-    type = models.ForeignKey('TypeRemuneration', db_column='type',
+    type = models.ForeignKey('TypeRemuneration', db_column='type', 
                             related_name='+',
                             verbose_name = u"Type de rémunération")
     type_revalorisation = models.ForeignKey('TypeRevalorisation',
@@ -689,9 +719,7 @@ class RemunerationMixin(AUFMetadata):
     montant = models.FloatField(null=True, blank=True,
                             default=0)
                             # Annuel (12 mois, 52 semaines, 364 jours?)
-    devise = models.ForeignKey('Devise', to_field='id',
-                            db_column='devise', related_name='+',
-                            default=5)
+    devise = models.ForeignKey('Devise', db_column='devise', related_name='+', default=5)
     # commentaire = precision
     commentaire = models.CharField(max_length=255, null=True, blank=True)
     # date_debut = anciennement date_effectif
@@ -717,10 +745,24 @@ class Remuneration_(RemunerationMixin):
         return round(self.montant / 12, 2)
 
     def taux_devise(self):
-        return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
+        if self.devise.code == "EUR":
+            return 1
+
+        annee = datetime.datetime.now().year
+        if self.date_debut is not None:
+            annee = self.date_debut.year
+        if self.dossier.poste.date_debut is not None:
+            annee = self.dossier.poste.date_debut
+
+        taux = [tc.taux for tc in TauxChange.objects.filter(devise=self.devise_id, annee=annee)]
+        taux = set(taux)
+        if len(taux) != 1:
+            raise Exception(u"Le taux de la devise %s n'a pas n'existe pas pour %s ou il existe plusieurs taux pour la même année" % (self.devise.id, annee))
+        else:
+            return list(taux)[0]
 
     def montant_euro(self):
-        return round(float(self.montant) / float(self.taux_devise()), 2)
+        return round(float(self.montant) * float(self.taux_devise()), 2)
 
     def montant_euro_mois(self):
         return round(self.montant_euro() / 12, 2)
@@ -739,7 +781,7 @@ class Remuneration_(RemunerationMixin):
 
 
 class Remuneration(Remuneration_):
-    __doc__ = Remuneration_.__doc__
+    pass
 
 
 ### CONTRATS
@@ -748,18 +790,15 @@ 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_(AUFMetadata):
     """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='contrats')
-    type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
+    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")
@@ -767,6 +806,7 @@ class Contrat(AUFMetadata):
                             null=True, blank=True)
 
     class Meta:
+        abstract = True
         ordering = ['dossier__employe__nom_affichage']
         verbose_name = u"Contrat"
         verbose_name_plural = u"Contrats"
@@ -774,71 +814,73 @@ class Contrat(AUFMetadata):
     def __unicode__(self):
         return u'%s - %s' % (self.dossier, self.id)
 
-# TODO? class ContratPiece(models.Model):
-
+class Contrat(Contrat_):
+    pass
+        
 
 ### É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('Dossier', 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__
+#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):
 
 
 ### RÉFÉRENCES RH
@@ -1083,19 +1125,6 @@ class ValeurPoint_(AUFMetadata):
         verbose_name = u"Valeur du point"
         verbose_name_plural = u"Valeurs du point"
 
-    # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
-    def get_tauxchange_courant(self):
-        """
-        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)