1 # -=- encoding: utf-8 -=-
4 from datetime
import date
5 from decimal
import Decimal
7 from auf
.django
.emploi
.models
import \
8 GENRE_CHOICES
, SITUATION_CHOICES
# devrait plutot être dans references
9 from auf
.django
.metadata
.models
import AUFMetadata
10 from auf
.django
.metadata
.managers
import NoDeleteManager
11 from auf
.django
.references
import models
as ref
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 change_list
import \
18 RechercheTemporelle
, KEY_STATUT
, STATUT_ACTIF
, STATUT_INACTIF
, \
20 from managers
import \
21 PosteManager
, DossierManager
, DossierComparaisonManager
, \
22 PosteComparaisonManager
, DeviseManager
, ServiceManager
, \
23 TypeRemunerationManager
24 from validators
import validate_date_passee
27 # Gruick hack pour déterminer d'ou provient l'instanciation d'une classe
28 # pour l'héritage. Cela permet de faire du dynamic loading par app sans
29 # avoir à redéfinir dans DAE la FK
32 models_stack
= [s
[1].split('/')[-2]
33 for s
in inspect
.stack()
34 if s
[1].endswith('models.py')]
35 return models_stack
[-1]
39 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
40 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
41 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
42 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
= \
43 "Saisir le nombre d'heure de travail à temps complet (100%), " \
44 "sans tenir compte du régime de travail"
47 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
48 base_url
=settings
.PRIVE_MEDIA_URL
)
51 def poste_piece_dispatch(instance
, filename
):
52 path
= "%s/poste/%s/%s" % (
53 instance
._meta
.app_label
, instance
.poste_id
, filename
58 def dossier_piece_dispatch(instance
, filename
):
59 path
= "%s/dossier/%s/%s" % (
60 instance
._meta
.app_label
, instance
.dossier_id
, filename
65 def employe_piece_dispatch(instance
, filename
):
66 path
= "%s/employe/%s/%s" % (
67 instance
._meta
.app_label
, instance
.employe_id
, filename
72 def contrat_dispatch(instance
, filename
):
73 path
= "%s/contrat/%s/%s" % (
74 instance
._meta
.app_label
, instance
.dossier_id
, filename
79 class DevisableMixin(object):
81 def get_annee_pour_taux_devise(self
):
82 return datetime
.datetime
.now().year
84 def taux_devise(self
, devise
=None):
90 if devise
.code
== "EUR":
93 annee
= self
.get_annee_pour_taux_devise()
96 for tc
in TauxChange
.objects
.filter(devise
=devise
, annee
=annee
)
102 u
"Pas de taux pour %s en %s" % (devise
.code
, annee
)
106 raise Exception(u
"Il existe plusieurs taux de %s en %s" %
107 (devise
.code
, annee
))
111 def montant_euros(self
):
113 taux
= self
.taux_devise()
118 return int(round(float(self
.montant
) * float(taux
), 2))
121 class Commentaire(AUFMetadata
):
122 texte
= models
.TextField()
123 owner
= models
.ForeignKey(
124 'auth.User', db_column
='owner', related_name
='+',
125 verbose_name
=u
"Commentaire de"
130 ordering
= ['-date_creation']
132 def __unicode__(self
):
133 return u
'%s' % (self
.texte
)
138 POSTE_APPEL_CHOICES
= (
139 ('interne', 'Interne'),
140 ('externe', 'Externe'),
144 class Poste_(AUFMetadata
):
146 Un Poste est un emploi (job) à combler dans une implantation.
147 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
148 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
151 objects
= PosteManager()
154 nom
= models
.CharField(u
"Titre du poste", max_length
=255)
155 nom_feminin
= models
.CharField(
156 u
"Titre du poste (au féminin)", max_length
=255, null
=True
158 implantation
= models
.ForeignKey(
160 help_text
=u
"Taper le nom de l'implantation ou sa région",
161 db_column
='implantation', related_name
='+'
163 type_poste
= models
.ForeignKey(
164 'TypePoste', db_column
='type_poste',
165 help_text
=u
"Taper le nom du type de poste", related_name
='+',
166 null
=True, verbose_name
=u
"type de poste"
168 service
= models
.ForeignKey(
169 'Service', db_column
='service', related_name
='+',
170 verbose_name
=u
"direction/service/pôle support", null
=True
172 responsable
= models
.ForeignKey(
173 'Poste', db_column
='responsable',
174 related_name
='+', null
=True,
175 help_text
=u
"Taper le nom du poste ou du type de poste",
176 verbose_name
=u
"Poste du responsable"
180 regime_travail
= models
.DecimalField(
181 u
"temps de travail", max_digits
=12, decimal_places
=2,
182 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
183 help_text
="% du temps complet"
185 regime_travail_nb_heure_semaine
= models
.DecimalField(
186 u
"nb. heures par semaine", max_digits
=12, decimal_places
=2,
187 null
=True, default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
188 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
192 local
= models
.NullBooleanField(
193 u
"local", default
=True, null
=True, blank
=True
195 expatrie
= models
.NullBooleanField(
196 u
"expatrié", default
=False, null
=True, blank
=True
198 mise_a_disposition
= models
.NullBooleanField(
199 u
"mise à disposition", null
=True, default
=False
201 appel
= models
.CharField(
202 u
"Appel à candidature", max_length
=10, null
=True,
203 choices
=POSTE_APPEL_CHOICES
, default
='interne'
207 classement_min
= models
.ForeignKey(
208 'Classement', db_column
='classement_min', related_name
='+',
209 null
=True, blank
=True
211 classement_max
= models
.ForeignKey(
212 'Classement', db_column
='classement_max', related_name
='+',
213 null
=True, blank
=True
215 valeur_point_min
= models
.ForeignKey(
217 help_text
=u
"Taper le code ou le nom de l'implantation",
218 db_column
='valeur_point_min', related_name
='+', null
=True,
221 valeur_point_max
= models
.ForeignKey(
223 help_text
=u
"Taper le code ou le nom de l'implantation",
224 db_column
='valeur_point_max', related_name
='+', null
=True,
227 devise_min
= models
.ForeignKey(
228 'Devise', db_column
='devise_min', null
=True, related_name
='+'
230 devise_max
= models
.ForeignKey(
231 'Devise', db_column
='devise_max', null
=True, related_name
='+'
233 salaire_min
= models
.DecimalField(
234 max_digits
=12, decimal_places
=2, null
=True, default
=0
236 salaire_max
= models
.DecimalField(
237 max_digits
=12, decimal_places
=2, null
=True, default
=0
239 indemn_min
= models
.DecimalField(
240 max_digits
=12, decimal_places
=2, null
=True, default
=0
242 indemn_max
= models
.DecimalField(
243 max_digits
=12, decimal_places
=2, null
=True, default
=0
245 autre_min
= models
.DecimalField(
246 max_digits
=12, decimal_places
=2, null
=True, default
=0
248 autre_max
= models
.DecimalField(
249 max_digits
=12, decimal_places
=2, null
=True, default
=0
252 # Comparatifs de rémunération
253 devise_comparaison
= models
.ForeignKey(
254 'Devise', null
=True, blank
=True, db_column
='devise_comparaison',
257 comp_locale_min
= models
.DecimalField(
258 max_digits
=12, decimal_places
=2, null
=True, blank
=True
260 comp_locale_max
= models
.DecimalField(
261 max_digits
=12, decimal_places
=2, null
=True, blank
=True
263 comp_universite_min
= models
.DecimalField(
264 max_digits
=12, decimal_places
=2, null
=True, blank
=True
266 comp_universite_max
= models
.DecimalField(
267 max_digits
=12, decimal_places
=2, null
=True, blank
=True
269 comp_fonctionpub_min
= models
.DecimalField(
270 max_digits
=12, decimal_places
=2, null
=True, blank
=True
272 comp_fonctionpub_max
= models
.DecimalField(
273 max_digits
=12, decimal_places
=2, null
=True, blank
=True
275 comp_ong_min
= models
.DecimalField(
276 max_digits
=12, decimal_places
=2, null
=True, blank
=True
278 comp_ong_max
= models
.DecimalField(
279 max_digits
=12, decimal_places
=2, null
=True, blank
=True
281 comp_autre_min
= models
.DecimalField(
282 max_digits
=12, decimal_places
=2, null
=True, blank
=True
284 comp_autre_max
= models
.DecimalField(
285 max_digits
=12, decimal_places
=2, null
=True, blank
=True
289 justification
= models
.TextField(null
=True, blank
=True)
292 date_debut
= models
.DateField(
293 u
"date de début", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True
295 date_fin
= models
.DateField(
296 u
"date de fin", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True
301 ordering
= ['implantation__nom', 'nom']
302 verbose_name
= u
"Poste"
303 verbose_name_plural
= u
"Postes"
306 def __unicode__(self
):
307 representation
= u
'%s - %s [%s]' % (
308 self
.implantation
, self
.nom
, self
.id
310 return representation
312 prefix_implantation
= "implantation__region"
314 def get_regions(self
):
315 return [self
.implantation
.region
]
317 def get_devise(self
):
318 vp
= ValeurPoint
.objects
.filter(
319 implantation
=self
.implantation
, devise__archive
=False
324 return Devise
.objects
.get(code
='EUR')
328 __doc__
= Poste_
.__doc__
330 # meta dématérialisation : pour permettre le filtrage
331 vacant
= models
.NullBooleanField(u
"vacant", null
=True, blank
=True)
335 if self
.occupe_par():
339 def occupe_par(self
):
341 Retourne la liste d'employé occupant ce poste.
342 Généralement, retourne une liste d'un élément.
343 Si poste inoccupé, retourne liste vide.
344 UTILISE pour mettre a jour le flag vacant
347 d
.employe
for d
in self
.rh_dossiers
348 .filter(supprime
=False)
349 .exclude(date_fin__lt
=date
.today())
353 POSTE_FINANCEMENT_CHOICES
= (
354 ('A', 'A - Frais de personnel'),
355 ('B', 'B - Projet(s)-Titre(s)'),
360 class PosteFinancement_(models
.Model
):
362 Pour un Poste, structure d'informations décrivant comment on prévoit
365 poste
= models
.ForeignKey(
366 '%s.Poste' % app_context(), db_column
='poste',
367 related_name
='%(app_label)s_financements'
369 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
370 pourcentage
= models
.DecimalField(
371 max_digits
=12, decimal_places
=2,
372 help_text
="ex.: 33.33 % (décimale avec point)"
374 commentaire
= models
.TextField(
375 help_text
="Spécifiez la source de financement."
382 def __unicode__(self
):
383 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
386 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
389 class PosteFinancement(PosteFinancement_
):
393 class PostePiece_(models
.Model
):
395 Documents relatifs au Poste.
396 Ex.: Description de poste
398 poste
= models
.ForeignKey(
399 '%s.Poste' % app_context(), db_column
='poste',
400 related_name
='%(app_label)s_pieces'
402 nom
= models
.CharField(u
"Nom", max_length
=255)
403 fichier
= models
.FileField(
404 u
"Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
411 def __unicode__(self
):
412 return u
'%s' % (self
.nom
)
415 class PostePiece(PostePiece_
):
419 class PosteComparaison_(AUFMetadata
, DevisableMixin
):
421 De la même manière qu'un dossier, un poste peut-être comparé à un autre
424 poste
= models
.ForeignKey(
425 '%s.Poste' % app_context(),
426 related_name
='%(app_label)s_comparaisons_internes'
428 objects
= PosteComparaisonManager()
430 implantation
= models
.ForeignKey(
431 ref
.Implantation
, null
=True, blank
=True, related_name
="+"
433 nom
= models
.CharField(u
"Poste", max_length
=255, null
=True, blank
=True)
434 montant
= models
.IntegerField(null
=True)
435 devise
= models
.ForeignKey(
436 "Devise", related_name
='+', null
=True, blank
=True
442 def __unicode__(self
):
446 class PosteComparaison(PosteComparaison_
):
447 objects
= NoDeleteManager()
450 class PosteCommentaire_(Commentaire
):
451 poste
= models
.ForeignKey(
452 '%s.Poste' % app_context(), db_column
='poste', related_name
='+'
459 class PosteCommentaire(PosteCommentaire_
):
465 class Employe(AUFMetadata
):
467 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
468 Dossiers qu'il occupe ou a occupé de Postes.
470 Cette classe aurait pu avantageusement s'appeler Personne car la notion
471 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
474 nom
= models
.CharField(max_length
=255)
475 prenom
= models
.CharField(u
"prénom", max_length
=255)
476 nom_affichage
= models
.CharField(
477 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
479 nationalite
= models
.ForeignKey(
480 ref
.Pays
, to_field
='code', db_column
='nationalite',
481 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
482 blank
=True, null
=True
484 date_naissance
= models
.DateField(
485 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
486 validators
=[validate_date_passee
], null
=True, blank
=True
488 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
491 situation_famille
= models
.CharField(
492 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
493 null
=True, blank
=True
495 date_entree
= models
.DateField(
496 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
501 tel_domicile
= models
.CharField(
502 u
"tél. domicile", max_length
=255, null
=True, blank
=True
504 tel_cellulaire
= models
.CharField(
505 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
507 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
508 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
509 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
510 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
511 pays
= models
.ForeignKey(
512 ref
.Pays
, to_field
='code', db_column
='pays',
513 related_name
='employes', null
=True, blank
=True
516 # meta dématérialisation : pour permettre le filtrage
517 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
520 ordering
= ['nom', 'prenom']
521 verbose_name
= u
"Employé"
522 verbose_name_plural
= u
"Employés"
524 def __unicode__(self
):
525 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
529 if self
.genre
.upper() == u
'M':
531 elif self
.genre
.upper() == u
'F':
537 Retourne l'URL du service retournant la photo de l'Employe.
538 Équivalent reverse url 'rh_photo' avec id en param.
540 from django
.core
.urlresolvers
import reverse
541 return reverse('rh_photo', kwargs
={'id': self
.id})
543 def dossiers_passes(self
):
544 params
= {KEY_STATUT
: STATUT_INACTIF
, }
545 search
= RechercheTemporelle(params
, self
.__class__
)
546 search
.purge_params(params
)
547 q
= search
.get_q_temporel(self
.rh_dossiers
)
548 return self
.rh_dossiers
.filter(q
)
550 def dossiers_futurs(self
):
551 params
= {KEY_STATUT
: STATUT_FUTUR
, }
552 search
= RechercheTemporelle(params
, self
.__class__
)
553 search
.purge_params(params
)
554 q
= search
.get_q_temporel(self
.rh_dossiers
)
555 return self
.rh_dossiers
.filter(q
)
557 def dossiers_encours(self
):
558 params
= {KEY_STATUT
: STATUT_ACTIF
, }
559 search
= RechercheTemporelle(params
, self
.__class__
)
560 search
.purge_params(params
)
561 q
= search
.get_q_temporel(self
.rh_dossiers
)
562 return self
.rh_dossiers
.filter(q
)
564 def postes_encours(self
):
565 postes_encours
= set()
566 for d
in self
.dossiers_encours():
567 postes_encours
.add(d
.poste
)
568 return postes_encours
570 def poste_principal(self
):
572 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
574 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
576 poste
= Poste
.objects
.none()
578 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
583 prefix_implantation
= "rh_dossiers__poste__implantation__region"
585 def get_regions(self
):
587 for d
in self
.dossiers
.all():
588 regions
.append(d
.poste
.implantation
.region
)
592 class EmployePiece(models
.Model
):
594 Documents relatifs à un employé.
597 employe
= models
.ForeignKey(
598 'Employe', db_column
='employe', related_name
="pieces",
599 verbose_name
=u
"employé"
601 nom
= models
.CharField(max_length
=255)
602 fichier
= models
.FileField(
603 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
608 verbose_name
= u
"Employé pièce"
609 verbose_name_plural
= u
"Employé pièces"
611 def __unicode__(self
):
612 return u
'%s' % (self
.nom
)
615 class EmployeCommentaire(Commentaire
):
616 employe
= models
.ForeignKey(
617 'Employe', db_column
='employe', related_name
='+'
621 verbose_name
= u
"Employé commentaire"
622 verbose_name_plural
= u
"Employé commentaires"
625 LIEN_PARENTE_CHOICES
= (
626 ('Conjoint', 'Conjoint'),
627 ('Conjointe', 'Conjointe'),
633 class AyantDroit(AUFMetadata
):
635 Personne en relation avec un Employe.
638 nom
= models
.CharField(max_length
=255)
639 prenom
= models
.CharField(u
"prénom", max_length
=255)
640 nom_affichage
= models
.CharField(
641 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
643 nationalite
= models
.ForeignKey(
644 ref
.Pays
, to_field
='code', db_column
='nationalite',
645 related_name
='ayantdroits_nationalite',
646 verbose_name
=u
"nationalité", null
=True, blank
=True
648 date_naissance
= models
.DateField(
649 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
650 validators
=[validate_date_passee
], null
=True, blank
=True
652 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
655 employe
= models
.ForeignKey(
656 'Employe', db_column
='employe', related_name
='ayantdroits',
657 verbose_name
=u
"Employé"
659 lien_parente
= models
.CharField(
660 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
661 null
=True, blank
=True
666 verbose_name
= u
"Ayant droit"
667 verbose_name_plural
= u
"Ayants droit"
669 def __unicode__(self
):
670 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
672 prefix_implantation
= "employe__dossiers__poste__implantation__region"
674 def get_regions(self
):
676 for d
in self
.employe
.dossiers
.all():
677 regions
.append(d
.poste
.implantation
.region
)
681 class AyantDroitCommentaire(Commentaire
):
682 ayant_droit
= models
.ForeignKey(
683 'AyantDroit', db_column
='ayant_droit', related_name
='+'
689 STATUT_RESIDENCE_CHOICES
= (
691 ('expat', 'Expatrié'),
694 COMPTE_COMPTA_CHOICES
= (
701 class Dossier_(AUFMetadata
, DevisableMixin
):
703 Le Dossier regroupe les informations relatives à l'occupation
704 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
707 Plusieurs Contrats peuvent être associés au Dossier.
708 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
709 lequel aucun Dossier n'existe est un poste vacant.
712 objects
= DossierManager()
715 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
716 organisme_bstg
= models
.ForeignKey(
717 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
718 verbose_name
=u
"organisme",
720 u
"Si détaché (DET) ou mis à disposition (MAD), "
721 u
"préciser l'organisme."
722 ), null
=True, blank
=True
726 remplacement
= models
.BooleanField(default
=False)
727 remplacement_de
= models
.ForeignKey(
728 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
729 null
=True, blank
=True
731 statut_residence
= models
.CharField(
732 u
"statut", max_length
=10, default
='local', null
=True,
733 choices
=STATUT_RESIDENCE_CHOICES
737 classement
= models
.ForeignKey(
738 'Classement', db_column
='classement', related_name
='+', null
=True,
741 regime_travail
= models
.DecimalField(
742 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
743 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
745 regime_travail_nb_heure_semaine
= models
.DecimalField(
746 u
"nb. heures par semaine", max_digits
=12,
747 decimal_places
=2, null
=True,
748 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
749 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
752 # Occupation du Poste par cet Employe (anciennement "mandat")
753 date_debut
= models
.DateField(u
"date de début d'occupation de poste")
754 date_fin
= models
.DateField(
755 u
"Date de fin d'occupation de poste", null
=True, blank
=True
763 ordering
= ['employe__nom', ]
764 verbose_name
= u
"Dossier"
765 verbose_name_plural
= "Dossiers"
767 def salaire_theorique(self
):
768 annee
= date
.today().year
769 coeff
= self
.classement
.coefficient
770 implantation
= self
.poste
.implantation
771 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
773 montant
= coeff
* point
.valeur
774 devise
= point
.devise
775 return {'montant': montant
, 'devise': devise
}
777 def __unicode__(self
):
778 poste
= self
.poste
.nom
779 if self
.employe
.genre
== 'F':
780 poste
= self
.poste
.nom_feminin
781 return u
'%s - %s' % (self
.employe
, poste
)
783 prefix_implantation
= "poste__implantation__region"
785 def get_regions(self
):
786 return [self
.poste
.implantation
.region
]
788 def remunerations(self
):
789 key
= "%s_remunerations" % self
._meta
.app_label
790 remunerations
= getattr(self
, key
)
791 return remunerations
.all().order_by('-date_debut')
793 def remunerations_en_cours(self
):
794 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
795 return self
.remunerations().all().filter(q
).order_by('date_debut')
797 def get_salaire(self
):
799 return [r
for r
in self
.remunerations().order_by('-date_debut')
800 if r
.type_id
== 1][0]
804 def get_salaire_euros(self
):
805 tx
= self
.taux_devise()
806 return (float)(tx
) * (float)(self
.salaire
)
808 def get_remunerations_brutes(self
):
812 4 Indemnité d'expatriation
813 5 Indemnité pour frais
814 6 Indemnité de logement
815 7 Indemnité de fonction
816 8 Indemnité de responsabilité
817 9 Indemnité de transport
818 10 Indemnité compensatrice
819 11 Indemnité de subsistance
820 12 Indemnité différentielle
821 13 Prime d'installation
824 16 Indemnité de départ
825 18 Prime de 13ième mois
828 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
829 return [r
for r
in self
.remunerations_en_cours().all()
832 def get_charges_salariales(self
):
834 20 Charges salariales ?
837 return [r
for r
in self
.remunerations_en_cours().all()
840 def get_charges_patronales(self
):
842 17 Charges patronales
845 return [r
for r
in self
.remunerations_en_cours().all()
848 def get_remunerations_tierces(self
):
852 return [r
for r
in self
.remunerations_en_cours().all()
853 if r
.type_id
in (2,)]
857 def get_total_local_charges_salariales(self
):
858 devise
= self
.poste
.get_devise()
860 for r
in self
.get_charges_salariales():
861 if r
.devise
!= devise
:
863 total
+= float(r
.montant
)
866 def get_total_local_charges_patronales(self
):
867 devise
= self
.poste
.get_devise()
869 for r
in self
.get_charges_patronales():
870 if r
.devise
!= devise
:
872 total
+= float(r
.montant
)
875 def get_local_salaire_brut(self
):
877 somme des rémuérations brutes
879 devise
= self
.poste
.get_devise()
881 for r
in self
.get_remunerations_brutes():
882 if r
.devise
!= devise
:
884 total
+= float(r
.montant
)
887 def get_local_salaire_net(self
):
889 salaire brut - charges salariales
891 devise
= self
.poste
.get_devise()
893 for r
in self
.get_charges_salariales():
894 if r
.devise
!= devise
:
896 total_charges
+= float(r
.montant
)
897 return self
.get_local_salaire_brut() - total_charges
899 def get_local_couts_auf(self
):
901 salaire net + charges patronales
903 devise
= self
.poste
.get_devise()
905 for r
in self
.get_charges_patronales():
906 if r
.devise
!= devise
:
908 total_charges
+= float(r
.montant
)
909 return self
.get_local_salaire_net() + total_charges
911 def get_total_local_remunerations_tierces(self
):
912 devise
= self
.poste
.get_devise()
914 for r
in self
.get_remunerations_tierces():
915 if r
.devise
!= devise
:
917 total
+= float(r
.montant
)
922 def get_total_charges_salariales(self
):
924 for r
in self
.get_charges_salariales():
925 total
+= r
.montant_euros()
928 def get_total_charges_patronales(self
):
930 for r
in self
.get_charges_patronales():
931 total
+= r
.montant_euros()
934 def get_salaire_brut(self
):
936 somme des rémuérations brutes
939 for r
in self
.get_remunerations_brutes():
940 total
+= r
.montant_euros()
943 def get_salaire_net(self
):
945 salaire brut - charges salariales
948 for r
in self
.get_charges_salariales():
949 total_charges
+= r
.montant_euros()
950 return self
.get_salaire_brut() - total_charges
952 def get_couts_auf(self
):
954 salaire net + charges patronales
957 for r
in self
.get_charges_patronales():
958 total_charges
+= r
.montant_euros()
959 return self
.get_salaire_net() + total_charges
961 def get_total_remunerations_tierces(self
):
963 for r
in self
.get_remunerations_tierces():
964 total
+= r
.montant_euros()
968 class Dossier(Dossier_
):
969 __doc__
= Dossier_
.__doc__
970 poste
= models
.ForeignKey('%s.Poste' % app_context(),
972 related_name
='%(app_label)s_dossiers',
973 help_text
=u
"Taper le nom du poste ou du type de poste",
975 employe
= models
.ForeignKey(
976 'Employe', db_column
='employe',
977 help_text
=u
"Taper le nom de l'employé",
978 related_name
='%(app_label)s_dossiers', verbose_name
=u
"employé")
979 principal
= models
.BooleanField(
980 u
"dossier principal", default
=True,
982 u
"Ce dossier est pour le principal poste occupé par l'employé"
987 class DossierPiece_(models
.Model
):
989 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
990 Ex.: Lettre de motivation.
992 dossier
= models
.ForeignKey(
993 '%s.Dossier' % app_context(),
994 db_column
='dossier', related_name
='%(app_label)s_dossierpieces'
996 nom
= models
.CharField(max_length
=255)
997 fichier
= models
.FileField(
998 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
1005 def __unicode__(self
):
1006 return u
'%s' % (self
.nom
)
1009 class DossierPiece(DossierPiece_
):
1013 class DossierCommentaire_(Commentaire
):
1014 dossier
= models
.ForeignKey(
1015 '%s.Dossier' % app_context(), db_column
='dossier', related_name
='+'
1022 class DossierCommentaire(DossierCommentaire_
):
1026 class DossierComparaison_(models
.Model
, DevisableMixin
):
1028 Photo d'une comparaison salariale au moment de l'embauche.
1030 dossier
= models
.ForeignKey(
1031 '%s.Dossier' % app_context(), related_name
='%(app_label)s_comparaisons'
1033 objects
= DossierComparaisonManager()
1035 implantation
= models
.ForeignKey(
1036 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1038 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1039 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1040 montant
= models
.IntegerField(null
=True)
1041 devise
= models
.ForeignKey(
1042 'Devise', related_name
='+', null
=True, blank
=True
1048 def __unicode__(self
):
1049 return "%s (%s)" % (self
.poste
, self
.personne
)
1052 class DossierComparaison(DossierComparaison_
):
1058 class RemunerationMixin(AUFMetadata
):
1059 dossier
= models
.ForeignKey(
1060 '%s.Dossier' % app_context(), db_column
='dossier',
1061 related_name
='%(app_label)s_remunerations'
1065 type = models
.ForeignKey(
1066 'TypeRemuneration', db_column
='type', related_name
='+',
1067 verbose_name
=u
"type de rémunération"
1069 type_revalorisation
= models
.ForeignKey(
1070 'TypeRevalorisation', db_column
='type_revalorisation',
1071 related_name
='+', verbose_name
=u
"type de revalorisation",
1072 null
=True, blank
=True
1074 montant
= models
.DecimalField(
1075 null
=True, blank
=True,
1076 default
=0, max_digits
=12, decimal_places
=2
1077 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1078 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1080 # commentaire = precision
1081 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1083 # date_debut = anciennement date_effectif
1084 date_debut
= models
.DateField(u
"date de début", null
=True, blank
=True)
1085 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1089 ordering
= ['type__nom', '-date_fin']
1091 def __unicode__(self
):
1092 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1095 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1097 Structure de rémunération (données budgétaires) en situation normale
1098 pour un Dossier. Si un Evenement existe, utiliser la structure de
1099 rémunération EvenementRemuneration de cet événement.
1102 def montant_mois(self
):
1103 return round(self
.montant
/ 12, 2)
1105 def montant_avec_regime(self
):
1106 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1108 def montant_euro_mois(self
):
1109 return round(self
.montant_euros() / 12, 2)
1111 def __unicode__(self
):
1113 devise
= self
.devise
.code
1116 return "%s %s" % (self
.montant
, devise
)
1120 verbose_name
= u
"Rémunération"
1121 verbose_name_plural
= u
"Rémunérations"
1124 class Remuneration(Remuneration_
):
1130 class ContratManager(NoDeleteManager
):
1131 def get_query_set(self
):
1132 return super(ContratManager
, self
).get_query_set() \
1133 .select_related('dossier', 'dossier__poste')
1136 class Contrat_(AUFMetadata
):
1138 Document juridique qui encadre la relation de travail d'un Employe
1139 pour un Poste particulier. Pour un Dossier (qui documente cette
1140 relation de travail) plusieurs contrats peuvent être associés.
1142 objects
= ContratManager()
1143 dossier
= models
.ForeignKey(
1144 '%s.Dossier' % app_context(), db_column
='dossier',
1145 related_name
='%(app_label)s_contrats'
1147 type_contrat
= models
.ForeignKey(
1148 'TypeContrat', db_column
='type_contrat',
1149 verbose_name
=u
'type de contrat', related_name
='+'
1151 date_debut
= models
.DateField(u
"date de début")
1152 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1153 fichier
= models
.FileField(
1154 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1160 ordering
= ['dossier__employe__nom']
1161 verbose_name
= u
"Contrat"
1162 verbose_name_plural
= u
"Contrats"
1164 def __unicode__(self
):
1165 return u
'%s - %s' % (self
.dossier
, self
.id)
1168 class Contrat(Contrat_
):
1173 #class Evenement_(AUFMetadata):
1175 # Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
1176 # d'un Dossier qui vient altérer des informations normales liées à un
1177 # Dossier (ex.: la Remuneration).
1179 # Ex.: congé de maternité, maladie...
1181 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
1182 # différent et une rémunération en conséquence. On souhaite toutefois
1183 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
1184 # du retour à la normale.
1186 # dossier = models.ForeignKey(
1187 # '%s.Dossier' % app_context(), db_column='dossier',
1190 # nom = models.CharField(max_length=255)
1191 # date_debut = models.DateField(verbose_name = u"Date de début")
1192 # date_fin = models.DateField(verbose_name = u"Date de fin",
1193 # null=True, blank=True)
1197 # ordering = ['nom']
1198 # verbose_name = u"Évènement"
1199 # verbose_name_plural = u"Évènements"
1201 # def __unicode__(self):
1202 # return u'%s' % (self.nom)
1205 #class Evenement(Evenement_):
1206 # __doc__ = Evenement_.__doc__
1209 #class EvenementRemuneration_(RemunerationMixin):
1211 # Structure de rémunération liée à un Evenement qui remplace
1212 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
1215 # evenement = models.ForeignKey("Evenement", db_column='evenement',
1217 # verbose_name = u"Évènement")
1218 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
1219 # # de l'Evenement associé
1223 # ordering = ['evenement', 'type__nom', '-date_fin']
1224 # verbose_name = u"Évènement - rémunération"
1225 # verbose_name_plural = u"Évènements - rémunérations"
1228 #class EvenementRemuneration(EvenementRemuneration_):
1229 # __doc__ = EvenementRemuneration_.__doc__
1235 #class EvenementRemuneration(EvenementRemuneration_):
1236 # __doc__ = EvenementRemuneration_.__doc__
1237 # TODO? class ContratPiece(models.Model):
1242 class CategorieEmploi(AUFMetadata
):
1244 Catégorie utilisée dans la gestion des Postes.
1245 Catégorie supérieure à TypePoste.
1247 nom
= models
.CharField(max_length
=255)
1251 verbose_name
= u
"catégorie d'emploi"
1252 verbose_name_plural
= u
"catégories d'emploi"
1254 def __unicode__(self
):
1258 class FamilleProfessionnelle(models
.Model
):
1260 Famille professionnelle d'un poste.
1262 nom
= models
.CharField(max_length
=100)
1266 verbose_name
= u
'famille professionnelle'
1267 verbose_name_plural
= u
'familles professionnelles'
1269 def __unicode__(self
):
1273 class TypePoste(AUFMetadata
):
1277 nom
= models
.CharField(max_length
=255)
1278 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1279 is_responsable
= models
.BooleanField(
1280 u
"poste de responsabilité", default
=False
1282 categorie_emploi
= models
.ForeignKey(
1283 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1284 verbose_name
=u
"catégorie d'emploi"
1286 famille_professionnelle
= models
.ForeignKey(
1287 FamilleProfessionnelle
, related_name
='types_de_poste',
1288 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1293 verbose_name
= u
"Type de poste"
1294 verbose_name_plural
= u
"Types de poste"
1296 def __unicode__(self
):
1297 return u
'%s' % (self
.nom
)
1299 TYPE_PAIEMENT_CHOICES
= (
1300 (u
'Régulier', u
'Régulier'),
1301 (u
'Ponctuel', u
'Ponctuel'),
1304 NATURE_REMUNERATION_CHOICES
= (
1305 (u
'Accessoire', u
'Accessoire'),
1306 (u
'Charges', u
'Charges'),
1307 (u
'Indemnité', u
'Indemnité'),
1308 (u
'RAS', u
'Rémunération autre source'),
1309 (u
'Traitement', u
'Traitement'),
1313 class TypeRemuneration(AUFMetadata
):
1315 Catégorie de Remuneration.
1317 objects
= TypeRemunerationManager()
1319 nom
= models
.CharField(max_length
=255)
1320 type_paiement
= models
.CharField(
1321 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1323 nature_remuneration
= models
.CharField(
1324 u
"nature de la rémunération", max_length
=30,
1325 choices
=NATURE_REMUNERATION_CHOICES
1327 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1331 verbose_name
= u
"Type de rémunération"
1332 verbose_name_plural
= u
"Types de rémunération"
1334 def __unicode__(self
):
1336 archive
= u
"(archivé)"
1339 return u
'%s %s' % (self
.nom
, archive
)
1342 class TypeRevalorisation(AUFMetadata
):
1344 Justification du changement de la Remuneration.
1345 (Actuellement utilisé dans aucun traitement informatique.)
1347 nom
= models
.CharField(max_length
=255)
1351 verbose_name
= u
"Type de revalorisation"
1352 verbose_name_plural
= u
"Types de revalorisation"
1354 def __unicode__(self
):
1355 return u
'%s' % (self
.nom
)
1358 class Service(AUFMetadata
):
1360 Unité administrative où les Postes sont rattachés.
1362 objects
= ServiceManager()
1364 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1365 nom
= models
.CharField(max_length
=255)
1369 verbose_name
= u
"Service"
1370 verbose_name_plural
= u
"Services"
1372 def __unicode__(self
):
1374 archive
= u
"(archivé)"
1377 return u
'%s %s' % (self
.nom
, archive
)
1380 TYPE_ORGANISME_CHOICES
= (
1381 ('MAD', 'Mise à disposition'),
1382 ('DET', 'Détachement'),
1386 class OrganismeBstg(AUFMetadata
):
1388 Organisation d'où provient un Employe mis à disposition (MAD) de
1389 ou détaché (DET) à l'AUF à titre gratuit.
1391 (BSTG = bien et service à titre gratuit.)
1393 nom
= models
.CharField(max_length
=255)
1394 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1395 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1397 related_name
='organismes_bstg',
1398 null
=True, blank
=True)
1401 ordering
= ['type', 'nom']
1402 verbose_name
= u
"Organisme BSTG"
1403 verbose_name_plural
= u
"Organismes BSTG"
1405 def __unicode__(self
):
1406 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1408 prefix_implantation
= "pays__region"
1410 def get_regions(self
):
1411 return [self
.pays
.region
]
1414 class Statut(AUFMetadata
):
1416 Statut de l'Employe dans le cadre d'un Dossier particulier.
1419 code
= models
.CharField(
1420 max_length
=25, unique
=True,
1422 u
"Saisir un code court mais lisible pour ce statut : "
1423 u
"le code est utilisé pour associer les statuts aux autres "
1424 u
"données tout en demeurant plus lisible qu'un identifiant "
1428 nom
= models
.CharField(max_length
=255)
1432 verbose_name
= u
"Statut d'employé"
1433 verbose_name_plural
= u
"Statuts d'employé"
1435 def __unicode__(self
):
1436 return u
'%s : %s' % (self
.code
, self
.nom
)
1439 TYPE_CLASSEMENT_CHOICES
= (
1440 ('S', 'S -Soutien'),
1441 ('T', 'T - Technicien'),
1442 ('P', 'P - Professionel'),
1444 ('D', 'D - Direction'),
1445 ('SO', 'SO - Sans objet [expatriés]'),
1446 ('HG', 'HG - Hors grille [direction]'),
1450 class ClassementManager(models
.Manager
):
1452 Ordonner les spcéfiquement les classements.
1454 def get_query_set(self
):
1455 qs
= super(self
.__class__
, self
).get_query_set()
1456 qs
= qs
.extra(select
={
1457 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1459 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1463 class Classement_(AUFMetadata
):
1465 Éléments de classement de la
1466 "Grille générique de classement hiérarchique".
1468 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1469 classement dans la grille. Le classement donne le coefficient utilisé dans:
1471 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1473 objects
= ClassementManager()
1476 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1477 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1478 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1479 coefficient
= models
.FloatField(u
"coefficient", default
=0, null
=True)
1482 # annee # au lieu de date_debut et date_fin
1483 commentaire
= models
.TextField(null
=True, blank
=True)
1487 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1488 verbose_name
= u
"Classement"
1489 verbose_name_plural
= u
"Classements"
1491 def __unicode__(self
):
1492 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1495 class Classement(Classement_
):
1496 __doc__
= Classement_
.__doc__
1499 class TauxChange_(AUFMetadata
):
1501 Taux de change de la devise vers l'euro (EUR)
1502 pour chaque année budgétaire.
1505 devise
= models
.ForeignKey('Devise', db_column
='devise')
1506 annee
= models
.IntegerField(u
"année")
1507 taux
= models
.FloatField(u
"taux vers l'euro")
1511 ordering
= ['-annee', 'devise__code']
1512 verbose_name
= u
"Taux de change"
1513 verbose_name_plural
= u
"Taux de change"
1515 def __unicode__(self
):
1516 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1519 class TauxChange(TauxChange_
):
1520 __doc__
= TauxChange_
.__doc__
1523 class ValeurPointManager(NoDeleteManager
):
1525 def get_query_set(self
):
1526 return super(ValeurPointManager
, self
).get_query_set() \
1527 .select_related('devise', 'implantation')
1530 class ValeurPoint_(AUFMetadata
):
1532 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1533 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1534 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1536 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1539 actuelles
= ValeurPointManager()
1541 valeur
= models
.FloatField(null
=True)
1542 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1543 implantation
= models
.ForeignKey(ref
.Implantation
,
1544 db_column
='implantation',
1545 related_name
='%(app_label)s_valeur_point')
1547 annee
= models
.IntegerField()
1550 ordering
= ['-annee', 'implantation__nom']
1552 verbose_name
= u
"Valeur du point"
1553 verbose_name_plural
= u
"Valeurs du point"
1555 def __unicode__(self
):
1556 return u
'%s %s %s [%s] %s' % (
1557 self
.devise
.code
, self
.annee
, self
.valeur
,
1558 self
.implantation
.nom_court
, self
.devise
.nom
1562 class ValeurPoint(ValeurPoint_
):
1563 __doc__
= ValeurPoint_
.__doc__
1566 class Devise(AUFMetadata
):
1570 objects
= DeviseManager()
1572 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1573 code
= models
.CharField(max_length
=10, unique
=True)
1574 nom
= models
.CharField(max_length
=255)
1578 verbose_name
= u
"Devise"
1579 verbose_name_plural
= u
"Devises"
1581 def __unicode__(self
):
1582 return u
'%s - %s' % (self
.code
, self
.nom
)
1585 class TypeContrat(AUFMetadata
):
1589 nom
= models
.CharField(max_length
=255)
1590 nom_long
= models
.CharField(max_length
=255)
1594 verbose_name
= u
"Type de contrat"
1595 verbose_name_plural
= u
"Types de contrat"
1597 def __unicode__(self
):
1598 return u
'%s' % (self
.nom
)
1603 class ResponsableImplantationProxy(ref
.Implantation
):
1607 verbose_name
= u
"Responsable d'implantation"
1608 verbose_name_plural
= u
"Responsables d'implantation"
1611 class ResponsableImplantation(models
.Model
):
1613 Le responsable d'une implantation.
1614 Anciennement géré sur le Dossier du responsable.
1616 employe
= models
.ForeignKey(
1617 'Employe', db_column
='employe', related_name
='+', null
=True,
1620 implantation
= models
.OneToOneField(
1621 "ResponsableImplantationProxy", db_column
='implantation',
1622 related_name
='responsable', unique
=True
1625 def __unicode__(self
):
1626 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1629 ordering
= ['implantation__nom']
1630 verbose_name
= "Responsable d'implantation"
1631 verbose_name_plural
= "Responsables d'implantation"