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 (
23 DossierComparaisonManager
,
24 PosteComparaisonManager
,
31 from project
.rh
.validators
import validate_date_passee
33 # import pour relocaliser le modèle selon la convention (models.py pour
35 from project
.rh
.historique
import ModificationTraite
38 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
39 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
40 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
41 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
= \
42 "Saisir le nombre d'heure de travail à temps complet (100%), " \
43 "sans tenir compte du régime de travail"
46 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
47 base_url
=settings
.PRIVE_MEDIA_URL
)
50 class RemunIntegrityException(Exception):
53 def poste_piece_dispatch(instance
, filename
):
54 path
= "%s/poste/%s/%s" % (
55 instance
._meta
.app_label
, instance
.poste_id
, filename
60 def dossier_piece_dispatch(instance
, filename
):
61 path
= "%s/dossier/%s/%s" % (
62 instance
._meta
.app_label
, instance
.dossier_id
, filename
67 def employe_piece_dispatch(instance
, filename
):
68 path
= "%s/employe/%s/%s" % (
69 instance
._meta
.app_label
, instance
.employe_id
, filename
74 def contrat_dispatch(instance
, filename
):
75 path
= "%s/contrat/%s/%s" % (
76 instance
._meta
.app_label
, instance
.dossier_id
, filename
81 class DateActiviteMixin(models
.Model
):
83 Mixin pour mettre à jour l'activité d'un modèle
87 date_creation
= models
.DateTimeField(auto_now_add
=True,
88 null
=True, blank
=True,
89 verbose_name
=u
"Date de création",)
90 date_modification
= models
.DateTimeField(auto_now
=True,
91 null
=True, blank
=True,
92 verbose_name
=u
"Date de modification",)
95 class Archivable(models
.Model
):
96 archive
= models
.BooleanField(u
'archivé', default
=False)
98 objects
= ArchivableManager()
99 avec_archives
= models
.Manager()
105 class DevisableMixin(object):
107 def get_annee_pour_taux_devise(self
):
108 return datetime
.datetime
.now().year
110 def taux_devise(self
, devise
=None):
116 if devise
.code
== "EUR":
119 annee
= self
.get_annee_pour_taux_devise()
120 taux
= TauxChange
.objects
.filter(devise
=devise
, annee__lte
=annee
) \
124 def montant_euros(self
):
126 taux
= self
.taux_devise()
131 return int(round(float(self
.montant
) * float(taux
), 2))
134 class Commentaire(models
.Model
):
135 texte
= models
.TextField()
136 owner
= models
.ForeignKey(
137 'auth.User', db_column
='owner', related_name
='+',
138 verbose_name
=u
"Commentaire de"
140 date_creation
= models
.DateTimeField(
141 u
'date', auto_now_add
=True, blank
=True, null
=True
146 ordering
= ['-date_creation']
148 def __unicode__(self
):
149 return u
'%s' % (self
.texte
)
154 POSTE_APPEL_CHOICES
= (
155 ('interne', 'Interne'),
156 ('externe', 'Externe'),
160 class Poste_( DateActiviteMixin
, models
.Model
,):
162 Un Poste est un emploi (job) à combler dans une implantation.
163 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
164 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
167 objects
= PosteManager()
170 nom
= models
.CharField(u
"Titre du poste", max_length
=255)
171 nom_feminin
= models
.CharField(
172 u
"Titre du poste (au féminin)", max_length
=255, null
=True
174 implantation
= models
.ForeignKey(
176 help_text
=u
"Taper le nom de l'implantation ou sa région",
177 db_column
='implantation', related_name
='+'
179 type_poste
= models
.ForeignKey(
180 'TypePoste', db_column
='type_poste',
181 help_text
=u
"Taper le nom du type de poste", related_name
='+',
182 null
=True, verbose_name
=u
"type de poste"
184 service
= models
.ForeignKey(
185 'Service', db_column
='service', related_name
='%(app_label)s_postes',
186 verbose_name
=u
"direction/service/pôle support", null
=True
188 responsable
= models
.ForeignKey(
189 'Poste', db_column
='responsable',
190 related_name
='+', null
=True,
191 help_text
=u
"Taper le nom du poste ou du type de poste",
192 verbose_name
=u
"Poste du responsable"
196 regime_travail
= models
.DecimalField(
197 u
"temps de travail", max_digits
=12, decimal_places
=2,
198 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
199 help_text
="% du temps complet"
201 regime_travail_nb_heure_semaine
= models
.DecimalField(
202 u
"nb. heures par semaine", max_digits
=12, decimal_places
=2,
203 null
=True, default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
204 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
208 local
= models
.NullBooleanField(
209 u
"local", default
=True, null
=True, blank
=True
211 expatrie
= models
.NullBooleanField(
212 u
"expatrié", default
=False, null
=True, blank
=True
214 mise_a_disposition
= models
.NullBooleanField(
215 u
"mise à disposition", null
=True, default
=False
217 appel
= models
.CharField(
218 u
"Appel à candidature", max_length
=10, null
=True,
219 choices
=POSTE_APPEL_CHOICES
, default
='interne'
223 classement_min
= models
.ForeignKey(
224 'Classement', db_column
='classement_min', related_name
='+',
225 null
=True, blank
=True
227 classement_max
= models
.ForeignKey(
228 'Classement', db_column
='classement_max', related_name
='+',
229 null
=True, blank
=True
231 valeur_point_min
= models
.ForeignKey(
233 help_text
=u
"Taper le code ou le nom de l'implantation",
234 db_column
='valeur_point_min', related_name
='+', null
=True,
237 valeur_point_max
= models
.ForeignKey(
239 help_text
=u
"Taper le code ou le nom de l'implantation",
240 db_column
='valeur_point_max', related_name
='+', null
=True,
243 devise_min
= models
.ForeignKey(
244 'Devise', db_column
='devise_min', null
=True, related_name
='+'
246 devise_max
= models
.ForeignKey(
247 'Devise', db_column
='devise_max', null
=True, related_name
='+'
249 salaire_min
= models
.DecimalField(
250 max_digits
=12, decimal_places
=2, default
=0,
252 salaire_max
= models
.DecimalField(
253 max_digits
=12, decimal_places
=2, default
=0,
255 indemn_min
= models
.DecimalField(
256 max_digits
=12, decimal_places
=2, default
=0,
258 indemn_max
= models
.DecimalField(
259 max_digits
=12, decimal_places
=2, default
=0,
261 autre_min
= models
.DecimalField(
262 max_digits
=12, decimal_places
=2, default
=0,
264 autre_max
= models
.DecimalField(
265 max_digits
=12, decimal_places
=2, default
=0,
268 # Comparatifs de rémunération
269 devise_comparaison
= models
.ForeignKey(
270 'Devise', null
=True, blank
=True, db_column
='devise_comparaison',
273 comp_locale_min
= models
.DecimalField(
274 max_digits
=12, decimal_places
=2, null
=True, blank
=True
276 comp_locale_max
= models
.DecimalField(
277 max_digits
=12, decimal_places
=2, null
=True, blank
=True
279 comp_universite_min
= models
.DecimalField(
280 max_digits
=12, decimal_places
=2, null
=True, blank
=True
282 comp_universite_max
= models
.DecimalField(
283 max_digits
=12, decimal_places
=2, null
=True, blank
=True
285 comp_fonctionpub_min
= models
.DecimalField(
286 max_digits
=12, decimal_places
=2, null
=True, blank
=True
288 comp_fonctionpub_max
= models
.DecimalField(
289 max_digits
=12, decimal_places
=2, null
=True, blank
=True
291 comp_ong_min
= models
.DecimalField(
292 max_digits
=12, decimal_places
=2, null
=True, blank
=True
294 comp_ong_max
= models
.DecimalField(
295 max_digits
=12, decimal_places
=2, null
=True, blank
=True
297 comp_autre_min
= models
.DecimalField(
298 max_digits
=12, decimal_places
=2, null
=True, blank
=True
300 comp_autre_max
= models
.DecimalField(
301 max_digits
=12, decimal_places
=2, null
=True, blank
=True
305 justification
= models
.TextField(null
=True, blank
=True)
308 date_debut
= models
.DateField(
309 u
"date de début", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True,
312 date_fin
= models
.DateField(
313 u
"date de fin", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True,
319 ordering
= ['implantation__nom', 'nom']
320 verbose_name
= u
"Poste"
321 verbose_name_plural
= u
"Postes"
324 def __unicode__(self
):
325 representation
= u
'%s - %s [%s]' % (
326 self
.implantation
, self
.nom
, self
.id
328 return representation
330 prefix_implantation
= "implantation__zone_administrative"
332 def get_zones_administratives(self
):
333 return [self
.implantation
.zone_administrative
]
335 def get_devise(self
):
336 vp
= ValeurPoint
.objects
.filter(
337 implantation
=self
.implantation
, devise__archive
=False
342 return Devise
.objects
.get(code
='EUR')
346 __doc__
= Poste_
.__doc__
348 # meta dématérialisation : pour permettre le filtrage
349 vacant
= models
.NullBooleanField(u
"vacant", null
=True, blank
=True)
353 if self
.occupe_par():
357 def occupe_par(self
):
359 Retourne la liste d'employé occupant ce poste.
360 Généralement, retourne une liste d'un élément.
361 Si poste inoccupé, retourne liste vide.
362 UTILISE pour mettre a jour le flag vacant
366 for d
in self
.rh_dossiers
.exclude(date_fin__lt
=date
.today())
369 reversion
.register(Poste
, format
='xml', follow
=[
370 'rh_financements', 'rh_pieces', 'rh_comparaisons_internes',
375 POSTE_FINANCEMENT_CHOICES
= (
376 ('A', 'A - Frais de personnel'),
377 ('B', 'B - Projet(s)-Titre(s)'),
382 class PosteFinancement_(models
.Model
):
384 Pour un Poste, structure d'informations décrivant comment on prévoit
387 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
388 pourcentage
= models
.DecimalField(
389 max_digits
=12, decimal_places
=2,
390 help_text
="ex.: 33.33 % (décimale avec point)"
392 commentaire
= models
.TextField(
393 help_text
="Spécifiez la source de financement."
400 def __unicode__(self
):
401 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
404 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
407 class PosteFinancement(PosteFinancement_
):
408 poste
= models
.ForeignKey(
409 Poste
, db_column
='poste', related_name
='rh_financements'
412 reversion
.register(PosteFinancement
, format
='xml')
415 class PostePiece_(models
.Model
):
417 Documents relatifs au Poste.
418 Ex.: Description de poste
420 nom
= models
.CharField(u
"Nom", max_length
=255)
421 fichier
= models
.FileField(
422 u
"Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
429 def __unicode__(self
):
430 return u
'%s' % (self
.nom
)
433 class PostePiece(PostePiece_
):
434 poste
= models
.ForeignKey(
435 Poste
, db_column
='poste', related_name
='rh_pieces'
438 reversion
.register(PostePiece
, format
='xml')
441 class PosteComparaison_(models
.Model
, DevisableMixin
):
443 De la même manière qu'un dossier, un poste peut-être comparé à un autre
446 objects
= PosteComparaisonManager()
448 implantation
= models
.ForeignKey(
449 ref
.Implantation
, null
=True, blank
=True, related_name
="+"
451 nom
= models
.CharField(u
"Poste", max_length
=255, null
=True, blank
=True)
452 montant
= models
.IntegerField(null
=True)
453 devise
= models
.ForeignKey(
454 "Devise", related_name
='+', null
=True, blank
=True
460 def __unicode__(self
):
464 class PosteComparaison(PosteComparaison_
):
465 poste
= models
.ForeignKey(
466 Poste
, related_name
='rh_comparaisons_internes'
469 reversion
.register(PosteComparaison
, format
='xml')
472 class PosteCommentaire(Commentaire
):
473 poste
= models
.ForeignKey(
474 Poste
, db_column
='poste', related_name
='commentaires'
477 reversion
.register(PosteCommentaire
, format
='xml')
481 class Employe(models
.Model
):
483 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
484 Dossiers qu'il occupe ou a occupé de Postes.
486 Cette classe aurait pu avantageusement s'appeler Personne car la notion
487 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
490 objects
= EmployeManager()
493 nom
= models
.CharField(max_length
=255)
494 prenom
= models
.CharField(u
"prénom", max_length
=255)
495 nom_affichage
= models
.CharField(
496 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
498 nationalite
= models
.ForeignKey(
499 ref
.Pays
, to_field
='code', db_column
='nationalite',
500 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
501 blank
=True, null
=True
503 date_naissance
= models
.DateField(
504 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
505 validators
=[validate_date_passee
], null
=True, blank
=True
507 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
510 situation_famille
= models
.CharField(
511 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
512 null
=True, blank
=True
514 date_entree
= models
.DateField(
515 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
520 tel_domicile
= models
.CharField(
521 u
"tél. domicile", max_length
=255, null
=True, blank
=True
523 tel_cellulaire
= models
.CharField(
524 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
526 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
527 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
528 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
529 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
530 pays
= models
.ForeignKey(
531 ref
.Pays
, to_field
='code', db_column
='pays',
532 related_name
='employes', null
=True, blank
=True
534 courriel_perso
= models
.EmailField(
535 u
'adresse courriel personnelle', blank
=True
538 # meta dématérialisation : pour permettre le filtrage
539 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
542 ordering
= ['nom', 'prenom']
543 verbose_name
= u
"Employé"
544 verbose_name_plural
= u
"Employés"
546 def __unicode__(self
):
547 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
551 if self
.genre
.upper() == u
'M':
553 elif self
.genre
.upper() == u
'F':
559 Retourne l'URL du service retournant la photo de l'Employe.
560 Équivalent reverse url 'rh_photo' avec id en param.
562 from django
.core
.urlresolvers
import reverse
563 return reverse('rh_photo', kwargs
={'id': self
.id})
565 def dossiers_passes(self
):
566 params
= {KEY_STATUT
: STATUT_INACTIF
, }
567 search
= RechercheTemporelle(params
, Dossier
)
568 search
.purge_params(params
)
569 q
= search
.get_q_temporel(self
.rh_dossiers
)
570 return self
.rh_dossiers
.filter(q
)
572 def dossiers_futurs(self
):
573 params
= {KEY_STATUT
: STATUT_FUTUR
, }
574 search
= RechercheTemporelle(params
, Dossier
)
575 search
.purge_params(params
)
576 q
= search
.get_q_temporel(self
.rh_dossiers
)
577 return self
.rh_dossiers
.filter(q
)
579 def dossiers_encours(self
):
580 params
= {KEY_STATUT
: STATUT_ACTIF
, }
581 search
= RechercheTemporelle(params
, Dossier
)
582 search
.purge_params(params
)
583 q
= search
.get_q_temporel(self
.rh_dossiers
)
584 return self
.rh_dossiers
.filter(q
)
586 def dossier_principal(self
):
588 Retourne le dossier principal (ou le plus ancien si il y en a
592 dossier
= self
.rh_dossiers \
593 .filter(principal
=True).order_by('date_debut')[0]
594 except IndexError, Dossier
.DoesNotExist
:
598 def postes_encours(self
):
599 postes_encours
= set()
600 for d
in self
.dossiers_encours():
601 postes_encours
.add(d
.poste
)
602 return postes_encours
604 def poste_principal(self
):
606 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
608 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
610 # DEPRECATED : on a maintenant Dossier.principal
611 poste
= Poste
.objects
.none()
613 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
618 prefix_implantation
= \
619 "rh_dossiers__poste__implantation__zone_administrative"
621 def get_zones_administratives(self
):
623 d
.poste
.implantation
.zone_administrative
624 for d
in self
.dossiers
.all()
627 reversion
.register(Employe
, format
='xml', follow
=[
628 'pieces', 'commentaires', 'ayantdroits'
632 class EmployePiece(models
.Model
):
634 Documents relatifs à un employé.
637 employe
= models
.ForeignKey(
638 'Employe', db_column
='employe', related_name
="pieces",
639 verbose_name
=u
"employé"
641 nom
= models
.CharField(max_length
=255)
642 fichier
= models
.FileField(
643 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
648 verbose_name
= u
"Employé pièce"
649 verbose_name_plural
= u
"Employé pièces"
651 def __unicode__(self
):
652 return u
'%s' % (self
.nom
)
654 reversion
.register(EmployePiece
, format
='xml')
657 class EmployeCommentaire(Commentaire
):
658 employe
= models
.ForeignKey(
659 'Employe', db_column
='employe', related_name
='commentaires'
663 verbose_name
= u
"Employé commentaire"
664 verbose_name_plural
= u
"Employé commentaires"
666 reversion
.register(EmployeCommentaire
, format
='xml')
669 LIEN_PARENTE_CHOICES
= (
670 ('Conjoint', 'Conjoint'),
671 ('Conjointe', 'Conjointe'),
677 class AyantDroit(models
.Model
):
679 Personne en relation avec un Employe.
682 nom
= models
.CharField(max_length
=255)
683 prenom
= models
.CharField(u
"prénom", max_length
=255)
684 nom_affichage
= models
.CharField(
685 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
687 nationalite
= models
.ForeignKey(
688 ref
.Pays
, to_field
='code', db_column
='nationalite',
689 related_name
='ayantdroits_nationalite',
690 verbose_name
=u
"nationalité", null
=True, blank
=True
692 date_naissance
= models
.DateField(
693 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
694 validators
=[validate_date_passee
], null
=True, blank
=True
696 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
699 employe
= models
.ForeignKey(
700 'Employe', db_column
='employe', related_name
='ayantdroits',
701 verbose_name
=u
"Employé"
703 lien_parente
= models
.CharField(
704 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
705 null
=True, blank
=True
710 verbose_name
= u
"Ayant droit"
711 verbose_name_plural
= u
"Ayants droit"
713 def __unicode__(self
):
714 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
716 prefix_implantation
= \
717 "employe__dossiers__poste__implantation__zone_administrative"
719 def get_zones_administratives(self
):
721 d
.poste
.implantation
.zone_administrative
722 for d
in self
.employe
.dossiers
.all()
725 reversion
.register(AyantDroit
, format
='xml', follow
=['commentaires'])
728 class AyantDroitCommentaire(Commentaire
):
729 ayant_droit
= models
.ForeignKey(
730 'AyantDroit', db_column
='ayant_droit', related_name
='commentaires'
733 reversion
.register(AyantDroitCommentaire
, format
='xml')
738 STATUT_RESIDENCE_CHOICES
= (
740 ('expat', 'Expatrié'),
743 COMPTE_COMPTA_CHOICES
= (
750 class Dossier_(DateActiviteMixin
, models
.Model
, DevisableMixin
,):
752 Le Dossier regroupe les informations relatives à l'occupation
753 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
756 Plusieurs Contrats peuvent être associés au Dossier.
757 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
758 lequel aucun Dossier n'existe est un poste vacant.
761 objects
= DossierManager()
764 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
765 organisme_bstg
= models
.ForeignKey(
766 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
767 verbose_name
=u
"organisme",
769 u
"Si détaché (DET) ou mis à disposition (MAD), "
770 u
"préciser l'organisme."
771 ), null
=True, blank
=True
775 remplacement
= models
.BooleanField(default
=False)
776 remplacement_de
= models
.ForeignKey(
777 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
778 null
=True, blank
=True
780 statut_residence
= models
.CharField(
781 u
"statut", max_length
=10, default
='local', null
=True,
782 choices
=STATUT_RESIDENCE_CHOICES
786 classement
= models
.ForeignKey(
787 'Classement', db_column
='classement', related_name
='+', null
=True,
790 regime_travail
= models
.DecimalField(
791 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
792 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
794 regime_travail_nb_heure_semaine
= models
.DecimalField(
795 u
"nb. heures par semaine", max_digits
=12,
796 decimal_places
=2, null
=True,
797 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
798 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
801 # Occupation du Poste par cet Employe (anciennement "mandat")
802 date_debut
= models
.DateField(
803 u
"date de début d'occupation de poste", db_index
=True
805 date_fin
= models
.DateField(
806 u
"Date de fin d'occupation de poste", null
=True, blank
=True,
815 ordering
= ['employe__nom', ]
816 verbose_name
= u
"Dossier"
817 verbose_name_plural
= "Dossiers"
819 def salaire_theorique(self
):
820 annee
= date
.today().year
821 coeff
= self
.classement
.coefficient
822 implantation
= self
.poste
.implantation
823 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
825 montant
= coeff
* point
.valeur
826 devise
= point
.devise
827 return {'montant': montant
, 'devise': devise
}
829 def __unicode__(self
):
830 poste
= self
.poste
.nom
831 if self
.employe
.genre
== 'F':
832 poste
= self
.poste
.nom_feminin
833 return u
'%s - %s' % (self
.employe
, poste
)
835 prefix_implantation
= "poste__implantation__zone_administrative"
837 def get_zones_administratives(self
):
838 return [self
.poste
.implantation
.zone_administrative
]
840 def remunerations(self
):
841 key
= "%s_remunerations" % self
._meta
.app_label
842 remunerations
= getattr(self
, key
)
843 return remunerations
.all().order_by('-date_debut')
845 def remunerations_en_cours(self
):
846 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
847 return self
.remunerations().all().filter(q
).order_by('date_debut')
849 def get_salaire(self
):
851 return [r
for r
in self
.remunerations().order_by('-date_debut')
852 if r
.type_id
== 1][0]
856 def get_salaire_euros(self
):
857 tx
= self
.taux_devise()
858 return (float)(tx
) * (float)(self
.salaire
)
860 def get_remunerations_brutes(self
):
864 4 Indemnité d'expatriation
865 5 Indemnité pour frais
866 6 Indemnité de logement
867 7 Indemnité de fonction
868 8 Indemnité de responsabilité
869 9 Indemnité de transport
870 10 Indemnité compensatrice
871 11 Indemnité de subsistance
872 12 Indemnité différentielle
873 13 Prime d'installation
876 16 Indemnité de départ
877 18 Prime de 13ième mois
880 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
881 return [r
for r
in self
.remunerations_en_cours().all()
884 def get_charges_salariales(self
):
886 20 Charges salariales ?
889 return [r
for r
in self
.remunerations_en_cours().all()
892 def get_charges_patronales(self
):
894 17 Charges patronales
897 return [r
for r
in self
.remunerations_en_cours().all()
900 def get_remunerations_tierces(self
):
904 return [r
for r
in self
.remunerations_en_cours().all()
905 if r
.type_id
in (2,)]
909 def get_total_local_charges_salariales(self
):
910 devise
= self
.poste
.get_devise()
912 for r
in self
.get_charges_salariales():
913 if r
.devise
!= devise
:
915 total
+= float(r
.montant
)
918 def get_total_local_charges_patronales(self
):
919 devise
= self
.poste
.get_devise()
921 for r
in self
.get_charges_patronales():
922 if r
.devise
!= devise
:
924 total
+= float(r
.montant
)
927 def get_local_salaire_brut(self
):
929 somme des rémuérations brutes
931 devise
= self
.poste
.get_devise()
933 for r
in self
.get_remunerations_brutes():
934 if r
.devise
!= devise
:
936 total
+= float(r
.montant
)
939 def get_local_salaire_net(self
):
941 salaire brut - charges salariales
943 devise
= self
.poste
.get_devise()
945 for r
in self
.get_charges_salariales():
946 if r
.devise
!= devise
:
948 total_charges
+= float(r
.montant
)
949 return self
.get_local_salaire_brut() - total_charges
951 def get_local_couts_auf(self
):
953 salaire net + charges patronales
955 devise
= self
.poste
.get_devise()
957 for r
in self
.get_charges_patronales():
958 if r
.devise
!= devise
:
960 total_charges
+= float(r
.montant
)
961 return self
.get_local_salaire_net() + total_charges
963 def get_total_local_remunerations_tierces(self
):
964 devise
= self
.poste
.get_devise()
966 for r
in self
.get_remunerations_tierces():
967 if r
.devise
!= devise
:
969 total
+= float(r
.montant
)
974 def get_total_charges_salariales(self
):
976 for r
in self
.get_charges_salariales():
977 total
+= r
.montant_euros()
980 def get_total_charges_patronales(self
):
982 for r
in self
.get_charges_patronales():
983 total
+= r
.montant_euros()
986 def get_salaire_brut(self
):
988 somme des rémuérations brutes
991 for r
in self
.get_remunerations_brutes():
992 total
+= r
.montant_euros()
995 def get_salaire_net(self
):
997 salaire brut - charges salariales
1000 for r
in self
.get_charges_salariales():
1001 total_charges
+= r
.montant_euros()
1002 return self
.get_salaire_brut() - total_charges
1004 def get_couts_auf(self
):
1006 salaire net + charges patronales
1009 for r
in self
.get_charges_patronales():
1010 total_charges
+= r
.montant_euros()
1011 return self
.get_salaire_net() + total_charges
1013 def get_total_remunerations_tierces(self
):
1015 for r
in self
.get_remunerations_tierces():
1016 total
+= r
.montant_euros()
1019 def premier_contrat(self
):
1020 """contrat avec plus petite date de début"""
1022 contrat
= self
.rh_contrats
.exclude(date_debut
=None) \
1023 .order_by('date_debut')[0]
1024 except IndexError, Contrat
.DoesNotExist
:
1028 def dernier_contrat(self
):
1029 """contrat avec plus grande date de fin"""
1031 contrat
= self
.rh_contrats
.exclude(date_debut
=None) \
1032 .order_by('-date_debut')[0]
1033 except IndexError, Contrat
.DoesNotExist
:
1038 today
= date
.today()
1039 return (self
.date_debut
is None or self
.date_debut
<= today
) \
1040 and (self
.date_fin
is None or self
.date_fin
>= today
) \
1041 and not (self
.date_fin
is None and self
.date_debut
is None)
1044 class Dossier(Dossier_
):
1045 __doc__
= Dossier_
.__doc__
1046 poste
= models
.ForeignKey(
1047 Poste
, db_column
='poste', related_name
='rh_dossiers',
1048 help_text
=u
"Taper le nom du poste ou du type de poste",
1050 employe
= models
.ForeignKey(
1051 'Employe', db_column
='employe',
1052 help_text
=u
"Taper le nom de l'employé",
1053 related_name
='rh_dossiers', verbose_name
=u
"employé"
1055 principal
= models
.BooleanField(
1056 u
"dossier principal", default
=True,
1058 u
"Ce dossier est pour le principal poste occupé par l'employé"
1062 reversion
.register(Dossier
, format
='xml', follow
=[
1063 'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
1064 'rh_contrats', 'commentaires'
1068 class DossierPiece_(models
.Model
):
1070 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1071 Ex.: Lettre de motivation.
1073 nom
= models
.CharField(max_length
=255)
1074 fichier
= models
.FileField(
1075 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
1082 def __unicode__(self
):
1083 return u
'%s' % (self
.nom
)
1086 class DossierPiece(DossierPiece_
):
1087 dossier
= models
.ForeignKey(
1088 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
1091 reversion
.register(DossierPiece
, format
='xml')
1093 class DossierCommentaire(Commentaire
):
1094 dossier
= models
.ForeignKey(
1095 Dossier
, db_column
='dossier', related_name
='commentaires'
1098 reversion
.register(DossierCommentaire
, format
='xml')
1101 class DossierComparaison_(models
.Model
, DevisableMixin
):
1103 Photo d'une comparaison salariale au moment de l'embauche.
1105 objects
= DossierComparaisonManager()
1107 implantation
= models
.ForeignKey(
1108 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1110 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1111 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1112 montant
= models
.IntegerField(null
=True)
1113 devise
= models
.ForeignKey(
1114 'Devise', related_name
='+', null
=True, blank
=True
1120 def __unicode__(self
):
1121 return "%s (%s)" % (self
.poste
, self
.personne
)
1124 class DossierComparaison(DossierComparaison_
):
1125 dossier
= models
.ForeignKey(
1126 Dossier
, related_name
='rh_comparaisons'
1129 reversion
.register(DossierComparaison
, format
='xml')
1134 class RemunerationMixin(models
.Model
):
1137 type = models
.ForeignKey(
1138 'TypeRemuneration', db_column
='type', related_name
='+',
1139 verbose_name
=u
"type de rémunération"
1141 type_revalorisation
= models
.ForeignKey(
1142 'TypeRevalorisation', db_column
='type_revalorisation',
1143 related_name
='+', verbose_name
=u
"type de revalorisation",
1144 null
=True, blank
=True
1146 montant
= models
.DecimalField(
1147 null
=True, blank
=True, max_digits
=12, decimal_places
=2
1148 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1149 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1151 # commentaire = precision
1152 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1154 # date_debut = anciennement date_effectif
1155 date_debut
= models
.DateField(
1156 u
"date de début", null
=True, blank
=True, db_index
=True
1158 date_fin
= models
.DateField(
1159 u
"date de fin", null
=True, blank
=True, db_index
=True
1164 ordering
= ['type__nom', '-date_fin']
1166 def __unicode__(self
):
1167 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1170 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1172 Structure de rémunération (données budgétaires) en situation normale
1173 pour un Dossier. Si un Evenement existe, utiliser la structure de
1174 rémunération EvenementRemuneration de cet événement.
1176 objects
= RemunerationManager()
1178 def montant_mois(self
):
1179 return round(self
.montant
/ 12, 2)
1181 def montant_avec_regime(self
):
1182 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1184 def montant_euro_mois(self
):
1185 return round(self
.montant_euros() / 12, 2)
1187 def __unicode__(self
):
1189 devise
= self
.devise
.code
1192 return "%s %s" % (self
.montant
, devise
)
1196 verbose_name
= u
"Rémunération"
1197 verbose_name_plural
= u
"Rémunérations"
1200 class Remuneration(Remuneration_
):
1201 dossier
= models
.ForeignKey(
1202 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1205 reversion
.register(Remuneration
, format
='xml')
1210 class Contrat_(models
.Model
):
1212 Document juridique qui encadre la relation de travail d'un Employe
1213 pour un Poste particulier. Pour un Dossier (qui documente cette
1214 relation de travail) plusieurs contrats peuvent être associés.
1216 objects
= ContratManager()
1217 type_contrat
= models
.ForeignKey(
1218 'TypeContrat', db_column
='type_contrat',
1219 verbose_name
=u
'type de contrat', related_name
='+'
1221 date_debut
= models
.DateField(
1222 u
"date de début", db_index
=True
1224 date_fin
= models
.DateField(
1225 u
"date de fin", null
=True, blank
=True, db_index
=True
1227 fichier
= models
.FileField(
1228 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1234 ordering
= ['dossier__employe__nom']
1235 verbose_name
= u
"Contrat"
1236 verbose_name_plural
= u
"Contrats"
1238 def __unicode__(self
):
1239 return u
'%s - %s' % (self
.dossier
, self
.id)
1242 class Contrat(Contrat_
):
1243 dossier
= models
.ForeignKey(
1244 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1247 reversion
.register(Contrat
, format
='xml')
1252 class CategorieEmploi(models
.Model
):
1254 Catégorie utilisée dans la gestion des Postes.
1255 Catégorie supérieure à TypePoste.
1257 nom
= models
.CharField(max_length
=255)
1261 verbose_name
= u
"catégorie d'emploi"
1262 verbose_name_plural
= u
"catégories d'emploi"
1264 def __unicode__(self
):
1267 reversion
.register(CategorieEmploi
, format
='xml')
1270 class FamilleProfessionnelle(models
.Model
):
1272 Famille professionnelle d'un poste.
1274 nom
= models
.CharField(max_length
=100)
1278 verbose_name
= u
'famille professionnelle'
1279 verbose_name_plural
= u
'familles professionnelles'
1281 def __unicode__(self
):
1284 reversion
.register(FamilleProfessionnelle
, format
='xml')
1287 class TypePoste(Archivable
):
1291 nom
= models
.CharField(max_length
=255)
1292 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1293 is_responsable
= models
.BooleanField(
1294 u
"poste de responsabilité", default
=False
1296 categorie_emploi
= models
.ForeignKey(
1297 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1298 verbose_name
=u
"catégorie d'emploi"
1300 famille_professionnelle
= models
.ForeignKey(
1301 FamilleProfessionnelle
, related_name
='types_de_poste',
1302 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1307 verbose_name
= u
"Type de poste"
1308 verbose_name_plural
= u
"Types de poste"
1310 def __unicode__(self
):
1311 return u
'%s' % (self
.nom
)
1313 reversion
.register(TypePoste
, format
='xml')
1316 TYPE_PAIEMENT_CHOICES
= (
1317 (u
'Régulier', u
'Régulier'),
1318 (u
'Ponctuel', u
'Ponctuel'),
1321 NATURE_REMUNERATION_CHOICES
= (
1322 (u
'Accessoire', u
'Accessoire'),
1323 (u
'Charges', u
'Charges'),
1324 (u
'Indemnité', u
'Indemnité'),
1325 (u
'RAS', u
'Rémunération autre source'),
1326 (u
'Traitement', u
'Traitement'),
1330 class TypeRemuneration(Archivable
):
1332 Catégorie de Remuneration.
1335 nom
= models
.CharField(max_length
=255)
1336 type_paiement
= models
.CharField(
1337 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1339 nature_remuneration
= models
.CharField(
1340 u
"nature de la rémunération", max_length
=30,
1341 choices
=NATURE_REMUNERATION_CHOICES
1346 verbose_name
= u
"Type de rémunération"
1347 verbose_name_plural
= u
"Types de rémunération"
1349 def __unicode__(self
):
1352 reversion
.register(TypeRemuneration
, format
='xml')
1355 class TypeRevalorisation(Archivable
):
1357 Justification du changement de la Remuneration.
1358 (Actuellement utilisé dans aucun traitement informatique.)
1360 nom
= models
.CharField(max_length
=255)
1364 verbose_name
= u
"Type de revalorisation"
1365 verbose_name_plural
= u
"Types de revalorisation"
1367 def __unicode__(self
):
1368 return u
'%s' % (self
.nom
)
1370 reversion
.register(TypeRevalorisation
, format
='xml')
1373 class Service(Archivable
):
1375 Unité administrative où les Postes sont rattachés.
1377 nom
= models
.CharField(max_length
=255)
1381 verbose_name
= u
"service"
1382 verbose_name_plural
= u
"services"
1384 def __unicode__(self
):
1387 reversion
.register(Service
, format
='xml')
1390 TYPE_ORGANISME_CHOICES
= (
1391 ('MAD', 'Mise à disposition'),
1392 ('DET', 'Détachement'),
1396 class OrganismeBstg(models
.Model
):
1398 Organisation d'où provient un Employe mis à disposition (MAD) de
1399 ou détaché (DET) à l'AUF à titre gratuit.
1401 (BSTG = bien et service à titre gratuit.)
1403 nom
= models
.CharField(max_length
=255)
1404 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1405 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1407 related_name
='organismes_bstg',
1408 null
=True, blank
=True)
1411 ordering
= ['type', 'nom']
1412 verbose_name
= u
"Organisme BSTG"
1413 verbose_name_plural
= u
"Organismes BSTG"
1415 def __unicode__(self
):
1416 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1418 reversion
.register(OrganismeBstg
, format
='xml')
1421 class Statut(Archivable
):
1423 Statut de l'Employe dans le cadre d'un Dossier particulier.
1426 code
= models
.CharField(
1427 max_length
=25, unique
=True,
1429 u
"Saisir un code court mais lisible pour ce statut : "
1430 u
"le code est utilisé pour associer les statuts aux autres "
1431 u
"données tout en demeurant plus lisible qu'un identifiant "
1435 nom
= models
.CharField(max_length
=255)
1439 verbose_name
= u
"Statut d'employé"
1440 verbose_name_plural
= u
"Statuts d'employé"
1442 def __unicode__(self
):
1443 return u
'%s : %s' % (self
.code
, self
.nom
)
1445 reversion
.register(Statut
, format
='xml')
1448 TYPE_CLASSEMENT_CHOICES
= (
1449 ('S', 'S -Soutien'),
1450 ('T', 'T - Technicien'),
1451 ('P', 'P - Professionel'),
1453 ('D', 'D - Direction'),
1454 ('SO', 'SO - Sans objet [expatriés]'),
1455 ('HG', 'HG - Hors grille [direction]'),
1459 class ClassementManager(ArchivableManager
):
1461 Ordonner les spcéfiquement les classements.
1463 def get_query_set(self
):
1464 qs
= super(self
.__class__
, self
).get_query_set()
1465 qs
= qs
.extra(select
={
1466 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1468 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1472 class Classement_(Archivable
):
1474 Éléments de classement de la
1475 "Grille générique de classement hiérarchique".
1477 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1478 classement dans la grille. Le classement donne le coefficient utilisé dans:
1480 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1482 objects
= ClassementManager()
1485 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1486 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1487 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1488 coefficient
= models
.FloatField(u
"coefficient", blank
=True, null
=True)
1491 # annee # au lieu de date_debut et date_fin
1492 commentaire
= models
.TextField(null
=True, blank
=True)
1496 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1497 verbose_name
= u
"Classement"
1498 verbose_name_plural
= u
"Classements"
1500 def __unicode__(self
):
1501 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1504 class Classement(Classement_
):
1505 __doc__
= Classement_
.__doc__
1507 reversion
.register(Classement
, format
='xml')
1510 class TauxChange_(models
.Model
):
1512 Taux de change de la devise vers l'euro (EUR)
1513 pour chaque année budgétaire.
1516 devise
= models
.ForeignKey('Devise', db_column
='devise')
1517 annee
= models
.IntegerField(u
"année")
1518 taux
= models
.FloatField(u
"taux vers l'euro")
1522 ordering
= ['-annee', 'devise__code']
1523 verbose_name
= u
"Taux de change"
1524 verbose_name_plural
= u
"Taux de change"
1525 unique_together
= ('devise', 'annee')
1527 def __unicode__(self
):
1528 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1531 class TauxChange(TauxChange_
):
1532 __doc__
= TauxChange_
.__doc__
1534 reversion
.register(TauxChange
, format
='xml')
1537 class ValeurPointManager(models
.Manager
):
1539 def get_query_set(self
):
1540 return super(ValeurPointManager
, self
).get_query_set() \
1541 .select_related('devise', 'implantation')
1544 class ValeurPoint_(models
.Model
):
1546 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1547 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1548 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1550 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1553 objects
= models
.Manager()
1554 actuelles
= ValeurPointManager()
1556 valeur
= models
.FloatField(null
=True)
1557 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1558 implantation
= models
.ForeignKey(ref
.Implantation
,
1559 db_column
='implantation',
1560 related_name
='%(app_label)s_valeur_point')
1562 annee
= models
.IntegerField()
1565 ordering
= ['-annee', 'implantation__nom']
1567 verbose_name
= u
"Valeur du point"
1568 verbose_name_plural
= u
"Valeurs du point"
1569 unique_together
= ('implantation', 'annee')
1571 def __unicode__(self
):
1572 return u
'%s %s %s [%s] %s' % (
1573 self
.devise
.code
, self
.annee
, self
.valeur
,
1574 self
.implantation
.nom_court
, self
.devise
.nom
1578 class ValeurPoint(ValeurPoint_
):
1579 __doc__
= ValeurPoint_
.__doc__
1581 reversion
.register(ValeurPoint
, format
='xml')
1584 class Devise(Archivable
):
1588 code
= models
.CharField(max_length
=10, unique
=True)
1589 nom
= models
.CharField(max_length
=255)
1593 verbose_name
= u
"devise"
1594 verbose_name_plural
= u
"devises"
1596 def __unicode__(self
):
1597 return u
'%s - %s' % (self
.code
, self
.nom
)
1599 reversion
.register(Devise
, format
='xml')
1602 class TypeContrat(Archivable
):
1606 nom
= models
.CharField(max_length
=255)
1607 nom_long
= models
.CharField(max_length
=255)
1611 verbose_name
= u
"Type de contrat"
1612 verbose_name_plural
= u
"Types de contrat"
1614 def __unicode__(self
):
1615 return u
'%s' % (self
.nom
)
1617 reversion
.register(TypeContrat
, format
='xml')
1622 class ResponsableImplantationProxy(ref
.Implantation
):
1630 verbose_name
= u
"Responsable d'implantation"
1631 verbose_name_plural
= u
"Responsables d'implantation"
1634 class ResponsableImplantation(models
.Model
):
1636 Le responsable d'une implantation.
1637 Anciennement géré sur le Dossier du responsable.
1639 employe
= models
.ForeignKey(
1640 'Employe', db_column
='employe', related_name
='+', null
=True,
1643 implantation
= models
.OneToOneField(
1644 "ResponsableImplantationProxy", db_column
='implantation',
1645 related_name
='responsable', unique
=True
1648 def __unicode__(self
):
1649 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1652 ordering
= ['implantation__nom']
1653 verbose_name
= "Responsable d'implantation"
1654 verbose_name_plural
= "Responsables d'implantation"
1656 reversion
.register(ResponsableImplantation
, format
='xml')