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
.contrib
.auth
.models
import User
12 from django
.core
.files
.storage
import FileSystemStorage
13 from django
.db
import models
14 from django
.db
.models
import Q
15 from django
.conf
import settings
17 from project
.rh
.change_list
import \
18 RechercheTemporelle
, KEY_STATUT
, STATUT_ACTIF
, STATUT_INACTIF
, \
20 from project
import groups
21 from project
.rh
.managers
import (
25 DossierComparaisonManager
,
26 PosteComparaisonManager
,
33 from project
.rh
.validators
import validate_date_passee
35 # import pour relocaliser le modèle selon la convention (models.py pour
37 from project
.rh
.historique
import ModificationTraite
40 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
41 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
42 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
43 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
= \
44 "Saisir le nombre d'heure de travail à temps complet (100%), " \
45 "sans tenir compte du régime de travail"
48 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
49 base_url
=settings
.PRIVE_MEDIA_URL
)
52 class RemunIntegrityException(Exception):
55 def poste_piece_dispatch(instance
, filename
):
56 path
= "%s/poste/%s/%s" % (
57 instance
._meta
.app_label
, instance
.poste_id
, filename
62 def dossier_piece_dispatch(instance
, filename
):
63 path
= "%s/dossier/%s/%s" % (
64 instance
._meta
.app_label
, instance
.dossier_id
, filename
69 def employe_piece_dispatch(instance
, filename
):
70 path
= "%s/employe/%s/%s" % (
71 instance
._meta
.app_label
, instance
.employe_id
, filename
76 def contrat_dispatch(instance
, filename
):
77 path
= "%s/contrat/%s/%s" % (
78 instance
._meta
.app_label
, instance
.dossier_id
, filename
83 class DateActiviteMixin(models
.Model
):
85 Mixin pour mettre à jour l'activité d'un modèle
89 date_creation
= models
.DateTimeField(auto_now_add
=True,
90 null
=True, blank
=True,
91 verbose_name
=u
"Date de création",)
92 date_modification
= models
.DateTimeField(auto_now
=True,
93 null
=True, blank
=True,
94 verbose_name
=u
"Date de modification",)
97 class Archivable(models
.Model
):
98 archive
= models
.BooleanField(u
'archivé', default
=False)
100 objects
= ArchivableManager()
101 avec_archives
= models
.Manager()
107 class DevisableMixin(object):
109 def get_annee_pour_taux_devise(self
):
110 return datetime
.datetime
.now().year
112 def taux_devise(self
, devise
=None):
118 if devise
.code
== "EUR":
121 annee
= self
.get_annee_pour_taux_devise()
122 taux
= TauxChange
.objects
.filter(devise
=devise
, annee__lte
=annee
) \
126 def montant_euros(self
):
128 taux
= self
.taux_devise()
133 return int(round(float(self
.montant
) * float(taux
), 2))
136 class Commentaire(models
.Model
):
137 texte
= models
.TextField()
138 owner
= models
.ForeignKey(
139 'auth.User', db_column
='owner', related_name
='+',
140 verbose_name
=u
"Commentaire de"
142 date_creation
= models
.DateTimeField(
143 u
'date', auto_now_add
=True, blank
=True, null
=True
148 ordering
= ['-date_creation']
150 def __unicode__(self
):
151 return u
'%s' % (self
.texte
)
156 POSTE_APPEL_CHOICES
= (
157 ('interne', 'Interne'),
158 ('externe', 'Externe'),
162 class Poste_( DateActiviteMixin
, models
.Model
,):
164 Un Poste est un emploi (job) à combler dans une implantation.
165 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
166 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
169 objects
= PosteManager()
172 nom
= models
.CharField(u
"Titre du poste", max_length
=255)
173 nom_feminin
= models
.CharField(
174 u
"Titre du poste (au féminin)", max_length
=255, null
=True
176 implantation
= models
.ForeignKey(
178 help_text
=u
"Taper le nom de l'implantation ou sa région",
179 db_column
='implantation', related_name
='+'
181 type_poste
= models
.ForeignKey(
182 'TypePoste', db_column
='type_poste',
183 help_text
=u
"Taper le nom du type de poste", related_name
='+',
184 null
=True, verbose_name
=u
"type de poste"
186 service
= models
.ForeignKey(
187 'Service', db_column
='service', related_name
='%(app_label)s_postes',
188 verbose_name
=u
"direction/service/pôle support", null
=True
190 responsable
= models
.ForeignKey(
191 'Poste', db_column
='responsable',
192 related_name
='+', null
=True,
193 help_text
=u
"Taper le nom du poste ou du type de poste",
194 verbose_name
=u
"Poste du responsable"
198 regime_travail
= models
.DecimalField(
199 u
"temps de travail", max_digits
=12, decimal_places
=2,
200 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
201 help_text
="% du temps complet"
203 regime_travail_nb_heure_semaine
= models
.DecimalField(
204 u
"nb. heures par semaine", max_digits
=12, decimal_places
=2,
205 null
=True, default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
206 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
210 local
= models
.NullBooleanField(
211 u
"local", default
=True, null
=True, blank
=True
213 expatrie
= models
.NullBooleanField(
214 u
"expatrié", default
=False, null
=True, blank
=True
216 mise_a_disposition
= models
.NullBooleanField(
217 u
"mise à disposition", null
=True, default
=False
219 appel
= models
.CharField(
220 u
"Appel à candidature", max_length
=10, null
=True,
221 choices
=POSTE_APPEL_CHOICES
, default
='interne'
225 classement_min
= models
.ForeignKey(
226 'Classement', db_column
='classement_min', related_name
='+',
227 null
=True, blank
=True
229 classement_max
= models
.ForeignKey(
230 'Classement', db_column
='classement_max', related_name
='+',
231 null
=True, blank
=True
233 valeur_point_min
= models
.ForeignKey(
235 help_text
=u
"Taper le code ou le nom de l'implantation",
236 db_column
='valeur_point_min', related_name
='+', null
=True,
239 valeur_point_max
= models
.ForeignKey(
241 help_text
=u
"Taper le code ou le nom de l'implantation",
242 db_column
='valeur_point_max', related_name
='+', null
=True,
245 devise_min
= models
.ForeignKey(
246 'Devise', db_column
='devise_min', null
=True, related_name
='+'
248 devise_max
= models
.ForeignKey(
249 'Devise', db_column
='devise_max', null
=True, related_name
='+'
251 salaire_min
= models
.DecimalField(
252 max_digits
=12, decimal_places
=2, default
=0,
254 salaire_max
= models
.DecimalField(
255 max_digits
=12, decimal_places
=2, default
=0,
257 indemn_min
= models
.DecimalField(
258 max_digits
=12, decimal_places
=2, default
=0,
260 indemn_max
= models
.DecimalField(
261 max_digits
=12, decimal_places
=2, default
=0,
263 autre_min
= models
.DecimalField(
264 max_digits
=12, decimal_places
=2, default
=0,
266 autre_max
= models
.DecimalField(
267 max_digits
=12, decimal_places
=2, default
=0,
270 # Comparatifs de rémunération
271 devise_comparaison
= models
.ForeignKey(
272 'Devise', null
=True, blank
=True, db_column
='devise_comparaison',
275 comp_locale_min
= models
.DecimalField(
276 max_digits
=12, decimal_places
=2, null
=True, blank
=True
278 comp_locale_max
= models
.DecimalField(
279 max_digits
=12, decimal_places
=2, null
=True, blank
=True
281 comp_universite_min
= models
.DecimalField(
282 max_digits
=12, decimal_places
=2, null
=True, blank
=True
284 comp_universite_max
= models
.DecimalField(
285 max_digits
=12, decimal_places
=2, null
=True, blank
=True
287 comp_fonctionpub_min
= models
.DecimalField(
288 max_digits
=12, decimal_places
=2, null
=True, blank
=True
290 comp_fonctionpub_max
= models
.DecimalField(
291 max_digits
=12, decimal_places
=2, null
=True, blank
=True
293 comp_ong_min
= models
.DecimalField(
294 max_digits
=12, decimal_places
=2, null
=True, blank
=True
296 comp_ong_max
= models
.DecimalField(
297 max_digits
=12, decimal_places
=2, null
=True, blank
=True
299 comp_autre_min
= models
.DecimalField(
300 max_digits
=12, decimal_places
=2, null
=True, blank
=True
302 comp_autre_max
= models
.DecimalField(
303 max_digits
=12, decimal_places
=2, null
=True, blank
=True
307 justification
= models
.TextField(null
=True, blank
=True)
310 date_debut
= models
.DateField(
311 u
"date de début", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True,
314 date_fin
= models
.DateField(
315 u
"date de fin", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True,
321 ordering
= ['implantation__nom', 'nom']
322 verbose_name
= u
"Poste"
323 verbose_name_plural
= u
"Postes"
326 def __unicode__(self
):
327 representation
= u
'%s - %s [%s]' % (
328 self
.implantation
, self
.nom
, self
.id
330 return representation
332 prefix_implantation
= "implantation__zone_administrative"
334 def get_zones_administratives(self
):
335 return [self
.implantation
.zone_administrative
]
337 def get_devise(self
):
338 vp
= ValeurPoint
.objects
.filter(
339 implantation
=self
.implantation
, devise__archive
=False
344 return Devise
.objects
.get(code
='EUR')
348 __doc__
= Poste_
.__doc__
350 # meta dématérialisation : pour permettre le filtrage
351 vacant
= models
.NullBooleanField(u
"vacant", null
=True, blank
=True)
355 if self
.occupe_par():
359 def occupe_par(self
):
361 Retourne la liste d'employé occupant ce poste.
362 Généralement, retourne une liste d'un élément.
363 Si poste inoccupé, retourne liste vide.
364 UTILISE pour mettre a jour le flag vacant
368 for d
in self
.rh_dossiers
.exclude(date_fin__lt
=date
.today())
371 reversion
.register(Poste
, format
='xml', follow
=[
372 'rh_financements', 'rh_pieces', 'rh_comparaisons_internes',
377 POSTE_FINANCEMENT_CHOICES
= (
378 ('A', 'A - Frais de personnel'),
379 ('B', 'B - Projet(s)-Titre(s)'),
384 class PosteFinancement_(models
.Model
):
386 Pour un Poste, structure d'informations décrivant comment on prévoit
389 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
390 pourcentage
= models
.DecimalField(
391 max_digits
=12, decimal_places
=2,
392 help_text
="ex.: 33.33 % (décimale avec point)"
394 commentaire
= models
.TextField(
395 help_text
="Spécifiez la source de financement."
402 def __unicode__(self
):
403 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
406 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
409 class PosteFinancement(PosteFinancement_
):
410 poste
= models
.ForeignKey(
411 Poste
, db_column
='poste', related_name
='rh_financements'
414 reversion
.register(PosteFinancement
, format
='xml')
417 class PostePiece_(models
.Model
):
419 Documents relatifs au Poste.
420 Ex.: Description de poste
422 nom
= models
.CharField(u
"Nom", max_length
=255)
423 fichier
= models
.FileField(
424 u
"Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
431 def __unicode__(self
):
432 return u
'%s' % (self
.nom
)
435 class PostePiece(PostePiece_
):
436 poste
= models
.ForeignKey(
437 Poste
, db_column
='poste', related_name
='rh_pieces'
440 reversion
.register(PostePiece
, format
='xml')
443 class PosteComparaison_(models
.Model
, DevisableMixin
):
445 De la même manière qu'un dossier, un poste peut-être comparé à un autre
448 objects
= PosteComparaisonManager()
450 implantation
= models
.ForeignKey(
451 ref
.Implantation
, null
=True, blank
=True, related_name
="+"
453 nom
= models
.CharField(u
"Poste", max_length
=255, null
=True, blank
=True)
454 montant
= models
.IntegerField(null
=True)
455 devise
= models
.ForeignKey(
456 "Devise", related_name
='+', null
=True, blank
=True
462 def __unicode__(self
):
466 class PosteComparaison(PosteComparaison_
):
467 poste
= models
.ForeignKey(
468 Poste
, related_name
='rh_comparaisons_internes'
471 reversion
.register(PosteComparaison
, format
='xml')
474 class PosteCommentaire(Commentaire
):
475 poste
= models
.ForeignKey(
476 Poste
, db_column
='poste', related_name
='commentaires'
479 reversion
.register(PosteCommentaire
, format
='xml')
483 class Employe(models
.Model
):
485 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
486 Dossiers qu'il occupe ou a occupé de Postes.
488 Cette classe aurait pu avantageusement s'appeler Personne car la notion
489 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
492 objects
= EmployeManager()
495 nom
= models
.CharField(max_length
=255)
496 prenom
= models
.CharField(u
"prénom", max_length
=255)
497 nom_affichage
= models
.CharField(
498 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
500 nationalite
= models
.ForeignKey(
501 ref
.Pays
, to_field
='code', db_column
='nationalite',
502 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
503 blank
=True, null
=True
505 date_naissance
= models
.DateField(
506 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
507 validators
=[validate_date_passee
], null
=True, blank
=True
509 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
512 situation_famille
= models
.CharField(
513 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
514 null
=True, blank
=True
516 date_entree
= models
.DateField(
517 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
522 tel_domicile
= models
.CharField(
523 u
"tél. domicile", max_length
=255, null
=True, blank
=True
525 tel_cellulaire
= models
.CharField(
526 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
528 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
529 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
530 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
531 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
532 pays
= models
.ForeignKey(
533 ref
.Pays
, to_field
='code', db_column
='pays',
534 related_name
='employes', null
=True, blank
=True
536 courriel_perso
= models
.EmailField(
537 u
'adresse courriel personnelle', blank
=True
540 # meta dématérialisation : pour permettre le filtrage
541 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
544 ordering
= ['nom', 'prenom']
545 verbose_name
= u
"Employé"
546 verbose_name_plural
= u
"Employés"
548 def __unicode__(self
):
549 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
553 if self
.genre
.upper() == u
'M':
555 elif self
.genre
.upper() == u
'F':
561 Retourne l'URL du service retournant la photo de l'Employe.
562 Équivalent reverse url 'rh_photo' avec id en param.
564 from django
.core
.urlresolvers
import reverse
565 return reverse('rh_photo', kwargs
={'id': self
.id})
567 def dossiers_passes(self
):
568 params
= {KEY_STATUT
: STATUT_INACTIF
, }
569 search
= RechercheTemporelle(params
, Dossier
)
570 search
.purge_params(params
)
571 q
= search
.get_q_temporel(self
.rh_dossiers
)
572 return self
.rh_dossiers
.filter(q
)
574 def dossiers_futurs(self
):
575 params
= {KEY_STATUT
: STATUT_FUTUR
, }
576 search
= RechercheTemporelle(params
, Dossier
)
577 search
.purge_params(params
)
578 q
= search
.get_q_temporel(self
.rh_dossiers
)
579 return self
.rh_dossiers
.filter(q
)
581 def dossiers_encours(self
):
582 params
= {KEY_STATUT
: STATUT_ACTIF
, }
583 search
= RechercheTemporelle(params
, Dossier
)
584 search
.purge_params(params
)
585 q
= search
.get_q_temporel(self
.rh_dossiers
)
586 return self
.rh_dossiers
.filter(q
)
588 def dossier_principal(self
):
590 Retourne le dossier principal (ou le plus ancien si il y en a
594 dossier
= self
.rh_dossiers \
595 .filter(principal
=True).order_by('date_debut')[0]
596 except IndexError, Dossier
.DoesNotExist
:
600 def postes_encours(self
):
601 postes_encours
= set()
602 for d
in self
.dossiers_encours():
603 postes_encours
.add(d
.poste
)
604 return postes_encours
606 def poste_principal(self
):
608 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
610 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
612 # DEPRECATED : on a maintenant Dossier.principal
613 poste
= Poste
.objects
.none()
615 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
620 prefix_implantation
= \
621 "rh_dossiers__poste__implantation__zone_administrative"
623 def get_zones_administratives(self
):
625 d
.poste
.implantation
.zone_administrative
626 for d
in self
.dossiers
.all()
629 reversion
.register(Employe
, format
='xml', follow
=[
630 'pieces', 'commentaires', 'ayantdroits'
634 class EmployePiece(models
.Model
):
636 Documents relatifs à un employé.
639 employe
= models
.ForeignKey(
640 'Employe', db_column
='employe', related_name
="pieces",
641 verbose_name
=u
"employé"
643 nom
= models
.CharField(max_length
=255)
644 fichier
= models
.FileField(
645 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
650 verbose_name
= u
"Employé pièce"
651 verbose_name_plural
= u
"Employé pièces"
653 def __unicode__(self
):
654 return u
'%s' % (self
.nom
)
656 reversion
.register(EmployePiece
, format
='xml')
659 class EmployeCommentaire(Commentaire
):
660 employe
= models
.ForeignKey(
661 'Employe', db_column
='employe', related_name
='commentaires'
665 verbose_name
= u
"Employé commentaire"
666 verbose_name_plural
= u
"Employé commentaires"
668 reversion
.register(EmployeCommentaire
, format
='xml')
671 LIEN_PARENTE_CHOICES
= (
672 ('Conjoint', 'Conjoint'),
673 ('Conjointe', 'Conjointe'),
679 class AyantDroit(models
.Model
):
681 Personne en relation avec un Employe.
684 nom
= models
.CharField(max_length
=255)
685 prenom
= models
.CharField(u
"prénom", max_length
=255)
686 nom_affichage
= models
.CharField(
687 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
689 nationalite
= models
.ForeignKey(
690 ref
.Pays
, to_field
='code', db_column
='nationalite',
691 related_name
='ayantdroits_nationalite',
692 verbose_name
=u
"nationalité", null
=True, blank
=True
694 date_naissance
= models
.DateField(
695 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
696 validators
=[validate_date_passee
], null
=True, blank
=True
698 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
701 employe
= models
.ForeignKey(
702 'Employe', db_column
='employe', related_name
='ayantdroits',
703 verbose_name
=u
"Employé"
705 lien_parente
= models
.CharField(
706 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
707 null
=True, blank
=True
712 verbose_name
= u
"Ayant droit"
713 verbose_name_plural
= u
"Ayants droit"
715 def __unicode__(self
):
716 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
718 prefix_implantation
= \
719 "employe__dossiers__poste__implantation__zone_administrative"
721 def get_zones_administratives(self
):
723 d
.poste
.implantation
.zone_administrative
724 for d
in self
.employe
.dossiers
.all()
727 reversion
.register(AyantDroit
, format
='xml', follow
=['commentaires'])
730 class AyantDroitCommentaire(Commentaire
):
731 ayant_droit
= models
.ForeignKey(
732 'AyantDroit', db_column
='ayant_droit', related_name
='commentaires'
735 reversion
.register(AyantDroitCommentaire
, format
='xml')
740 STATUT_RESIDENCE_CHOICES
= (
742 ('expat', 'Expatrié'),
745 COMPTE_COMPTA_CHOICES
= (
752 class Dossier_(DateActiviteMixin
, models
.Model
, DevisableMixin
,):
754 Le Dossier regroupe les informations relatives à l'occupation
755 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
758 Plusieurs Contrats peuvent être associés au Dossier.
759 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
760 lequel aucun Dossier n'existe est un poste vacant.
763 objects
= DossierManager()
766 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
767 organisme_bstg
= models
.ForeignKey(
768 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
769 verbose_name
=u
"organisme",
771 u
"Si détaché (DET) ou mis à disposition (MAD), "
772 u
"préciser l'organisme."
773 ), null
=True, blank
=True
777 remplacement
= models
.BooleanField(default
=False)
778 remplacement_de
= models
.ForeignKey(
779 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
780 null
=True, blank
=True
782 statut_residence
= models
.CharField(
783 u
"statut", max_length
=10, default
='local', null
=True,
784 choices
=STATUT_RESIDENCE_CHOICES
788 classement
= models
.ForeignKey(
789 'Classement', db_column
='classement', related_name
='+', null
=True,
792 regime_travail
= models
.DecimalField(
793 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
794 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
796 regime_travail_nb_heure_semaine
= models
.DecimalField(
797 u
"nb. heures par semaine", max_digits
=12,
798 decimal_places
=2, null
=True,
799 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
800 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
803 # Occupation du Poste par cet Employe (anciennement "mandat")
804 date_debut
= models
.DateField(
805 u
"date de début d'occupation de poste", db_index
=True
807 date_fin
= models
.DateField(
808 u
"Date de fin d'occupation de poste", null
=True, blank
=True,
817 ordering
= ['employe__nom', ]
818 verbose_name
= u
"Dossier"
819 verbose_name_plural
= "Dossiers"
821 def salaire_theorique(self
):
822 annee
= date
.today().year
823 coeff
= self
.classement
.coefficient
824 implantation
= self
.poste
.implantation
825 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
827 montant
= coeff
* point
.valeur
828 devise
= point
.devise
829 return {'montant': montant
, 'devise': devise
}
831 def __unicode__(self
):
832 poste
= self
.poste
.nom
833 if self
.employe
.genre
== 'F':
834 poste
= self
.poste
.nom_feminin
835 return u
'%s - %s' % (self
.employe
, poste
)
837 prefix_implantation
= "poste__implantation__zone_administrative"
839 def get_zones_administratives(self
):
840 return [self
.poste
.implantation
.zone_administrative
]
842 def remunerations(self
):
843 key
= "%s_remunerations" % self
._meta
.app_label
844 remunerations
= getattr(self
, key
)
845 return remunerations
.all().order_by('-date_debut')
847 def remunerations_en_cours(self
):
848 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
849 return self
.remunerations().all().filter(q
).order_by('date_debut')
851 def get_salaire(self
):
853 return [r
for r
in self
.remunerations().order_by('-date_debut')
854 if r
.type_id
== 1][0]
858 def get_salaire_euros(self
):
859 tx
= self
.taux_devise()
860 return (float)(tx
) * (float)(self
.salaire
)
862 def get_remunerations_brutes(self
):
866 4 Indemnité d'expatriation
867 5 Indemnité pour frais
868 6 Indemnité de logement
869 7 Indemnité de fonction
870 8 Indemnité de responsabilité
871 9 Indemnité de transport
872 10 Indemnité compensatrice
873 11 Indemnité de subsistance
874 12 Indemnité différentielle
875 13 Prime d'installation
878 16 Indemnité de départ
879 18 Prime de 13ième mois
882 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
883 return [r
for r
in self
.remunerations_en_cours().all()
886 def get_charges_salariales(self
):
888 20 Charges salariales ?
891 return [r
for r
in self
.remunerations_en_cours().all()
894 def get_charges_patronales(self
):
896 17 Charges patronales
899 return [r
for r
in self
.remunerations_en_cours().all()
902 def get_remunerations_tierces(self
):
906 return [r
for r
in self
.remunerations_en_cours().all()
907 if r
.type_id
in (2,)]
911 def get_total_local_charges_salariales(self
):
912 devise
= self
.poste
.get_devise()
914 for r
in self
.get_charges_salariales():
915 if r
.devise
!= devise
:
917 total
+= float(r
.montant
)
920 def get_total_local_charges_patronales(self
):
921 devise
= self
.poste
.get_devise()
923 for r
in self
.get_charges_patronales():
924 if r
.devise
!= devise
:
926 total
+= float(r
.montant
)
929 def get_local_salaire_brut(self
):
931 somme des rémuérations brutes
933 devise
= self
.poste
.get_devise()
935 for r
in self
.get_remunerations_brutes():
936 if r
.devise
!= devise
:
938 total
+= float(r
.montant
)
941 def get_local_salaire_net(self
):
943 salaire brut - charges salariales
945 devise
= self
.poste
.get_devise()
947 for r
in self
.get_charges_salariales():
948 if r
.devise
!= devise
:
950 total_charges
+= float(r
.montant
)
951 return self
.get_local_salaire_brut() - total_charges
953 def get_local_couts_auf(self
):
955 salaire net + charges patronales
957 devise
= self
.poste
.get_devise()
959 for r
in self
.get_charges_patronales():
960 if r
.devise
!= devise
:
962 total_charges
+= float(r
.montant
)
963 return self
.get_local_salaire_net() + total_charges
965 def get_total_local_remunerations_tierces(self
):
966 devise
= self
.poste
.get_devise()
968 for r
in self
.get_remunerations_tierces():
969 if r
.devise
!= devise
:
971 total
+= float(r
.montant
)
976 def get_total_charges_salariales(self
):
978 for r
in self
.get_charges_salariales():
979 total
+= r
.montant_euros()
982 def get_total_charges_patronales(self
):
984 for r
in self
.get_charges_patronales():
985 total
+= r
.montant_euros()
988 def get_salaire_brut(self
):
990 somme des rémuérations brutes
993 for r
in self
.get_remunerations_brutes():
994 total
+= r
.montant_euros()
997 def get_salaire_net(self
):
999 salaire brut - charges salariales
1002 for r
in self
.get_charges_salariales():
1003 total_charges
+= r
.montant_euros()
1004 return self
.get_salaire_brut() - total_charges
1006 def get_couts_auf(self
):
1008 salaire net + charges patronales
1011 for r
in self
.get_charges_patronales():
1012 total_charges
+= r
.montant_euros()
1013 return self
.get_salaire_net() + total_charges
1015 def get_total_remunerations_tierces(self
):
1017 for r
in self
.get_remunerations_tierces():
1018 total
+= r
.montant_euros()
1021 def premier_contrat(self
):
1022 """contrat avec plus petite date de début"""
1024 contrat
= self
.rh_contrats
.exclude(date_debut
=None) \
1025 .order_by('date_debut')[0]
1026 except IndexError, Contrat
.DoesNotExist
:
1030 def dernier_contrat(self
):
1031 """contrat avec plus grande date de fin"""
1033 contrat
= self
.rh_contrats
.exclude(date_debut
=None) \
1034 .order_by('-date_debut')[0]
1035 except IndexError, Contrat
.DoesNotExist
:
1040 today
= date
.today()
1041 return (self
.date_debut
is None or self
.date_debut
<= today
) \
1042 and (self
.date_fin
is None or self
.date_fin
>= today
) \
1043 and not (self
.date_fin
is None and self
.date_debut
is None)
1046 class Dossier(Dossier_
):
1047 __doc__
= Dossier_
.__doc__
1048 poste
= models
.ForeignKey(
1049 Poste
, db_column
='poste', related_name
='rh_dossiers',
1050 help_text
=u
"Taper le nom du poste ou du type de poste",
1052 employe
= models
.ForeignKey(
1053 'Employe', db_column
='employe',
1054 help_text
=u
"Taper le nom de l'employé",
1055 related_name
='rh_dossiers', verbose_name
=u
"employé"
1057 principal
= models
.BooleanField(
1058 u
"dossier principal", default
=True,
1060 u
"Ce dossier est pour le principal poste occupé par l'employé"
1064 reversion
.register(Dossier
, format
='xml', follow
=[
1065 'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
1066 'rh_contrats', 'commentaires'
1070 class DossierPiece_(models
.Model
):
1072 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1073 Ex.: Lettre de motivation.
1075 nom
= models
.CharField(max_length
=255)
1076 fichier
= models
.FileField(
1077 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
1084 def __unicode__(self
):
1085 return u
'%s' % (self
.nom
)
1088 class DossierPiece(DossierPiece_
):
1089 dossier
= models
.ForeignKey(
1090 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
1093 reversion
.register(DossierPiece
, format
='xml')
1095 class DossierCommentaire(Commentaire
):
1096 dossier
= models
.ForeignKey(
1097 Dossier
, db_column
='dossier', related_name
='commentaires'
1100 reversion
.register(DossierCommentaire
, format
='xml')
1103 class DossierComparaison_(models
.Model
, DevisableMixin
):
1105 Photo d'une comparaison salariale au moment de l'embauche.
1107 objects
= DossierComparaisonManager()
1109 implantation
= models
.ForeignKey(
1110 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1112 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1113 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1114 montant
= models
.IntegerField(null
=True)
1115 devise
= models
.ForeignKey(
1116 'Devise', related_name
='+', null
=True, blank
=True
1122 def __unicode__(self
):
1123 return "%s (%s)" % (self
.poste
, self
.personne
)
1126 class DossierComparaison(DossierComparaison_
):
1127 dossier
= models
.ForeignKey(
1128 Dossier
, related_name
='rh_comparaisons'
1131 reversion
.register(DossierComparaison
, format
='xml')
1136 class RemunerationMixin(models
.Model
):
1139 type = models
.ForeignKey(
1140 'TypeRemuneration', db_column
='type', related_name
='+',
1141 verbose_name
=u
"type de rémunération"
1143 type_revalorisation
= models
.ForeignKey(
1144 'TypeRevalorisation', db_column
='type_revalorisation',
1145 related_name
='+', verbose_name
=u
"type de revalorisation",
1146 null
=True, blank
=True
1148 montant
= models
.DecimalField(
1149 null
=True, blank
=True, max_digits
=12, decimal_places
=2
1150 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1151 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1153 # commentaire = precision
1154 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1156 # date_debut = anciennement date_effectif
1157 date_debut
= models
.DateField(
1158 u
"date de début", null
=True, blank
=True, db_index
=True
1160 date_fin
= models
.DateField(
1161 u
"date de fin", null
=True, blank
=True, db_index
=True
1166 ordering
= ['type__nom', '-date_fin']
1168 def __unicode__(self
):
1169 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1172 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1174 Structure de rémunération (données budgétaires) en situation normale
1175 pour un Dossier. Si un Evenement existe, utiliser la structure de
1176 rémunération EvenementRemuneration de cet événement.
1178 objects
= RemunerationManager()
1180 def montant_mois(self
):
1181 return round(self
.montant
/ 12, 2)
1183 def montant_avec_regime(self
):
1184 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1186 def montant_euro_mois(self
):
1187 return round(self
.montant_euros() / 12, 2)
1189 def __unicode__(self
):
1191 devise
= self
.devise
.code
1194 return "%s %s" % (self
.montant
, devise
)
1198 verbose_name
= u
"Rémunération"
1199 verbose_name_plural
= u
"Rémunérations"
1202 class Remuneration(Remuneration_
):
1203 dossier
= models
.ForeignKey(
1204 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1207 reversion
.register(Remuneration
, format
='xml')
1212 class Contrat_(models
.Model
):
1214 Document juridique qui encadre la relation de travail d'un Employe
1215 pour un Poste particulier. Pour un Dossier (qui documente cette
1216 relation de travail) plusieurs contrats peuvent être associés.
1218 objects
= ContratManager()
1219 type_contrat
= models
.ForeignKey(
1220 'TypeContrat', db_column
='type_contrat',
1221 verbose_name
=u
'type de contrat', related_name
='+'
1223 date_debut
= models
.DateField(
1224 u
"date de début", db_index
=True
1226 date_fin
= models
.DateField(
1227 u
"date de fin", null
=True, blank
=True, db_index
=True
1229 fichier
= models
.FileField(
1230 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1236 ordering
= ['dossier__employe__nom']
1237 verbose_name
= u
"Contrat"
1238 verbose_name_plural
= u
"Contrats"
1240 def __unicode__(self
):
1241 return u
'%s - %s' % (self
.dossier
, self
.id)
1244 class Contrat(Contrat_
):
1245 dossier
= models
.ForeignKey(
1246 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1249 reversion
.register(Contrat
, format
='xml')
1254 class CategorieEmploi(models
.Model
):
1256 Catégorie utilisée dans la gestion des Postes.
1257 Catégorie supérieure à TypePoste.
1259 nom
= models
.CharField(max_length
=255)
1263 verbose_name
= u
"catégorie d'emploi"
1264 verbose_name_plural
= u
"catégories d'emploi"
1266 def __unicode__(self
):
1269 reversion
.register(CategorieEmploi
, format
='xml')
1272 class FamilleProfessionnelle(models
.Model
):
1274 Famille professionnelle d'un poste.
1276 nom
= models
.CharField(max_length
=100)
1280 verbose_name
= u
'famille professionnelle'
1281 verbose_name_plural
= u
'familles professionnelles'
1283 def __unicode__(self
):
1286 reversion
.register(FamilleProfessionnelle
, format
='xml')
1289 class TypePoste(Archivable
):
1293 nom
= models
.CharField(max_length
=255)
1294 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1295 is_responsable
= models
.BooleanField(
1296 u
"poste de responsabilité", default
=False
1298 categorie_emploi
= models
.ForeignKey(
1299 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1300 verbose_name
=u
"catégorie d'emploi"
1302 famille_professionnelle
= models
.ForeignKey(
1303 FamilleProfessionnelle
, related_name
='types_de_poste',
1304 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1309 verbose_name
= u
"Type de poste"
1310 verbose_name_plural
= u
"Types de poste"
1312 def __unicode__(self
):
1313 return u
'%s' % (self
.nom
)
1315 reversion
.register(TypePoste
, format
='xml')
1318 TYPE_PAIEMENT_CHOICES
= (
1319 (u
'Régulier', u
'Régulier'),
1320 (u
'Ponctuel', u
'Ponctuel'),
1323 NATURE_REMUNERATION_CHOICES
= (
1324 (u
'Accessoire', u
'Traitement ponctuel'),
1325 (u
'Charges', u
'Charges patronales'),
1326 (u
'Indemnité', u
'Indemnité'),
1327 (u
'RAS', u
'Rémunération autre source'),
1328 (u
'Traitement', u
'Traitement'),
1332 class TypeRemuneration(Archivable
):
1334 Catégorie de Remuneration.
1337 nom
= models
.CharField(max_length
=255)
1338 type_paiement
= models
.CharField(
1339 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1342 nature_remuneration
= models
.CharField(
1343 u
"nature de la rémunération", max_length
=30,
1344 choices
=NATURE_REMUNERATION_CHOICES
1349 verbose_name
= u
"Type de rémunération"
1350 verbose_name_plural
= u
"Types de rémunération"
1352 def __unicode__(self
):
1355 reversion
.register(TypeRemuneration
, format
='xml')
1358 class TypeRevalorisation(Archivable
):
1360 Justification du changement de la Remuneration.
1361 (Actuellement utilisé dans aucun traitement informatique.)
1363 nom
= models
.CharField(max_length
=255)
1367 verbose_name
= u
"Type de revalorisation"
1368 verbose_name_plural
= u
"Types de revalorisation"
1370 def __unicode__(self
):
1371 return u
'%s' % (self
.nom
)
1373 reversion
.register(TypeRevalorisation
, format
='xml')
1376 class Service(Archivable
):
1378 Unité administrative où les Postes sont rattachés.
1380 nom
= models
.CharField(max_length
=255)
1384 verbose_name
= u
"service"
1385 verbose_name_plural
= u
"services"
1387 def __unicode__(self
):
1390 reversion
.register(Service
, format
='xml')
1393 TYPE_ORGANISME_CHOICES
= (
1394 ('MAD', 'Mise à disposition'),
1395 ('DET', 'Détachement'),
1399 class OrganismeBstg(models
.Model
):
1401 Organisation d'où provient un Employe mis à disposition (MAD) de
1402 ou détaché (DET) à l'AUF à titre gratuit.
1404 (BSTG = bien et service à titre gratuit.)
1406 nom
= models
.CharField(max_length
=255)
1407 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1408 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1410 related_name
='organismes_bstg',
1411 null
=True, blank
=True)
1414 ordering
= ['type', 'nom']
1415 verbose_name
= u
"Organisme BSTG"
1416 verbose_name_plural
= u
"Organismes BSTG"
1418 def __unicode__(self
):
1419 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1421 reversion
.register(OrganismeBstg
, format
='xml')
1424 class Statut(Archivable
):
1426 Statut de l'Employe dans le cadre d'un Dossier particulier.
1429 code
= models
.CharField(
1430 max_length
=25, unique
=True,
1432 u
"Saisir un code court mais lisible pour ce statut : "
1433 u
"le code est utilisé pour associer les statuts aux autres "
1434 u
"données tout en demeurant plus lisible qu'un identifiant "
1438 nom
= models
.CharField(max_length
=255)
1442 verbose_name
= u
"Statut d'employé"
1443 verbose_name_plural
= u
"Statuts d'employé"
1445 def __unicode__(self
):
1446 return u
'%s : %s' % (self
.code
, self
.nom
)
1448 reversion
.register(Statut
, format
='xml')
1451 TYPE_CLASSEMENT_CHOICES
= (
1452 ('S', 'S -Soutien'),
1453 ('T', 'T - Technicien'),
1454 ('P', 'P - Professionel'),
1456 ('D', 'D - Direction'),
1457 ('SO', 'SO - Sans objet [expatriés]'),
1458 ('HG', 'HG - Hors grille [direction]'),
1462 class ClassementManager(ArchivableManager
):
1464 Ordonner les spcéfiquement les classements.
1466 def get_query_set(self
):
1467 qs
= super(self
.__class__
, self
).get_query_set()
1468 qs
= qs
.extra(select
={
1469 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1471 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1475 class Classement_(Archivable
):
1477 Éléments de classement de la
1478 "Grille générique de classement hiérarchique".
1480 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1481 classement dans la grille. Le classement donne le coefficient utilisé dans:
1483 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1485 objects
= ClassementManager()
1488 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1489 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1490 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1491 coefficient
= models
.FloatField(u
"coefficient", blank
=True, null
=True)
1494 # annee # au lieu de date_debut et date_fin
1495 commentaire
= models
.TextField(null
=True, blank
=True)
1499 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1500 verbose_name
= u
"Classement"
1501 verbose_name_plural
= u
"Classements"
1503 def __unicode__(self
):
1504 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1507 class Classement(Classement_
):
1508 __doc__
= Classement_
.__doc__
1510 reversion
.register(Classement
, format
='xml')
1513 class TauxChange_(models
.Model
):
1515 Taux de change de la devise vers l'euro (EUR)
1516 pour chaque année budgétaire.
1519 devise
= models
.ForeignKey('Devise', db_column
='devise')
1520 annee
= models
.IntegerField(u
"année")
1521 taux
= models
.FloatField(u
"taux vers l'euro")
1525 ordering
= ['-annee', 'devise__code']
1526 verbose_name
= u
"Taux de change"
1527 verbose_name_plural
= u
"Taux de change"
1528 unique_together
= ('devise', 'annee')
1530 def __unicode__(self
):
1531 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1534 class TauxChange(TauxChange_
):
1535 __doc__
= TauxChange_
.__doc__
1537 reversion
.register(TauxChange
, format
='xml')
1540 class ValeurPointManager(models
.Manager
):
1542 def get_query_set(self
):
1543 return super(ValeurPointManager
, self
).get_query_set() \
1544 .select_related('devise', 'implantation')
1547 class ValeurPoint_(models
.Model
):
1549 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1550 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1551 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1553 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1556 objects
= models
.Manager()
1557 actuelles
= ValeurPointManager()
1559 valeur
= models
.FloatField(null
=True)
1560 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1561 implantation
= models
.ForeignKey(ref
.Implantation
,
1562 db_column
='implantation',
1563 related_name
='%(app_label)s_valeur_point')
1565 annee
= models
.IntegerField()
1568 ordering
= ['-annee', 'implantation__nom']
1570 verbose_name
= u
"Valeur du point"
1571 verbose_name_plural
= u
"Valeurs du point"
1572 unique_together
= ('implantation', 'annee')
1574 def __unicode__(self
):
1575 return u
'%s %s %s [%s] %s' % (
1576 self
.devise
.code
, self
.annee
, self
.valeur
,
1577 self
.implantation
.nom_court
, self
.devise
.nom
1581 class ValeurPoint(ValeurPoint_
):
1582 __doc__
= ValeurPoint_
.__doc__
1584 reversion
.register(ValeurPoint
, format
='xml')
1587 class Devise(Archivable
):
1591 code
= models
.CharField(max_length
=10, unique
=True)
1592 nom
= models
.CharField(max_length
=255)
1596 verbose_name
= u
"devise"
1597 verbose_name_plural
= u
"devises"
1599 def __unicode__(self
):
1600 return u
'%s - %s' % (self
.code
, self
.nom
)
1602 reversion
.register(Devise
, format
='xml')
1605 class TypeContrat(Archivable
):
1609 nom
= models
.CharField(max_length
=255)
1610 nom_long
= models
.CharField(max_length
=255)
1614 verbose_name
= u
"Type de contrat"
1615 verbose_name_plural
= u
"Types de contrat"
1617 def __unicode__(self
):
1618 return u
'%s' % (self
.nom
)
1620 reversion
.register(TypeContrat
, format
='xml')
1625 class ResponsableImplantationProxy(ref
.Implantation
):
1633 verbose_name
= u
"Responsable d'implantation"
1634 verbose_name_plural
= u
"Responsables d'implantation"
1637 class ResponsableImplantation(models
.Model
):
1639 Le responsable d'une implantation.
1640 Anciennement géré sur le Dossier du responsable.
1642 employe
= models
.ForeignKey(
1643 'Employe', db_column
='employe', related_name
='+', null
=True,
1646 implantation
= models
.OneToOneField(
1647 "ResponsableImplantationProxy", db_column
='implantation',
1648 related_name
='responsable', unique
=True
1651 def __unicode__(self
):
1652 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1655 ordering
= ['implantation__nom']
1656 verbose_name
= "Responsable d'implantation"
1657 verbose_name_plural
= "Responsables d'implantation"
1659 reversion
.register(ResponsableImplantation
, format
='xml')
1662 class UserProfile(models
.Model
):
1663 user
= models
.OneToOneField(User
, related_name
='profile')
1664 zones_administratives
= models
.ManyToManyField(
1665 ref
.ZoneAdministrative
,
1666 related_name
='profiles'
1669 verbose_name
= "Permissions sur zones administratives"
1670 verbose_name_plural
= "Permissions sur zones administratives"
1672 def __unicode__(self
):
1673 return self
.user
.__unicode__()
1675 reversion
.register(UserProfile
, format
='xml')