# -=- encoding: utf-8 -=-
+from django.core.files.storage import FileSystemStorage
from django.db import models
-from datamaster_modeles.models import Pays, Implantation
+from django.conf import settings
+from auf.django.metadata.models import AUFMetadata
+from auf.django.metadata.managers import NoDeleteManager
+import datamaster_modeles.models as ref
+from validators import validate_date_passee
+
+# Constantes
+HELP_TEXT_DATE = "format: aaaa-mm-jj"
+REGIME_TRAVAIL_DEFAULT = 100.00
+REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = 35.00
+
+
+# Upload de fichiers
+storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
+ base_url=settings.PRIVE_MEDIA_URL)
+
+def poste_piece_dispatch(instance, filename):
+ path = "poste/%s/%s" % (instance.poste_id, filename)
+ return path
+
+def dossier_piece_dispatch(instance, filename):
+ path = "dossier/%s/%s" % (instance.dossier_id, filename)
+ return path
+
+
+class Commentaire(AUFMetadata):
+ texte = models.TextField()
+ owner = models.ForeignKey('auth.User', db_column='owner', related_name='+')
+
+ class Meta:
+ abstract = True
+ ordering = ['-date_creation']
+
+ def __unicode__(self):
+ return u'%s' % (self.texte)
+
+
+### POSTE
+
+POSTE_APPEL_CHOICES = (
+ ('interne', 'Interne'),
+ ('externe', 'Externe'),
+)
+
+class PosteManager(NoDeleteManager):
+ def get_query_set(self):
+ return super(PosteManager, self).get_query_set().select_related('implantation')
+
+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éé.
+ Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
+ """
+
+ objects = PosteManager()
+
+ # Identification
+ nom = models.CharField(max_length=255,
+ verbose_name = u"Titre du poste", )
+ nom_feminin = models.CharField(max_length=255,
+ verbose_name = u"Titre du poste (au féminin)",
+ null=True)
+ implantation = models.ForeignKey(ref.Implantation,
+ db_column='implantation', related_name='+')
+ type_poste = models.ForeignKey('TypePoste', db_column='type_poste',
+ related_name='+',
+ null=True)
+ service = models.ForeignKey('Service', db_column='service', null=True,
+ related_name='+',
+ verbose_name = u"Direction/Service/Pôle support",
+ default=1) # default = Rectorat
+ responsable = models.ForeignKey('Poste', db_column='responsable',
+ related_name='+', null=True,
+ verbose_name = u"Poste du responsable",
+ default=149) # default = Recteur
+
+ # Contrat
+ regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
+ default=REGIME_TRAVAIL_DEFAULT, null=True,
+ verbose_name = u"Temps de travail",
+ help_text="% du temps complet")
+ regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
+ decimal_places=2, null=True,
+ default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
+ verbose_name = u"Nb. heures par semaine")
+
+ # Recrutement
+ local = models.NullBooleanField(verbose_name = u"Local", default=True,
+ null=True, blank=True)
+ expatrie = models.NullBooleanField(verbose_name = u"Expatrié", default=False,
+ null=True, blank=True)
+ mise_a_disposition = models.NullBooleanField(
+ verbose_name = u"Mise à disposition",
+ null=True, default=False)
+ appel = models.CharField(max_length=10, null=True,
+ verbose_name = u"Appel à candidature",
+ choices=POSTE_APPEL_CHOICES,
+ default='interne')
+
+ # Rémunération
+ classement_min = models.ForeignKey('Classement',
+ db_column='classement_min', related_name='+',
+ null=True, blank=True)
+ classement_max = models.ForeignKey('Classement',
+ db_column='classement_max', related_name='+',
+ null=True, blank=True)
+ valeur_point_min = models.ForeignKey('ValeurPoint',
+ db_column='valeur_point_min', related_name='+',
+ null=True, blank=True)
+ 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,
+ related_name='+', default=5)
+ devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
+ related_name='+', default=5)
+ salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, default=0)
+ salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, default=0)
+ indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, default=0)
+ indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, default=0)
+ autre_min = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, default=0)
+ autre_max = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, default=0)
+
+ # Comparatifs de rémunération
+ devise_comparaison = models.ForeignKey('Devise', null=True,
+ db_column='devise_comparaison',
+ related_name='+',
+ default=5)
+ comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, blank=True)
+ comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, blank=True)
+ comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, blank=True)
+ comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, blank=True)
+ comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, blank=True)
+ comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, blank=True)
+ comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, blank=True)
+ comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, blank=True)
+ comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, blank=True)
+ comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
+ null=True, blank=True)
+
+ # Justification
+ justification = models.TextField(null=True, blank=True)
+
+ # Autres Metadata
+ date_validation = models.DateTimeField(null=True, blank=True) # de dae
+ date_debut = models.DateField(verbose_name="Date de début",
+ help_text=HELP_TEXT_DATE,
+ null=True, blank=True)
+ date_fin = models.DateField(verbose_name="Date de fin",
+ help_text=HELP_TEXT_DATE,
+ null=True, blank=True)
+
+ class Meta:
+ abstract = True
+ ordering = ['implantation__nom', 'nom']
+ verbose_name = u"Poste"
+ verbose_name_plural = u"Postes"
+
+ def __unicode__(self):
+ representation = u'%s - %s [%s]' % (self.implantation, self.nom,
+ self.id)
+ if self.is_vacant():
+ representation = representation + u' (vacant)'
+ return representation
+
+ def is_vacant(self):
+ # TODO : si existe un dossier actif pour ce poste, return False
+ # self.dossier_set.all() fonctionne pas
+ return False
+
+
+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)'),
+ ('C', 'C - Autre')
+)
+
+
+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')
+ 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)")
+ commentaire = models.TextField(
+ help_text="Spécifiez la source de financement.")
+
+ class Meta:
+ abstract = True
+ ordering = ['type']
+
+ def __unicode__(self):
+ return u'%s : %s %' % (self.type, self.pourcentage)
+
+
+class PosteFinancement(PosteFinancement_):
+ __doc__ = PosteFinancement_.__doc__
+
+
+class PostePiece(models.Model):
+ """Documents relatifs au Poste.
+ Ex.: Description de poste
+ """
+ poste = models.ForeignKey('Poste', db_column='poste',
+ related_name='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:
+ ordering = ['nom']
+
+ def __unicode__(self):
+ return u'%s' % (self.nom)
+
+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="+")
+ 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)
+
+ 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))
+ else:
+ return liste_taux[0].taux
+
+ def montant_euros(self):
+ return round(float(self.montant) * float(self.taux_devise()), 2)
+
+
+class PosteCommentaire(Commentaire):
+ poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
+
+
+### EMPLOYÉ/PERSONNE
GENRE_CHOICES = (
('M', 'Homme'),
('M', 'Marié'),
)
-class Employe(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
+class Employe(AUFMetadata):
+ """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
+ Dossiers qu'il occupe ou a occupé de Postes.
+
+ Cette classe aurait pu avantageusement s'appeler Personne car la notion
+ d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
+ """
+ # Identification
nom = models.CharField(max_length=255)
- prenom = models.CharField(max_length=255)
- nationalite = models.ForeignKey('datamaster_modeles.Pays', to_field='code', related_name='nationalite', db_column='nationalite')
- date_naissance = models.DateField(null=True, blank=True)
- #Infos personnelles
- genre = models.CharField(max_length=1, choices=GENRE_CHOICES, null=True, blank=True)
- situation_famille = models.CharField(max_length=1, choices=SITUATION_CHOICES, null=True, blank=True)
- date_entree = models.DateField(null=True, blank=True) #devrait pas être là
- #Coordonnées
- tel_domicile = models.CharField(max_length=255, null=True, blank=True)
- tel_cellulaire = models.CharField(max_length=255, null=True, blank=True)
+ prenom = models.CharField(max_length=255, verbose_name = u"Prénom")
+ nom_affichage = models.CharField(max_length=255,
+ verbose_name = u"Nom d'affichage",
+ null=True, blank=True)
+ nationalite = models.ForeignKey(ref.Pays, to_field='code',
+ db_column='nationalite',
+ related_name='employes_nationalite',
+ verbose_name = u"Nationalité")
+ date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
+ verbose_name = u"Date de naissance",
+ validators=[validate_date_passee],
+ null=True, blank=True)
+ genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
+
+ # Infos personnelles
+ situation_famille = models.CharField(max_length=1,
+ choices=SITUATION_CHOICES,
+ verbose_name = u"Situation familiale",
+ null=True, blank=True)
+ date_entree = models.DateField(verbose_name = u"Date d'entrée à l'AUF",
+ help_text=HELP_TEXT_DATE,
+ null=True, blank=True)
+
+ # Coordonnées
+ tel_domicile = models.CharField(max_length=255,
+ verbose_name = u"Tél. domicile",
+ null=True, blank=True)
+ tel_cellulaire = models.CharField(max_length=255,
+ verbose_name = u"Tél. cellulaire",
+ null=True, blank=True)
adresse = models.CharField(max_length=255, null=True, blank=True)
- no_rue = models.CharField(max_length=255, null=True, blank=True)
ville = models.CharField(max_length=255, null=True, blank=True)
province = models.CharField(max_length=255, null=True, blank=True)
code_postal = models.CharField(max_length=255, null=True, blank=True)
- pays = models.ForeignKey('datamaster_modeles.Pays', to_field='code', null=True, blank=True, related_name='pays', db_column='pays')
- #Métas
- date_creation = models.DateField(auto_now_add=True)
- date_maj = models.DateField(auto_now=True)
- commentaire = models.TextField(null=True, blank=True)
-
-TYPE_DOSSIER_CHOICES = (
- ('2', 'Local'),
- ('1', 'Expatrié'),
-)
+ pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
+ related_name='employes',
+ null=True, blank=True)
+
+ class Meta:
+ ordering = ['nom_affichage','nom','prenom']
+ verbose_name = u"Employé"
+ verbose_name_plural = u"Employés"
+
+ def __unicode__(self):
+ return u'%s' % (self.get_nom())
+
+ def get_nom(self):
+ nom_affichage = self.nom_affichage
+ 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
+
+class EmployePiece(models.Model):
+ """Documents relatifs à un employé.
+ Ex.: CV...
+ """
+ employe = models.ForeignKey('Employe', db_column='employe',
+ related_name='+')
+ nom = models.CharField(verbose_name = u"Nom", max_length=255)
+ fichier = models.FileField(verbose_name = u"Fichier",
+ upload_to=dossier_piece_dispatch,
+ storage=storage_prive)
+
+ class Meta:
+ ordering = ['nom']
+
+ def __unicode__(self):
+ return u'%s' % (self.nom)
+
+class EmployeCommentaire(Commentaire):
+ employe = models.ForeignKey('Employe', db_column='employe',
+ related_name='+')
-class Dossier(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
- code = models.CharField(max_length=10, unique=True)
- employe = models.ForeignKey('Employe', db_column='employe')
- #Postes
- poste1 = models.ForeignKey('Poste', db_column='poste1', related_name='poste1')
- implantation1 = models.ForeignKey('datamaster_modeles.Implantation', db_column='implantation1', related_name='implantation1')
- complement1 = models.TextField(null=True, blank=True)
- responsable_implantation1 = models.IntegerField()
- poste2 = models.ForeignKey('Poste', db_column='poste2', related_name='poste2', blank=True, null=True)
- implantation2 = models.ForeignKey('datamaster_modeles.Implantation', db_column='implantation2', related_name='implantation2')
- complement2 = models.TextField(null=True, blank=True)
- responsable_implantation2 = models.IntegerField()
- #Relations
- service = models.ForeignKey('Service', db_column='service')
- responsable = models.ForeignKey('Employe', db_column='responsable', related_name='responsable')
- remplacement_de = models.ForeignKey('Employe', db_column='remplacement_de', related_name='remplacement_de')
- type = models.CharField(max_length=1, choices=TYPE_DOSSIER_CHOICES)
- statut = models.ForeignKey('Statut', db_column='statut')
- organisme_bstg = models.ForeignKey('OrganismeBstg', db_column='organisme_bstg')
- #Rémunération
- classement = models.ForeignKey('Classement', db_column='classement')
- regime_travail = models.IntegerField()
- #Mandat
- mandat_date_debut = models.DateField()
- mandat_date_fin = models.DateField()
- #Contrat
- contrat_date_debut = models.DateField()
- contrat_date_fin = models.DateField()
- type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat')
- #Meta
- date_creation = models.DateField(auto_now_add=True)
- date_maj = models.DateField(auto_now=True)
- commentaire = models.TextField(null=True, blank=True)
LIEN_PARENTE_CHOICES = (
('Conjoint', 'Conjoint'),
('Fils', 'Fils'),
)
-class AyantDroit(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
+class AyantDroit(AUFMetadata):
+ """Personne en relation avec un Employe.
+ """
+ # Identification
nom = models.CharField(max_length=255)
- prenom = models.CharField(max_length=255)
- #Relation
- employe = models.ForeignKey('Employe', db_column='employe', related_name='employe')
- lien_parente = models.CharField(max_length=10, choices=LIEN_PARENTE_CHOICES, null=True, blank=True)
- #Méta
- commentaire = models.TextField(null=True, blank=True)
- actif = models.BooleanField()
+ prenom = models.CharField(max_length=255,
+ verbose_name = u"Prénom",)
+ nom_affichage = models.CharField(max_length=255,
+ verbose_name = u"Nom d'affichage",
+ null=True, blank=True)
+ nationalite = models.ForeignKey(ref.Pays, to_field='code',
+ db_column='nationalite',
+ related_name='ayantdroits_nationalite',
+ verbose_name = u"Nationalité")
+ date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
+ verbose_name = u"Date de naissance",
+ validators=[validate_date_passee],
+ null=True, blank=True)
+ genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
+ # Relation
+ employe = models.ForeignKey('Employe', db_column='employe',
+ related_name='ayantdroits',
+ verbose_name = u"Employé")
+ lien_parente = models.CharField(max_length=10,
+ choices=LIEN_PARENTE_CHOICES,
+ verbose_name = u"Lien de parenté",
+ null=True, blank=True)
+
+ class Meta:
+ ordering = ['nom_affichage']
+ verbose_name = u"Ayant droit"
+ verbose_name_plural = u"Ayants droit"
+
+ def __unicode__(self):
+ return u'%s' % (self.get_nom())
+
+ def get_nom(self):
+ nom_affichage = self.nom_affichage
+ if not nom_affichage:
+ nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
+ return nom_affichage
+
+class AyantDroitCommentaire(Commentaire):
+ ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
+ related_name='+')
+
+
+### DOSSIER
+
+STATUT_RESIDENCE_CHOICES = (
+ ('local', 'Local'),
+ ('expat', 'Expatrié'),
+)
+
+COMPTE_COMPTA_CHOICES = (
+ ('coda', 'CODA'),
+ ('scs', 'SCS'),
+ ('aucun', 'Aucun'),
+)
+
+class Dossier_(AUFMetadata):
+ """Le Dossier regroupe les informations relatives à l'occupation
+ d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
+ par un Employe.
-class Remuneration(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
- dossier = models.ForeignKey('Dossier', db_column='dossier')
- type = models.ForeignKey('TypeRemuneration', db_column='type')
- type_revalorisation = models.ForeignKey('TypeRevalorisation', db_column='type_revalorisation')
- montant = models.FloatField()
- devise = models.ForeignKey('Devise', to_field='code', db_column='devise')
- date_effective = models.DateField()
- pourcentage = models.IntegerField()
- #Méta
- date_creation = models.DateField(auto_now_add=True)
- user_creation = models.IntegerField() #User ou employé
- desactivation = models.BooleanField() #
- date_desactivation = models.DateField()
- user_desactivation = models.IntegerField() #User ou employé
- annule = models.BooleanField()
- date_annule = models.DateField()
- user_annule = models.IntegerField() #User ou employé
+ Plusieurs Contrats peuvent être associés au Dossier.
+ 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é")
+ poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
+ statut = models.ForeignKey('Statut', related_name='+', default=3,
+ null=True)
+ organisme_bstg = models.ForeignKey('OrganismeBstg',
+ db_column='organisme_bstg',
+ related_name='+',
+ verbose_name = u"Organisme",
+ help_text="Si détaché (DET) ou \
+ mis à disposition (MAD), \
+ préciser l'organisme.",
+ null=True, blank=True)
+
+ # Recrutement
+ remplacement = models.BooleanField(default=False)
+ statut_residence = models.CharField(max_length=10, default='local',
+ verbose_name = u"Statut", null=True,
+ 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,
+ decimal_places=2,
+ default=REGIME_TRAVAIL_DEFAULT,
+ verbose_name = u"Régime de travail",
+ help_text="% du temps complet")
+ regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
+ decimal_places=2, null=True,
+ default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
+ verbose_name = u"Nb. heures par semaine")
+
+ # Occupation du Poste par cet Employe (anciennement "mandat")
+ date_debut = models.DateField(verbose_name = u"Date de début d'occupation \
+ de poste",
+ help_text=HELP_TEXT_DATE)
+ date_fin = models.DateField(verbose_name = u"Date de fin d'occupation \
+ de poste",
+ help_text=HELP_TEXT_DATE,
+ null=True, blank=True)
+
+ # Comptes
+ # TODO?
+
+ class Meta:
+ abstract = True
+ ordering = ['employe__nom', ]
+ verbose_name = "Dossier"
+ verbose_name_plural = "Dossiers"
+
+ def __unicode__(self):
+ poste = self.poste.nom
+ if self.employe.genre == 'F':
+ poste = self.poste.nom_feminin
+ return u'%s - %s' % (self.employe, poste)
+
+
+class Dossier(Dossier_):
+ __doc__ = Dossier_.__doc__
+
+
+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='+')
+ 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:
+ ordering = ['nom']
+
+ def __unicode__(self):
+ return u'%s' % (self.nom)
+
+class DossierCommentaire(Commentaire):
+ dossier = models.ForeignKey('Dossier', db_column='dossier',
+ related_name='+')
+
+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)
+ 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)
+ 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:
+ return liste_taux[0].taux
+
+ def montant_euros(self):
+ return round(float(self.montant) * float(self.taux_devise()), 2)
+
+
+### RÉMUNÉRATION
-class FamilleEmploi(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
+class RemunerationMixin(AUFMetadata):
+ # Identification
+ dossier = models.ForeignKey('Dossier', db_column='dossier',
+ related_name='%(app_label)s_%(class)s_remunerations')
+ type = models.ForeignKey('TypeRemuneration', db_column='type',
+ related_name='+',
+ verbose_name = u"Type de rémunération")
+ type_revalorisation = models.ForeignKey('TypeRevalorisation',
+ db_column='type_revalorisation',
+ related_name='+',
+ verbose_name = u"Type de revalorisation",
+ null=True, blank=True)
+ montant = models.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)
+ # commentaire = precision
+ commentaire = models.CharField(max_length=255, null=True, blank=True)
+ # date_debut = anciennement date_effectif
+ date_debut = models.DateField(help_text=HELP_TEXT_DATE,
+ verbose_name = u"Date de début",
+ null=True, blank=True)
+ date_fin = models.DateField(help_text=HELP_TEXT_DATE,
+ verbose_name = u"Date de fin",
+ null=True, blank=True)
+
+ class Meta:
+ abstract = True
+ ordering = ['type__nom', '-date_fin']
+
+ def __unicode__(self):
+ return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
+
+class Remuneration_(RemunerationMixin):
+ """Structure de rémunération (données budgétaires) en situation normale
+ pour un Dossier. Si un Evenement existe, utiliser la structure de
+ rémunération EvenementRemuneration de cet événement.
+ """
+
+ def montant_mois(self):
+ return round(self.montant / 12, 2)
+
+ def taux_devise(self):
+ return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
+
+ def montant_euro(self):
+ return round(float(self.montant) / float(self.taux_devise()), 2)
+
+ def montant_euro_mois(self):
+ return round(self.montant_euro() / 12, 2)
+
+ def __unicode__(self):
+ try:
+ devise = self.devise.code
+ except:
+ devise = "???"
+ return "%s %s" % (self.montant, devise)
+
+ class Meta:
+ abstract = True
+ verbose_name = u"Rémunération"
+ verbose_name_plural = u"Rémunérations"
+
+
+class Remuneration(Remuneration_):
+ __doc__ = Remuneration_.__doc__
+
+
+### CONTRATS
+
+class ContratManager(NoDeleteManager):
+ def get_query_set(self):
+ return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
+
+
+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='+')
+ type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
+ related_name='+',
+ verbose_name = u"Type de contrat")
+ date_debut = models.DateField(help_text=HELP_TEXT_DATE,
+ verbose_name = u"Date de début")
+ date_fin = models.DateField(help_text=HELP_TEXT_DATE,
+ verbose_name = u"Date de fin",
+ null=True, blank=True)
+
+ class Meta:
+ ordering = ['dossier__employe__nom_affichage']
+ verbose_name = u"Contrat"
+ verbose_name_plural = u"Contrats"
+
+ def __unicode__(self):
+ return u'%s - %s' % (self.dossier, self.id)
+
+# TODO? class ContratPiece(models.Model):
+
+
+### É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(help_text=HELP_TEXT_DATE,
+ verbose_name = u"Date de début")
+ date_fin = models.DateField(help_text=HELP_TEXT_DATE,
+ 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__
+
+
+### RÉFÉRENCES RH
+
+class FamilleEmploi(AUFMetadata):
+ """Catégorie utilisée dans la gestion des Postes.
+ Catégorie supérieure à TypePoste.
+ """
nom = models.CharField(max_length=255)
- #Méta
- actif = models.BooleanField()
+
+ class Meta:
+ ordering = ['nom']
+ verbose_name = u"Famille d'emploi"
+ verbose_name_plural = u"Familles d'emploi"
+
+ def __unicode__(self):
+ return u'%s' % (self.nom)
-class TypePoste(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
+class TypePoste(AUFMetadata):
+ """Catégorie de Poste.
+ """
nom = models.CharField(max_length=255)
- nom_feminin = models.CharField(max_length=255)
- description = models.CharField(max_length=255)
- is_responsable = models.BooleanField()
- famille_emploi = models.ForeignKey('FamilleEmploi', db_column='famille_emploi')
- #Méta
- date_modification = models.DateField(auto_now=True)
- actif = models.BooleanField()
+ nom_feminin = models.CharField(max_length=255,
+ verbose_name = u"Nom féminin")
+
+ is_responsable = models.BooleanField(default=False,
+ verbose_name = u"Poste de responsabilité")
+ famille_emploi = models.ForeignKey('FamilleEmploi',
+ db_column='famille_emploi',
+ related_name='+',
+ verbose_name = u"Famille d'emploi")
+ class Meta:
+ ordering = ['nom']
+ verbose_name = u"Type de poste"
+ verbose_name_plural = u"Types de poste"
+
def __unicode__(self):
- return u'%s' % self.nom
+ return u'%s' % (self.nom)
TYPE_PAIEMENT_CHOICES = (
('Accessoire', 'Accessoire'),
('Charges', 'Charges'),
('Indemnité', 'Indemnité'),
- ('RAS', 'RAS'),
+ ('RAS', 'Rémunération autre source'),
('Traitement', 'Traitement'),
)
-class TypeRemuneration(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
+class TypeRemuneration(AUFMetadata):
+ """Catégorie de Remuneration.
+ """
nom = models.CharField(max_length=255)
- type_paiement = models.CharField(max_length=30, choices=TYPE_PAIEMENT_CHOICES)
- nature_remuneration = models.CharField(max_length=30, choices=NATURE_REMUNERATION_CHOICES)
- #Méta
- actif = models.BooleanField()
+ type_paiement = models.CharField(max_length=30,
+ choices=TYPE_PAIEMENT_CHOICES,
+ verbose_name = u"Type de paiement")
+ nature_remuneration = models.CharField(max_length=30,
+ choices=NATURE_REMUNERATION_CHOICES,
+ verbose_name = u"Nature de la rémunération")
+
+ class Meta:
+ ordering = ['nom']
+ verbose_name = u"Type de rémunération"
+ verbose_name_plural = u"Types de rémunération"
+
+ def __unicode__(self):
+ return u'%s' % (self.nom)
-class TypeRevalorisation(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
+class TypeRevalorisation(AUFMetadata):
+ """Justification du changement de la Remuneration.
+ (Actuellement utilisé dans aucun traitement informatique.)
+ """
nom = models.CharField(max_length=255)
- #Méta
- actif = models.BooleanField()
-PROPORTION_CHOICES = (
- ('0.5', '0.5'),
- ('1', '1'),
-)
-
-class Poste(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
- implantation = models.ForeignKey('datamaster_modeles.Implantation',
- db_column='implantation', related_name='+')
- type_poste = models.ForeignKey('TypePoste', db_column='type_poste')
- proportion = models.CharField(max_length=10, choices=PROPORTION_CHOICES)
- #(sert à quoi?) renommer "regime_travail" ou autre? convertir data en % (data * 100; ex: 1 = 100%)
- #Méta
- date_modification = models.DateField(auto_now=True)
- actif = models.BooleanField()
+ class Meta:
+ ordering = ['nom']
+ verbose_name = u"Type de revalorisation"
+ verbose_name_plural = u"Types de revalorisation"
def __unicode__(self):
- return u'%s - %s' % (self.implantation, self.type_poste.nom)
-
-
-class Service(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
+ return u'%s' % (self.nom)
+
+class Service(AUFMetadata):
+ """Unité administrative où les Postes sont rattachés.
+ """
nom = models.CharField(max_length=255)
- #Méta
- actif = models.BooleanField()
+
+ class Meta:
+ ordering = ['nom']
+ verbose_name = u"Service"
+ verbose_name_plural = u"Services"
def __unicode__(self):
- return u'%s' % self.nom
+ return u'%s' % (self.nom)
TYPE_ORGANISME_CHOICES = (
('DET', 'Détachement'),
)
-class OrganismeBstg(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
+class OrganismeBstg(AUFMetadata):
+ """Organisation d'où provient un Employe mis à disposition (MAD) de
+ ou détaché (DET) à l'AUF à titre gratuit.
+
+ (BSTG = bien et service à titre gratuit.)
+ """
nom = models.CharField(max_length=255)
type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
- #Méta
- actif = models.BooleanField()
+ pays = models.ForeignKey(ref.Pays, to_field='code',
+ db_column='pays',
+ related_name='organismes_bstg',
+ null=True, blank=True)
-CONTRAT_CATEGORIE_CHOICES= (
- ('A', 'A'),
- ('C', 'C'),
-)
-class Statut(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
+ class Meta:
+ ordering = ['type', 'nom']
+ verbose_name = u"Organisme BSTG"
+ verbose_name_plural = u"Organismes BSTG"
+
+ def __unicode__(self):
+ return u'%s (%s)' % (self.nom, self.get_type_display())
+
+class Statut(AUFMetadata):
+ """Statut de l'Employe dans le cadre d'un Dossier particulier.
+ """
+ # Identification
code = models.CharField(max_length=25, unique=True)
nom = models.CharField(max_length=255)
- type_contrat_categorie = models.CharField(max_length=10, choices=CONTRAT_CATEGORIE_CHOICES)
- #CHOICES A, C (veut dire quoi?) voir TypeContrat.categorie
- #Méta
- actif = models.BooleanField()
+
+ class Meta:
+ ordering = ['code']
+ verbose_name = u"Statut d'employé"
+ verbose_name_plural = u"Statuts d'employé"
+
+ def __unicode__(self):
+ return u'%s : %s' % (self.code, self.nom)
+
TYPE_CLASSEMENT_CHOICES = (
- ('S', 'S'),
- ('T', 'T'),
+ ('S', 'S -Soutien'),
+ ('T', 'T - Technicien'),
+ ('P', 'P - Professionel'),
+ ('C', 'C - Cadre'),
+ ('D', 'D - Direction'),
+ ('SO', 'SO - Sans objet [expatriés]'),
+ ('HG', 'HG - Hors grille [direction]'),
)
-class Classement(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
+
+
+class Classement_(AUFMetadata):
+ """Éléments de classement de la
+ "Grille générique de classement hiérarchique".
+
+ Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
+ classement dans la grille. Le classement donne le coefficient utilisé dans:
+
+ salaire de base = coefficient * valeur du point de l'Implantation du Poste
+ """
+ # Identification
type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
- echelon = models.IntegerField()
- degre = models.IntegerField()
- coefficient = models.FloatField()
- #Méta
- commentaire = models.TextField(null=True, blank=True)
- date_modification = models.DateField(auto_now=True)
- actif = models.BooleanField()
+ echelon = models.IntegerField(verbose_name = u"Échelon")
+ degre = models.IntegerField(verbose_name = u"Degré")
+ coefficient = models.FloatField(default=0, verbose_name = u"Coéfficient",
+ null=True)
+ # Méta
+ # annee # au lieu de date_debut et date_fin
+ commentaire = models.TextField(null=True, blank=True)
+
+ class Meta:
+ abstract = True
+ ordering = ['type','echelon','degre','coefficient']
+ verbose_name = u"Classement"
+ verbose_name_plural = u"Classements"
def __unicode__(self):
return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
self.coefficient)
+class Classement(Classement_):
+ __doc__ = Classement_.__doc__
-class ValeurPoint(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
- valeur = models.FloatField()
- implantation = models.ForeignKey('datamaster_modeles.Implantation', db_column='implantation')
- #Méta
- annee = models.IntegerField()
+class TauxChange_(AUFMetadata):
+ """Taux de change de la devise vers l'euro (EUR)
+ pour chaque année budgétaire.
+ """
+ # Identification
+ devise = models.ForeignKey('Devise', db_column='devise',
+ related_name='+')
+ annee = models.IntegerField(verbose_name = u"Année")
+ taux = models.FloatField(verbose_name = u"Taux vers l'euro")
+
+ class Meta:
+ abstract = True
+ ordering = ['-annee', 'devise__code']
+ verbose_name = u"Taux de change"
+ verbose_name_plural = u"Taux de change"
+
def __unicode__(self):
- return u'%s (%s-%s)' % (self.valeur, self.implantation_id, self.annee)
+ return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
+
+
+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):
+ """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).
-class TauxChange(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
- devise = models.ForeignKey('Devise', to_field='code', db_column='devise')
+ 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,
+ related_name='+', default=5)
+ implantation = models.ForeignKey(ref.Implantation,
+ db_column='implantation',
+ related_name='%(app_label)s_valeur_point')
+ # Méta
annee = models.IntegerField()
- taux = models.FloatField()
- #Relations
- implantation = models.ForeignKey('datamaster_modeles.Implantation', db_column='implantation')
+
+ class Meta:
+ ordering = ['-annee', 'implantation__nom']
+ abstract = True
+ 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)
-class Devise(models.Model):
- id = models.IntegerField(primary_key=True)
+class ValeurPoint(ValeurPoint_):
+ __doc__ = ValeurPoint_.__doc__
+
+
+class Devise(AUFMetadata):
+ """Devise monétaire.
+ """
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"
+
def __unicode__(self):
return u'%s - %s' % (self.code, self.nom)
-
-class TypeContrat(models.Model):
- #Identification
- id = models.IntegerField(primary_key=True)
+class TypeContrat(AUFMetadata):
+ """Type de contrat.
+ """
nom = models.CharField(max_length=255)
- nom_long = models.CharField(max_length=255) #description
- categorie = models.CharField(max_length=10, choices=CONTRAT_CATEGORIE_CHOICES)
- #Méta
- actif = models.BooleanField()
+ nom_long = models.CharField(max_length=255)
+
+ class Meta:
+ ordering = ['nom']
+ verbose_name = u"Type de contrat"
+ verbose_name_plural = u"Types de contrat"
+
+ def __unicode__(self):
+ return u'%s' % (self.nom)
+
+
+### AUTRES
+class ResponsableImplantation(AUFMetadata):
+ """Le responsable d'une implantation.
+ Anciennement géré sur le Dossier du responsable.
+ """
+ employe = models.ForeignKey('Employe', db_column='employe',
+ related_name='+',
+ null=True, blank=True)
+ implantation = models.ForeignKey(ref.Implantation,
+ db_column='implantation', related_name='+',
+ unique=True)
+
+ def __unicode__(self):
+ return u'%s : %s' % (self.implantation, self.employe)
+
+ class Meta:
+ ordering = ['implantation__nom']
+ verbose_name = u"Responsable d'implantation"
+ verbose_name_plural = u"Responsables d'implantation"