1 # -=- encoding: utf-8 -=-
4 from datetime
import date
5 from decimal
import Decimal
8 from auf
.django
.emploi
.models
import \
9 GENRE_CHOICES
, SITUATION_CHOICES
# devrait plutot être dans references
10 from auf
.django
.references
import models
as ref
11 from django
.core
.files
.storage
import FileSystemStorage
12 from django
.db
import models
13 from django
.db
.models
import Q
14 from django
.conf
import settings
16 from project
.rh
.change_list
import \
17 RechercheTemporelle
, KEY_STATUT
, STATUT_ACTIF
, STATUT_INACTIF
, \
19 from project
.rh
.managers
import PosteManager
, DossierManager
, EmployeManager
, \
20 DossierComparaisonManager
, \
21 PosteComparaisonManager
, \
22 TypeRemunerationManager
, \
25 from project
.rh
.validators
import validate_date_passee
27 # import pour relocaliser le modèle selon la convention (models.py pour
29 from project
.rh
.historique
import ModificationTraite
32 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
33 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
34 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
35 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
= \
36 "Saisir le nombre d'heure de travail à temps complet (100%), " \
37 "sans tenir compte du régime de travail"
40 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
41 base_url
=settings
.PRIVE_MEDIA_URL
)
44 def poste_piece_dispatch(instance
, filename
):
45 path
= "%s/poste/%s/%s" % (
46 instance
._meta
.app_label
, instance
.poste_id
, filename
51 def dossier_piece_dispatch(instance
, filename
):
52 path
= "%s/dossier/%s/%s" % (
53 instance
._meta
.app_label
, instance
.dossier_id
, filename
58 def employe_piece_dispatch(instance
, filename
):
59 path
= "%s/employe/%s/%s" % (
60 instance
._meta
.app_label
, instance
.employe_id
, filename
65 def contrat_dispatch(instance
, filename
):
66 path
= "%s/contrat/%s/%s" % (
67 instance
._meta
.app_label
, instance
.dossier_id
, filename
72 class DateActiviteMixin(models
.Model
):
74 Mixin pour mettre à jour l'activité d'un modèle
78 date_creation
= models
.DateTimeField(auto_now_add
=True,
79 null
=True, blank
=True,
80 verbose_name
=u
"Date de création",)
81 date_modification
= models
.DateTimeField(auto_now
=True,
82 null
=True, blank
=True,
83 verbose_name
=u
"Date de modification",)
86 class ArchivableManager(models
.Manager
):
88 def get_query_set(self
):
89 return super(ArchivableManager
, self
).get_query_set() \
90 .filter(archive
=False)
93 class Archivable(models
.Model
):
94 archive
= models
.BooleanField(u
'archivé', default
=False)
96 objects
= ArchivableManager()
97 avec_archives
= models
.Manager()
103 class DevisableMixin(object):
105 def get_annee_pour_taux_devise(self
):
106 return datetime
.datetime
.now().year
108 def taux_devise(self
, devise
=None):
114 if devise
.code
== "EUR":
117 annee
= self
.get_annee_pour_taux_devise()
120 for tc
in TauxChange
.objects
.filter(devise
=devise
, annee
=annee
)
126 u
"Pas de taux pour %s en %s" % (devise
.code
, annee
)
130 raise Exception(u
"Il existe plusieurs taux de %s en %s" %
131 (devise
.code
, annee
))
135 def montant_euros(self
):
137 taux
= self
.taux_devise()
142 return int(round(float(self
.montant
) * float(taux
), 2))
145 class Commentaire(models
.Model
):
146 texte
= models
.TextField()
147 owner
= models
.ForeignKey(
148 'auth.User', db_column
='owner', related_name
='+',
149 verbose_name
=u
"Commentaire de"
151 date_creation
= models
.DateTimeField(
152 u
'date', auto_now_add
=True, blank
=True, null
=True
157 ordering
= ['-date_creation']
159 def __unicode__(self
):
160 return u
'%s' % (self
.texte
)
165 POSTE_APPEL_CHOICES
= (
166 ('interne', 'Interne'),
167 ('externe', 'Externe'),
171 class Poste_( DateActiviteMixin
, models
.Model
,):
173 Un Poste est un emploi (job) à combler dans une implantation.
174 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
175 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
178 objects
= PosteManager()
181 nom
= models
.CharField(u
"Titre du poste", max_length
=255)
182 nom_feminin
= models
.CharField(
183 u
"Titre du poste (au féminin)", max_length
=255, null
=True
185 implantation
= models
.ForeignKey(
187 help_text
=u
"Taper le nom de l'implantation ou sa région",
188 db_column
='implantation', related_name
='+'
190 type_poste
= models
.ForeignKey(
191 'TypePoste', db_column
='type_poste',
192 help_text
=u
"Taper le nom du type de poste", related_name
='+',
193 null
=True, verbose_name
=u
"type de poste"
195 service
= models
.ForeignKey(
196 'Service', db_column
='service', related_name
='%(app_label)s_postes',
197 verbose_name
=u
"direction/service/pôle support", null
=True
199 responsable
= models
.ForeignKey(
200 'Poste', db_column
='responsable',
201 related_name
='+', null
=True,
202 help_text
=u
"Taper le nom du poste ou du type de poste",
203 verbose_name
=u
"Poste du responsable"
207 regime_travail
= models
.DecimalField(
208 u
"temps de travail", max_digits
=12, decimal_places
=2,
209 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
210 help_text
="% du temps complet"
212 regime_travail_nb_heure_semaine
= models
.DecimalField(
213 u
"nb. heures par semaine", max_digits
=12, decimal_places
=2,
214 null
=True, default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
215 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
219 local
= models
.NullBooleanField(
220 u
"local", default
=True, null
=True, blank
=True
222 expatrie
= models
.NullBooleanField(
223 u
"expatrié", default
=False, null
=True, blank
=True
225 mise_a_disposition
= models
.NullBooleanField(
226 u
"mise à disposition", null
=True, default
=False
228 appel
= models
.CharField(
229 u
"Appel à candidature", max_length
=10, null
=True,
230 choices
=POSTE_APPEL_CHOICES
, default
='interne'
234 classement_min
= models
.ForeignKey(
235 'Classement', db_column
='classement_min', related_name
='+',
236 null
=True, blank
=True
238 classement_max
= models
.ForeignKey(
239 'Classement', db_column
='classement_max', related_name
='+',
240 null
=True, blank
=True
242 valeur_point_min
= models
.ForeignKey(
244 help_text
=u
"Taper le code ou le nom de l'implantation",
245 db_column
='valeur_point_min', related_name
='+', null
=True,
248 valeur_point_max
= models
.ForeignKey(
250 help_text
=u
"Taper le code ou le nom de l'implantation",
251 db_column
='valeur_point_max', related_name
='+', null
=True,
254 devise_min
= models
.ForeignKey(
255 'Devise', db_column
='devise_min', null
=True, related_name
='+'
257 devise_max
= models
.ForeignKey(
258 'Devise', db_column
='devise_max', null
=True, related_name
='+'
260 salaire_min
= models
.DecimalField(
261 max_digits
=12, decimal_places
=2, default
=0,
263 salaire_max
= models
.DecimalField(
264 max_digits
=12, decimal_places
=2, default
=0,
266 indemn_min
= models
.DecimalField(
267 max_digits
=12, decimal_places
=2, default
=0,
269 indemn_max
= models
.DecimalField(
270 max_digits
=12, decimal_places
=2, default
=0,
272 autre_min
= models
.DecimalField(
273 max_digits
=12, decimal_places
=2, default
=0,
275 autre_max
= models
.DecimalField(
276 max_digits
=12, decimal_places
=2, default
=0,
279 # Comparatifs de rémunération
280 devise_comparaison
= models
.ForeignKey(
281 'Devise', null
=True, blank
=True, db_column
='devise_comparaison',
284 comp_locale_min
= models
.DecimalField(
285 max_digits
=12, decimal_places
=2, null
=True, blank
=True
287 comp_locale_max
= models
.DecimalField(
288 max_digits
=12, decimal_places
=2, null
=True, blank
=True
290 comp_universite_min
= models
.DecimalField(
291 max_digits
=12, decimal_places
=2, null
=True, blank
=True
293 comp_universite_max
= models
.DecimalField(
294 max_digits
=12, decimal_places
=2, null
=True, blank
=True
296 comp_fonctionpub_min
= models
.DecimalField(
297 max_digits
=12, decimal_places
=2, null
=True, blank
=True
299 comp_fonctionpub_max
= models
.DecimalField(
300 max_digits
=12, decimal_places
=2, null
=True, blank
=True
302 comp_ong_min
= models
.DecimalField(
303 max_digits
=12, decimal_places
=2, null
=True, blank
=True
305 comp_ong_max
= models
.DecimalField(
306 max_digits
=12, decimal_places
=2, null
=True, blank
=True
308 comp_autre_min
= models
.DecimalField(
309 max_digits
=12, decimal_places
=2, null
=True, blank
=True
311 comp_autre_max
= models
.DecimalField(
312 max_digits
=12, decimal_places
=2, null
=True, blank
=True
316 justification
= models
.TextField(null
=True, blank
=True)
319 date_debut
= models
.DateField(
320 u
"date de début", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True,
323 date_fin
= models
.DateField(
324 u
"date de fin", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True,
330 ordering
= ['implantation__nom', 'nom']
331 verbose_name
= u
"Poste"
332 verbose_name_plural
= u
"Postes"
335 def __unicode__(self
):
336 representation
= u
'%s - %s [%s]' % (
337 self
.implantation
, self
.nom
, self
.id
339 return representation
341 prefix_implantation
= "implantation__region"
343 def get_regions(self
):
344 return [self
.implantation
.region
]
346 def get_devise(self
):
347 vp
= ValeurPoint
.objects
.filter(
348 implantation
=self
.implantation
, devise__archive
=False
353 return Devise
.objects
.get(code
='EUR')
357 __doc__
= Poste_
.__doc__
359 # meta dématérialisation : pour permettre le filtrage
360 vacant
= models
.NullBooleanField(u
"vacant", null
=True, blank
=True)
364 if self
.occupe_par():
368 def occupe_par(self
):
370 Retourne la liste d'employé occupant ce poste.
371 Généralement, retourne une liste d'un élément.
372 Si poste inoccupé, retourne liste vide.
373 UTILISE pour mettre a jour le flag vacant
377 for d
in self
.rh_dossiers
.exclude(date_fin__lt
=date
.today())
380 reversion
.register(Poste
, format
='xml', follow
=[
381 'rh_financements', 'rh_pieces', 'rh_comparaisons_internes',
386 POSTE_FINANCEMENT_CHOICES
= (
387 ('A', 'A - Frais de personnel'),
388 ('B', 'B - Projet(s)-Titre(s)'),
393 class PosteFinancement_(models
.Model
):
395 Pour un Poste, structure d'informations décrivant comment on prévoit
398 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
399 pourcentage
= models
.DecimalField(
400 max_digits
=12, decimal_places
=2,
401 help_text
="ex.: 33.33 % (décimale avec point)"
403 commentaire
= models
.TextField(
404 help_text
="Spécifiez la source de financement."
411 def __unicode__(self
):
412 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
415 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
418 class PosteFinancement(PosteFinancement_
):
419 poste
= models
.ForeignKey(
420 Poste
, db_column
='poste', related_name
='rh_financements'
423 reversion
.register(PosteFinancement
, format
='xml')
426 class PostePiece_(models
.Model
):
428 Documents relatifs au Poste.
429 Ex.: Description de poste
431 nom
= models
.CharField(u
"Nom", max_length
=255)
432 fichier
= models
.FileField(
433 u
"Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
440 def __unicode__(self
):
441 return u
'%s' % (self
.nom
)
444 class PostePiece(PostePiece_
):
445 poste
= models
.ForeignKey(
446 Poste
, db_column
='poste', related_name
='rh_pieces'
449 reversion
.register(PostePiece
, format
='xml')
452 class PosteComparaison_(models
.Model
, DevisableMixin
):
454 De la même manière qu'un dossier, un poste peut-être comparé à un autre
457 objects
= PosteComparaisonManager()
459 implantation
= models
.ForeignKey(
460 ref
.Implantation
, null
=True, blank
=True, related_name
="+"
462 nom
= models
.CharField(u
"Poste", max_length
=255, null
=True, blank
=True)
463 montant
= models
.IntegerField(null
=True)
464 devise
= models
.ForeignKey(
465 "Devise", related_name
='+', null
=True, blank
=True
471 def __unicode__(self
):
475 class PosteComparaison(PosteComparaison_
):
476 poste
= models
.ForeignKey(
477 Poste
, related_name
='rh_comparaisons_internes'
480 reversion
.register(PosteComparaison
, format
='xml')
483 class PosteCommentaire(Commentaire
):
484 poste
= models
.ForeignKey(
485 Poste
, db_column
='poste', related_name
='commentaires'
488 reversion
.register(PosteCommentaire
, format
='xml')
492 class Employe(models
.Model
):
494 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
495 Dossiers qu'il occupe ou a occupé de Postes.
497 Cette classe aurait pu avantageusement s'appeler Personne car la notion
498 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
501 objects
= EmployeManager()
504 nom
= models
.CharField(max_length
=255)
505 prenom
= models
.CharField(u
"prénom", max_length
=255)
506 nom_affichage
= models
.CharField(
507 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
509 nationalite
= models
.ForeignKey(
510 ref
.Pays
, to_field
='code', db_column
='nationalite',
511 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
512 blank
=True, null
=True
514 date_naissance
= models
.DateField(
515 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
516 validators
=[validate_date_passee
], null
=True, blank
=True
518 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
521 situation_famille
= models
.CharField(
522 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
523 null
=True, blank
=True
525 date_entree
= models
.DateField(
526 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
531 tel_domicile
= models
.CharField(
532 u
"tél. domicile", max_length
=255, null
=True, blank
=True
534 tel_cellulaire
= models
.CharField(
535 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
537 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
538 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
539 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
540 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
541 pays
= models
.ForeignKey(
542 ref
.Pays
, to_field
='code', db_column
='pays',
543 related_name
='employes', null
=True, blank
=True
545 courriel_perso
= models
.EmailField(
546 u
'adresse courriel personnelle', blank
=True
549 # meta dématérialisation : pour permettre le filtrage
550 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
553 ordering
= ['nom', 'prenom']
554 verbose_name
= u
"Employé"
555 verbose_name_plural
= u
"Employés"
557 def __unicode__(self
):
558 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
562 if self
.genre
.upper() == u
'M':
564 elif self
.genre
.upper() == u
'F':
570 Retourne l'URL du service retournant la photo de l'Employe.
571 Équivalent reverse url 'rh_photo' avec id en param.
573 from django
.core
.urlresolvers
import reverse
574 return reverse('rh_photo', kwargs
={'id': self
.id})
576 def dossiers_passes(self
):
577 params
= {KEY_STATUT
: STATUT_INACTIF
, }
578 search
= RechercheTemporelle(params
, self
.__class__
)
579 search
.purge_params(params
)
580 q
= search
.get_q_temporel(self
.rh_dossiers
)
581 return self
.rh_dossiers
.filter(q
)
583 def dossiers_futurs(self
):
584 params
= {KEY_STATUT
: STATUT_FUTUR
, }
585 search
= RechercheTemporelle(params
, self
.__class__
)
586 search
.purge_params(params
)
587 q
= search
.get_q_temporel(self
.rh_dossiers
)
588 return self
.rh_dossiers
.filter(q
)
590 def dossiers_encours(self
):
591 params
= {KEY_STATUT
: STATUT_ACTIF
, }
592 search
= RechercheTemporelle(params
, self
.__class__
)
593 search
.purge_params(params
)
594 q
= search
.get_q_temporel(self
.rh_dossiers
)
595 return self
.rh_dossiers
.filter(q
)
597 def dossier_principal(self
):
599 Retourne le dossier principal (ou le plus ancien si il y en a
603 dossier
= self
.rh_dossiers \
604 .filter(principal
=True).order_by('date_debut')[0]
605 except IndexError, Dossier
.DoesNotExist
:
609 def postes_encours(self
):
610 postes_encours
= set()
611 for d
in self
.dossiers_encours():
612 postes_encours
.add(d
.poste
)
613 return postes_encours
615 def poste_principal(self
):
617 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
619 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
621 # DEPRECATED : on a maintenant Dossier.principal
622 poste
= Poste
.objects
.none()
624 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
629 prefix_implantation
= "rh_dossiers__poste__implantation__region"
631 def get_regions(self
):
633 for d
in self
.dossiers
.all():
634 regions
.append(d
.poste
.implantation
.region
)
637 reversion
.register(Employe
, format
='xml', follow
=[
638 'pieces', 'commentaires', 'ayantdroits'
642 class EmployePiece(models
.Model
):
644 Documents relatifs à un employé.
647 employe
= models
.ForeignKey(
648 'Employe', db_column
='employe', related_name
="pieces",
649 verbose_name
=u
"employé"
651 nom
= models
.CharField(max_length
=255)
652 fichier
= models
.FileField(
653 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
658 verbose_name
= u
"Employé pièce"
659 verbose_name_plural
= u
"Employé pièces"
661 def __unicode__(self
):
662 return u
'%s' % (self
.nom
)
664 reversion
.register(EmployePiece
, format
='xml')
667 class EmployeCommentaire(Commentaire
):
668 employe
= models
.ForeignKey(
669 'Employe', db_column
='employe', related_name
='commentaires'
673 verbose_name
= u
"Employé commentaire"
674 verbose_name_plural
= u
"Employé commentaires"
676 reversion
.register(EmployeCommentaire
, format
='xml')
679 LIEN_PARENTE_CHOICES
= (
680 ('Conjoint', 'Conjoint'),
681 ('Conjointe', 'Conjointe'),
687 class AyantDroit(models
.Model
):
689 Personne en relation avec un Employe.
692 nom
= models
.CharField(max_length
=255)
693 prenom
= models
.CharField(u
"prénom", max_length
=255)
694 nom_affichage
= models
.CharField(
695 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
697 nationalite
= models
.ForeignKey(
698 ref
.Pays
, to_field
='code', db_column
='nationalite',
699 related_name
='ayantdroits_nationalite',
700 verbose_name
=u
"nationalité", null
=True, blank
=True
702 date_naissance
= models
.DateField(
703 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
704 validators
=[validate_date_passee
], null
=True, blank
=True
706 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
709 employe
= models
.ForeignKey(
710 'Employe', db_column
='employe', related_name
='ayantdroits',
711 verbose_name
=u
"Employé"
713 lien_parente
= models
.CharField(
714 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
715 null
=True, blank
=True
720 verbose_name
= u
"Ayant droit"
721 verbose_name_plural
= u
"Ayants droit"
723 def __unicode__(self
):
724 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
726 prefix_implantation
= "employe__dossiers__poste__implantation__region"
728 def get_regions(self
):
730 for d
in self
.employe
.dossiers
.all():
731 regions
.append(d
.poste
.implantation
.region
)
734 reversion
.register(AyantDroit
, format
='xml', follow
=['commentaires'])
737 class AyantDroitCommentaire(Commentaire
):
738 ayant_droit
= models
.ForeignKey(
739 'AyantDroit', db_column
='ayant_droit', related_name
='commentaires'
742 reversion
.register(AyantDroitCommentaire
, format
='xml')
747 STATUT_RESIDENCE_CHOICES
= (
749 ('expat', 'Expatrié'),
752 COMPTE_COMPTA_CHOICES
= (
759 class Dossier_(DateActiviteMixin
, models
.Model
, DevisableMixin
,):
761 Le Dossier regroupe les informations relatives à l'occupation
762 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
765 Plusieurs Contrats peuvent être associés au Dossier.
766 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
767 lequel aucun Dossier n'existe est un poste vacant.
770 objects
= DossierManager()
773 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
774 organisme_bstg
= models
.ForeignKey(
775 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
776 verbose_name
=u
"organisme",
778 u
"Si détaché (DET) ou mis à disposition (MAD), "
779 u
"préciser l'organisme."
780 ), null
=True, blank
=True
784 remplacement
= models
.BooleanField(default
=False)
785 remplacement_de
= models
.ForeignKey(
786 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
787 null
=True, blank
=True
789 statut_residence
= models
.CharField(
790 u
"statut", max_length
=10, default
='local', null
=True,
791 choices
=STATUT_RESIDENCE_CHOICES
795 classement
= models
.ForeignKey(
796 'Classement', db_column
='classement', related_name
='+', null
=True,
799 regime_travail
= models
.DecimalField(
800 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
801 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
803 regime_travail_nb_heure_semaine
= models
.DecimalField(
804 u
"nb. heures par semaine", max_digits
=12,
805 decimal_places
=2, null
=True,
806 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
807 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
810 # Occupation du Poste par cet Employe (anciennement "mandat")
811 date_debut
= models
.DateField(
812 u
"date de début d'occupation de poste", db_index
=True
814 date_fin
= models
.DateField(
815 u
"Date de fin d'occupation de poste", null
=True, blank
=True,
824 ordering
= ['employe__nom', ]
825 verbose_name
= u
"Dossier"
826 verbose_name_plural
= "Dossiers"
828 def salaire_theorique(self
):
829 annee
= date
.today().year
830 coeff
= self
.classement
.coefficient
831 implantation
= self
.poste
.implantation
832 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
834 montant
= coeff
* point
.valeur
835 devise
= point
.devise
836 return {'montant': montant
, 'devise': devise
}
838 def __unicode__(self
):
839 poste
= self
.poste
.nom
840 if self
.employe
.genre
== 'F':
841 poste
= self
.poste
.nom_feminin
842 return u
'%s - %s' % (self
.employe
, poste
)
844 prefix_implantation
= "poste__implantation__region"
846 def get_regions(self
):
847 return [self
.poste
.implantation
.region
]
849 def remunerations(self
):
850 key
= "%s_remunerations" % self
._meta
.app_label
851 remunerations
= getattr(self
, key
)
852 return remunerations
.all().order_by('-date_debut')
854 def remunerations_en_cours(self
):
855 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
856 return self
.remunerations().all().filter(q
).order_by('date_debut')
858 def get_salaire(self
):
860 return [r
for r
in self
.remunerations().order_by('-date_debut')
861 if r
.type_id
== 1][0]
865 def get_salaire_euros(self
):
866 tx
= self
.taux_devise()
867 return (float)(tx
) * (float)(self
.salaire
)
869 def get_remunerations_brutes(self
):
873 4 Indemnité d'expatriation
874 5 Indemnité pour frais
875 6 Indemnité de logement
876 7 Indemnité de fonction
877 8 Indemnité de responsabilité
878 9 Indemnité de transport
879 10 Indemnité compensatrice
880 11 Indemnité de subsistance
881 12 Indemnité différentielle
882 13 Prime d'installation
885 16 Indemnité de départ
886 18 Prime de 13ième mois
889 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
890 return [r
for r
in self
.remunerations_en_cours().all()
893 def get_charges_salariales(self
):
895 20 Charges salariales ?
898 return [r
for r
in self
.remunerations_en_cours().all()
901 def get_charges_patronales(self
):
903 17 Charges patronales
906 return [r
for r
in self
.remunerations_en_cours().all()
909 def get_remunerations_tierces(self
):
913 return [r
for r
in self
.remunerations_en_cours().all()
914 if r
.type_id
in (2,)]
918 def get_total_local_charges_salariales(self
):
919 devise
= self
.poste
.get_devise()
921 for r
in self
.get_charges_salariales():
922 if r
.devise
!= devise
:
924 total
+= float(r
.montant
)
927 def get_total_local_charges_patronales(self
):
928 devise
= self
.poste
.get_devise()
930 for r
in self
.get_charges_patronales():
931 if r
.devise
!= devise
:
933 total
+= float(r
.montant
)
936 def get_local_salaire_brut(self
):
938 somme des rémuérations brutes
940 devise
= self
.poste
.get_devise()
942 for r
in self
.get_remunerations_brutes():
943 if r
.devise
!= devise
:
945 total
+= float(r
.montant
)
948 def get_local_salaire_net(self
):
950 salaire brut - charges salariales
952 devise
= self
.poste
.get_devise()
954 for r
in self
.get_charges_salariales():
955 if r
.devise
!= devise
:
957 total_charges
+= float(r
.montant
)
958 return self
.get_local_salaire_brut() - total_charges
960 def get_local_couts_auf(self
):
962 salaire net + charges patronales
964 devise
= self
.poste
.get_devise()
966 for r
in self
.get_charges_patronales():
967 if r
.devise
!= devise
:
969 total_charges
+= float(r
.montant
)
970 return self
.get_local_salaire_net() + total_charges
972 def get_total_local_remunerations_tierces(self
):
973 devise
= self
.poste
.get_devise()
975 for r
in self
.get_remunerations_tierces():
976 if r
.devise
!= devise
:
978 total
+= float(r
.montant
)
983 def get_total_charges_salariales(self
):
985 for r
in self
.get_charges_salariales():
986 total
+= r
.montant_euros()
989 def get_total_charges_patronales(self
):
991 for r
in self
.get_charges_patronales():
992 total
+= r
.montant_euros()
995 def get_salaire_brut(self
):
997 somme des rémuérations brutes
1000 for r
in self
.get_remunerations_brutes():
1001 total
+= r
.montant_euros()
1004 def get_salaire_net(self
):
1006 salaire brut - charges salariales
1009 for r
in self
.get_charges_salariales():
1010 total_charges
+= r
.montant_euros()
1011 return self
.get_salaire_brut() - total_charges
1013 def get_couts_auf(self
):
1015 salaire net + charges patronales
1018 for r
in self
.get_charges_patronales():
1019 total_charges
+= r
.montant_euros()
1020 return self
.get_salaire_net() + total_charges
1022 def get_total_remunerations_tierces(self
):
1024 for r
in self
.get_remunerations_tierces():
1025 total
+= r
.montant_euros()
1028 def premier_contrat(self
):
1029 """contrat avec plus petite date de début"""
1031 contrat
= self
.rh_contrats
.exclude(date_debut
=None) \
1032 .order_by('date_debut')[0]
1033 except IndexError, Contrat
.DoesNotExist
:
1037 def dernier_contrat(self
):
1038 """contrat avec plus grande date de fin"""
1040 contrat
= self
.rh_contrats
.exclude(date_debut
=None) \
1041 .order_by('-date_debut')[0]
1042 except IndexError, Contrat
.DoesNotExist
:
1047 today
= date
.today()
1048 return (self
.date_debut
is None or self
.date_debut
<= today
) \
1049 and (self
.date_fin
is None or self
.date_fin
>= today
) \
1050 and not (self
.date_fin
is None and self
.date_debut
is None)
1053 class Dossier(Dossier_
):
1054 __doc__
= Dossier_
.__doc__
1055 poste
= models
.ForeignKey(
1056 Poste
, db_column
='poste', related_name
='rh_dossiers',
1057 help_text
=u
"Taper le nom du poste ou du type de poste",
1059 employe
= models
.ForeignKey(
1060 'Employe', db_column
='employe',
1061 help_text
=u
"Taper le nom de l'employé",
1062 related_name
='rh_dossiers', verbose_name
=u
"employé"
1064 principal
= models
.BooleanField(
1065 u
"dossier principal", default
=True,
1067 u
"Ce dossier est pour le principal poste occupé par l'employé"
1071 reversion
.register(Dossier
, format
='xml', follow
=[
1072 'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
1073 'rh_contrats', 'commentaires'
1077 class DossierPiece_(models
.Model
):
1079 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1080 Ex.: Lettre de motivation.
1082 nom
= models
.CharField(max_length
=255)
1083 fichier
= models
.FileField(
1084 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
1091 def __unicode__(self
):
1092 return u
'%s' % (self
.nom
)
1095 class DossierPiece(DossierPiece_
):
1096 dossier
= models
.ForeignKey(
1097 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
1100 reversion
.register(DossierPiece
, format
='xml')
1102 class DossierCommentaire(Commentaire
):
1103 dossier
= models
.ForeignKey(
1104 Dossier
, db_column
='dossier', related_name
='commentaires'
1107 reversion
.register(DossierCommentaire
, format
='xml')
1110 class DossierComparaison_(models
.Model
, DevisableMixin
):
1112 Photo d'une comparaison salariale au moment de l'embauche.
1114 objects
= DossierComparaisonManager()
1116 implantation
= models
.ForeignKey(
1117 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1119 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1120 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1121 montant
= models
.IntegerField(null
=True)
1122 devise
= models
.ForeignKey(
1123 'Devise', related_name
='+', null
=True, blank
=True
1129 def __unicode__(self
):
1130 return "%s (%s)" % (self
.poste
, self
.personne
)
1133 class DossierComparaison(DossierComparaison_
):
1134 dossier
= models
.ForeignKey(
1135 Dossier
, related_name
='rh_comparaisons'
1138 reversion
.register(DossierComparaison
, format
='xml')
1143 class RemunerationMixin(models
.Model
):
1146 type = models
.ForeignKey(
1147 'TypeRemuneration', db_column
='type', related_name
='+',
1148 verbose_name
=u
"type de rémunération"
1150 type_revalorisation
= models
.ForeignKey(
1151 'TypeRevalorisation', db_column
='type_revalorisation',
1152 related_name
='+', verbose_name
=u
"type de revalorisation",
1153 null
=True, blank
=True
1155 montant
= models
.DecimalField(
1156 null
=True, blank
=True, max_digits
=12, decimal_places
=2
1157 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1158 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1160 # commentaire = precision
1161 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1163 # date_debut = anciennement date_effectif
1164 date_debut
= models
.DateField(
1165 u
"date de début", null
=True, blank
=True, db_index
=True
1167 date_fin
= models
.DateField(
1168 u
"date de fin", null
=True, blank
=True, db_index
=True
1173 ordering
= ['type__nom', '-date_fin']
1175 def __unicode__(self
):
1176 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1179 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1181 Structure de rémunération (données budgétaires) en situation normale
1182 pour un Dossier. Si un Evenement existe, utiliser la structure de
1183 rémunération EvenementRemuneration de cet événement.
1185 objects
= RemunerationManager()
1187 def montant_mois(self
):
1188 return round(self
.montant
/ 12, 2)
1190 def montant_avec_regime(self
):
1191 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1193 def montant_euro_mois(self
):
1194 return round(self
.montant_euros() / 12, 2)
1196 def __unicode__(self
):
1198 devise
= self
.devise
.code
1201 return "%s %s" % (self
.montant
, devise
)
1205 verbose_name
= u
"Rémunération"
1206 verbose_name_plural
= u
"Rémunérations"
1209 class Remuneration(Remuneration_
):
1210 dossier
= models
.ForeignKey(
1211 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1214 reversion
.register(Remuneration
, format
='xml')
1219 class Contrat_(models
.Model
):
1221 Document juridique qui encadre la relation de travail d'un Employe
1222 pour un Poste particulier. Pour un Dossier (qui documente cette
1223 relation de travail) plusieurs contrats peuvent être associés.
1225 objects
= ContratManager()
1226 type_contrat
= models
.ForeignKey(
1227 'TypeContrat', db_column
='type_contrat',
1228 verbose_name
=u
'type de contrat', related_name
='+'
1230 date_debut
= models
.DateField(
1231 u
"date de début", db_index
=True
1233 date_fin
= models
.DateField(
1234 u
"date de fin", null
=True, blank
=True, db_index
=True
1236 fichier
= models
.FileField(
1237 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1243 ordering
= ['dossier__employe__nom']
1244 verbose_name
= u
"Contrat"
1245 verbose_name_plural
= u
"Contrats"
1247 def __unicode__(self
):
1248 return u
'%s - %s' % (self
.dossier
, self
.id)
1251 class Contrat(Contrat_
):
1252 dossier
= models
.ForeignKey(
1253 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1256 reversion
.register(Contrat
, format
='xml')
1261 class CategorieEmploi(models
.Model
):
1263 Catégorie utilisée dans la gestion des Postes.
1264 Catégorie supérieure à TypePoste.
1266 nom
= models
.CharField(max_length
=255)
1270 verbose_name
= u
"catégorie d'emploi"
1271 verbose_name_plural
= u
"catégories d'emploi"
1273 def __unicode__(self
):
1276 reversion
.register(CategorieEmploi
, format
='xml')
1279 class FamilleProfessionnelle(models
.Model
):
1281 Famille professionnelle d'un poste.
1283 nom
= models
.CharField(max_length
=100)
1287 verbose_name
= u
'famille professionnelle'
1288 verbose_name_plural
= u
'familles professionnelles'
1290 def __unicode__(self
):
1293 reversion
.register(FamilleProfessionnelle
, format
='xml')
1296 class TypePoste(models
.Model
):
1300 nom
= models
.CharField(max_length
=255)
1301 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1302 is_responsable
= models
.BooleanField(
1303 u
"poste de responsabilité", default
=False
1305 categorie_emploi
= models
.ForeignKey(
1306 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1307 verbose_name
=u
"catégorie d'emploi"
1309 famille_professionnelle
= models
.ForeignKey(
1310 FamilleProfessionnelle
, related_name
='types_de_poste',
1311 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1316 verbose_name
= u
"Type de poste"
1317 verbose_name_plural
= u
"Types de poste"
1319 def __unicode__(self
):
1320 return u
'%s' % (self
.nom
)
1322 reversion
.register(TypePoste
, format
='xml')
1325 TYPE_PAIEMENT_CHOICES
= (
1326 (u
'Régulier', u
'Régulier'),
1327 (u
'Ponctuel', u
'Ponctuel'),
1330 NATURE_REMUNERATION_CHOICES
= (
1331 (u
'Accessoire', u
'Accessoire'),
1332 (u
'Charges', u
'Charges'),
1333 (u
'Indemnité', u
'Indemnité'),
1334 (u
'RAS', u
'Rémunération autre source'),
1335 (u
'Traitement', u
'Traitement'),
1339 class TypeRemuneration(Archivable
):
1341 Catégorie de Remuneration.
1343 objects
= TypeRemunerationManager()
1345 nom
= models
.CharField(max_length
=255)
1346 type_paiement
= models
.CharField(
1347 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1349 nature_remuneration
= models
.CharField(
1350 u
"nature de la rémunération", max_length
=30,
1351 choices
=NATURE_REMUNERATION_CHOICES
1356 verbose_name
= u
"Type de rémunération"
1357 verbose_name_plural
= u
"Types de rémunération"
1359 def __unicode__(self
):
1362 reversion
.register(TypeRemuneration
, format
='xml')
1365 class TypeRevalorisation(models
.Model
):
1367 Justification du changement de la Remuneration.
1368 (Actuellement utilisé dans aucun traitement informatique.)
1370 nom
= models
.CharField(max_length
=255)
1374 verbose_name
= u
"Type de revalorisation"
1375 verbose_name_plural
= u
"Types de revalorisation"
1377 def __unicode__(self
):
1378 return u
'%s' % (self
.nom
)
1380 reversion
.register(TypeRevalorisation
, format
='xml')
1383 class Service(Archivable
):
1385 Unité administrative où les Postes sont rattachés.
1387 nom
= models
.CharField(max_length
=255)
1391 verbose_name
= u
"service"
1392 verbose_name_plural
= u
"services"
1394 def __unicode__(self
):
1397 reversion
.register(Service
, format
='xml')
1400 TYPE_ORGANISME_CHOICES
= (
1401 ('MAD', 'Mise à disposition'),
1402 ('DET', 'Détachement'),
1406 class OrganismeBstg(models
.Model
):
1408 Organisation d'où provient un Employe mis à disposition (MAD) de
1409 ou détaché (DET) à l'AUF à titre gratuit.
1411 (BSTG = bien et service à titre gratuit.)
1413 nom
= models
.CharField(max_length
=255)
1414 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1415 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1417 related_name
='organismes_bstg',
1418 null
=True, blank
=True)
1421 ordering
= ['type', 'nom']
1422 verbose_name
= u
"Organisme BSTG"
1423 verbose_name_plural
= u
"Organismes BSTG"
1425 def __unicode__(self
):
1426 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1428 prefix_implantation
= "pays__region"
1430 def get_regions(self
):
1431 return [self
.pays
.region
]
1433 reversion
.register(OrganismeBstg
, format
='xml')
1436 class Statut(models
.Model
):
1438 Statut de l'Employe dans le cadre d'un Dossier particulier.
1441 code
= models
.CharField(
1442 max_length
=25, unique
=True,
1444 u
"Saisir un code court mais lisible pour ce statut : "
1445 u
"le code est utilisé pour associer les statuts aux autres "
1446 u
"données tout en demeurant plus lisible qu'un identifiant "
1450 nom
= models
.CharField(max_length
=255)
1454 verbose_name
= u
"Statut d'employé"
1455 verbose_name_plural
= u
"Statuts d'employé"
1457 def __unicode__(self
):
1458 return u
'%s : %s' % (self
.code
, self
.nom
)
1460 reversion
.register(Statut
, format
='xml')
1463 TYPE_CLASSEMENT_CHOICES
= (
1464 ('S', 'S -Soutien'),
1465 ('T', 'T - Technicien'),
1466 ('P', 'P - Professionel'),
1468 ('D', 'D - Direction'),
1469 ('SO', 'SO - Sans objet [expatriés]'),
1470 ('HG', 'HG - Hors grille [direction]'),
1474 class ClassementManager(models
.Manager
):
1476 Ordonner les spcéfiquement les classements.
1478 def get_query_set(self
):
1479 qs
= super(self
.__class__
, self
).get_query_set()
1480 qs
= qs
.extra(select
={
1481 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1483 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1487 class Classement_(models
.Model
):
1489 Éléments de classement de la
1490 "Grille générique de classement hiérarchique".
1492 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1493 classement dans la grille. Le classement donne le coefficient utilisé dans:
1495 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1497 objects
= ClassementManager()
1500 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1501 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1502 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1503 coefficient
= models
.FloatField(u
"coefficient", blank
=True, null
=True)
1506 # annee # au lieu de date_debut et date_fin
1507 commentaire
= models
.TextField(null
=True, blank
=True)
1511 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1512 verbose_name
= u
"Classement"
1513 verbose_name_plural
= u
"Classements"
1515 def __unicode__(self
):
1516 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1519 class Classement(Classement_
):
1520 __doc__
= Classement_
.__doc__
1522 reversion
.register(Classement
, format
='xml')
1525 class TauxChange_(models
.Model
):
1527 Taux de change de la devise vers l'euro (EUR)
1528 pour chaque année budgétaire.
1531 devise
= models
.ForeignKey('Devise', db_column
='devise')
1532 annee
= models
.IntegerField(u
"année")
1533 taux
= models
.FloatField(u
"taux vers l'euro")
1537 ordering
= ['-annee', 'devise__code']
1538 verbose_name
= u
"Taux de change"
1539 verbose_name_plural
= u
"Taux de change"
1540 unique_together
= ('devise', 'annee')
1542 def __unicode__(self
):
1543 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1546 class TauxChange(TauxChange_
):
1547 __doc__
= TauxChange_
.__doc__
1549 reversion
.register(TauxChange
, format
='xml')
1552 class ValeurPointManager(models
.Manager
):
1554 def get_query_set(self
):
1555 return super(ValeurPointManager
, self
).get_query_set() \
1556 .select_related('devise', 'implantation')
1559 class ValeurPoint_(models
.Model
):
1561 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1562 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1563 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1565 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1568 objects
= models
.Manager()
1569 actuelles
= ValeurPointManager()
1571 valeur
= models
.FloatField(null
=True)
1572 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1573 implantation
= models
.ForeignKey(ref
.Implantation
,
1574 db_column
='implantation',
1575 related_name
='%(app_label)s_valeur_point')
1577 annee
= models
.IntegerField()
1580 ordering
= ['-annee', 'implantation__nom']
1582 verbose_name
= u
"Valeur du point"
1583 verbose_name_plural
= u
"Valeurs du point"
1584 unique_together
= ('implantation', 'annee')
1586 def __unicode__(self
):
1587 return u
'%s %s %s [%s] %s' % (
1588 self
.devise
.code
, self
.annee
, self
.valeur
,
1589 self
.implantation
.nom_court
, self
.devise
.nom
1593 class ValeurPoint(ValeurPoint_
):
1594 __doc__
= ValeurPoint_
.__doc__
1596 reversion
.register(ValeurPoint
, format
='xml')
1599 class Devise(Archivable
):
1603 code
= models
.CharField(max_length
=10, unique
=True)
1604 nom
= models
.CharField(max_length
=255)
1608 verbose_name
= u
"devise"
1609 verbose_name_plural
= u
"devises"
1611 def __unicode__(self
):
1612 return u
'%s - %s' % (self
.code
, self
.nom
)
1614 reversion
.register(Devise
, format
='xml')
1617 class TypeContrat(models
.Model
):
1621 nom
= models
.CharField(max_length
=255)
1622 nom_long
= models
.CharField(max_length
=255)
1626 verbose_name
= u
"Type de contrat"
1627 verbose_name_plural
= u
"Types de contrat"
1629 def __unicode__(self
):
1630 return u
'%s' % (self
.nom
)
1632 reversion
.register(TypeContrat
, format
='xml')
1637 class ResponsableImplantationProxy(ref
.Implantation
):
1645 verbose_name
= u
"Responsable d'implantation"
1646 verbose_name_plural
= u
"Responsables d'implantation"
1649 class ResponsableImplantation(models
.Model
):
1651 Le responsable d'une implantation.
1652 Anciennement géré sur le Dossier du responsable.
1654 employe
= models
.ForeignKey(
1655 'Employe', db_column
='employe', related_name
='+', null
=True,
1658 implantation
= models
.OneToOneField(
1659 "ResponsableImplantationProxy", db_column
='implantation',
1660 related_name
='responsable', unique
=True
1663 def __unicode__(self
):
1664 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1667 ordering
= ['implantation__nom']
1668 verbose_name
= "Responsable d'implantation"
1669 verbose_name_plural
= "Responsables d'implantation"
1671 reversion
.register(ResponsableImplantation
, format
='xml')