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
='%(app_label)s_postes',
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
515 courriel_perso
= models
.EmailField(
516 u
'adresse courriel personnelle', blank
=True
519 # meta dématérialisation : pour permettre le filtrage
520 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
523 ordering
= ['nom', 'prenom']
524 verbose_name
= u
"Employé"
525 verbose_name_plural
= u
"Employés"
527 def __unicode__(self
):
528 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
532 if self
.genre
.upper() == u
'M':
534 elif self
.genre
.upper() == u
'F':
540 Retourne l'URL du service retournant la photo de l'Employe.
541 Équivalent reverse url 'rh_photo' avec id en param.
543 from django
.core
.urlresolvers
import reverse
544 return reverse('rh_photo', kwargs
={'id': self
.id})
546 def dossiers_passes(self
):
547 params
= {KEY_STATUT
: STATUT_INACTIF
, }
548 search
= RechercheTemporelle(params
, self
.__class__
)
549 search
.purge_params(params
)
550 q
= search
.get_q_temporel(self
.rh_dossiers
)
551 return self
.rh_dossiers
.filter(q
)
553 def dossiers_futurs(self
):
554 params
= {KEY_STATUT
: STATUT_FUTUR
, }
555 search
= RechercheTemporelle(params
, self
.__class__
)
556 search
.purge_params(params
)
557 q
= search
.get_q_temporel(self
.rh_dossiers
)
558 return self
.rh_dossiers
.filter(q
)
560 def dossiers_encours(self
):
561 params
= {KEY_STATUT
: STATUT_ACTIF
, }
562 search
= RechercheTemporelle(params
, self
.__class__
)
563 search
.purge_params(params
)
564 q
= search
.get_q_temporel(self
.rh_dossiers
)
565 return self
.rh_dossiers
.filter(q
)
567 def postes_encours(self
):
568 postes_encours
= set()
569 for d
in self
.dossiers_encours():
570 postes_encours
.add(d
.poste
)
571 return postes_encours
573 def poste_principal(self
):
575 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
577 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
579 poste
= Poste
.objects
.none()
581 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
586 prefix_implantation
= "rh_dossiers__poste__implantation__region"
588 def get_regions(self
):
590 for d
in self
.dossiers
.all():
591 regions
.append(d
.poste
.implantation
.region
)
595 class EmployePiece(models
.Model
):
597 Documents relatifs à un employé.
600 employe
= models
.ForeignKey(
601 'Employe', db_column
='employe', related_name
="pieces",
602 verbose_name
=u
"employé"
604 nom
= models
.CharField(max_length
=255)
605 fichier
= models
.FileField(
606 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
611 verbose_name
= u
"Employé pièce"
612 verbose_name_plural
= u
"Employé pièces"
614 def __unicode__(self
):
615 return u
'%s' % (self
.nom
)
618 class EmployeCommentaire(Commentaire
):
619 employe
= models
.ForeignKey(
620 'Employe', db_column
='employe', related_name
='+'
624 verbose_name
= u
"Employé commentaire"
625 verbose_name_plural
= u
"Employé commentaires"
628 LIEN_PARENTE_CHOICES
= (
629 ('Conjoint', 'Conjoint'),
630 ('Conjointe', 'Conjointe'),
636 class AyantDroit(AUFMetadata
):
638 Personne en relation avec un Employe.
641 nom
= models
.CharField(max_length
=255)
642 prenom
= models
.CharField(u
"prénom", max_length
=255)
643 nom_affichage
= models
.CharField(
644 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
646 nationalite
= models
.ForeignKey(
647 ref
.Pays
, to_field
='code', db_column
='nationalite',
648 related_name
='ayantdroits_nationalite',
649 verbose_name
=u
"nationalité", null
=True, blank
=True
651 date_naissance
= models
.DateField(
652 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
653 validators
=[validate_date_passee
], null
=True, blank
=True
655 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
658 employe
= models
.ForeignKey(
659 'Employe', db_column
='employe', related_name
='ayantdroits',
660 verbose_name
=u
"Employé"
662 lien_parente
= models
.CharField(
663 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
664 null
=True, blank
=True
669 verbose_name
= u
"Ayant droit"
670 verbose_name_plural
= u
"Ayants droit"
672 def __unicode__(self
):
673 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
675 prefix_implantation
= "employe__dossiers__poste__implantation__region"
677 def get_regions(self
):
679 for d
in self
.employe
.dossiers
.all():
680 regions
.append(d
.poste
.implantation
.region
)
684 class AyantDroitCommentaire(Commentaire
):
685 ayant_droit
= models
.ForeignKey(
686 'AyantDroit', db_column
='ayant_droit', related_name
='+'
692 STATUT_RESIDENCE_CHOICES
= (
694 ('expat', 'Expatrié'),
697 COMPTE_COMPTA_CHOICES
= (
704 class Dossier_(AUFMetadata
, DevisableMixin
):
706 Le Dossier regroupe les informations relatives à l'occupation
707 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
710 Plusieurs Contrats peuvent être associés au Dossier.
711 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
712 lequel aucun Dossier n'existe est un poste vacant.
715 objects
= DossierManager()
718 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
719 organisme_bstg
= models
.ForeignKey(
720 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
721 verbose_name
=u
"organisme",
723 u
"Si détaché (DET) ou mis à disposition (MAD), "
724 u
"préciser l'organisme."
725 ), null
=True, blank
=True
729 remplacement
= models
.BooleanField(default
=False)
730 remplacement_de
= models
.ForeignKey(
731 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
732 null
=True, blank
=True
734 statut_residence
= models
.CharField(
735 u
"statut", max_length
=10, default
='local', null
=True,
736 choices
=STATUT_RESIDENCE_CHOICES
740 classement
= models
.ForeignKey(
741 'Classement', db_column
='classement', related_name
='+', null
=True,
744 regime_travail
= models
.DecimalField(
745 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
746 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
748 regime_travail_nb_heure_semaine
= models
.DecimalField(
749 u
"nb. heures par semaine", max_digits
=12,
750 decimal_places
=2, null
=True,
751 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
752 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
755 # Occupation du Poste par cet Employe (anciennement "mandat")
756 date_debut
= models
.DateField(u
"date de début d'occupation de poste")
757 date_fin
= models
.DateField(
758 u
"Date de fin d'occupation de poste", null
=True, blank
=True
766 ordering
= ['employe__nom', ]
767 verbose_name
= u
"Dossier"
768 verbose_name_plural
= "Dossiers"
770 def salaire_theorique(self
):
771 annee
= date
.today().year
772 coeff
= self
.classement
.coefficient
773 implantation
= self
.poste
.implantation
774 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
776 montant
= coeff
* point
.valeur
777 devise
= point
.devise
778 return {'montant': montant
, 'devise': devise
}
780 def __unicode__(self
):
781 poste
= self
.poste
.nom
782 if self
.employe
.genre
== 'F':
783 poste
= self
.poste
.nom_feminin
784 return u
'%s - %s' % (self
.employe
, poste
)
786 prefix_implantation
= "poste__implantation__region"
788 def get_regions(self
):
789 return [self
.poste
.implantation
.region
]
791 def remunerations(self
):
792 key
= "%s_remunerations" % self
._meta
.app_label
793 remunerations
= getattr(self
, key
)
794 return remunerations
.all().order_by('-date_debut')
796 def remunerations_en_cours(self
):
797 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
798 return self
.remunerations().all().filter(q
).order_by('date_debut')
800 def get_salaire(self
):
802 return [r
for r
in self
.remunerations().order_by('-date_debut')
803 if r
.type_id
== 1][0]
807 def get_salaire_euros(self
):
808 tx
= self
.taux_devise()
809 return (float)(tx
) * (float)(self
.salaire
)
811 def get_remunerations_brutes(self
):
815 4 Indemnité d'expatriation
816 5 Indemnité pour frais
817 6 Indemnité de logement
818 7 Indemnité de fonction
819 8 Indemnité de responsabilité
820 9 Indemnité de transport
821 10 Indemnité compensatrice
822 11 Indemnité de subsistance
823 12 Indemnité différentielle
824 13 Prime d'installation
827 16 Indemnité de départ
828 18 Prime de 13ième mois
831 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
832 return [r
for r
in self
.remunerations_en_cours().all()
835 def get_charges_salariales(self
):
837 20 Charges salariales ?
840 return [r
for r
in self
.remunerations_en_cours().all()
843 def get_charges_patronales(self
):
845 17 Charges patronales
848 return [r
for r
in self
.remunerations_en_cours().all()
851 def get_remunerations_tierces(self
):
855 return [r
for r
in self
.remunerations_en_cours().all()
856 if r
.type_id
in (2,)]
860 def get_total_local_charges_salariales(self
):
861 devise
= self
.poste
.get_devise()
863 for r
in self
.get_charges_salariales():
864 if r
.devise
!= devise
:
866 total
+= float(r
.montant
)
869 def get_total_local_charges_patronales(self
):
870 devise
= self
.poste
.get_devise()
872 for r
in self
.get_charges_patronales():
873 if r
.devise
!= devise
:
875 total
+= float(r
.montant
)
878 def get_local_salaire_brut(self
):
880 somme des rémuérations brutes
882 devise
= self
.poste
.get_devise()
884 for r
in self
.get_remunerations_brutes():
885 if r
.devise
!= devise
:
887 total
+= float(r
.montant
)
890 def get_local_salaire_net(self
):
892 salaire brut - charges salariales
894 devise
= self
.poste
.get_devise()
896 for r
in self
.get_charges_salariales():
897 if r
.devise
!= devise
:
899 total_charges
+= float(r
.montant
)
900 return self
.get_local_salaire_brut() - total_charges
902 def get_local_couts_auf(self
):
904 salaire net + charges patronales
906 devise
= self
.poste
.get_devise()
908 for r
in self
.get_charges_patronales():
909 if r
.devise
!= devise
:
911 total_charges
+= float(r
.montant
)
912 return self
.get_local_salaire_net() + total_charges
914 def get_total_local_remunerations_tierces(self
):
915 devise
= self
.poste
.get_devise()
917 for r
in self
.get_remunerations_tierces():
918 if r
.devise
!= devise
:
920 total
+= float(r
.montant
)
925 def get_total_charges_salariales(self
):
927 for r
in self
.get_charges_salariales():
928 total
+= r
.montant_euros()
931 def get_total_charges_patronales(self
):
933 for r
in self
.get_charges_patronales():
934 total
+= r
.montant_euros()
937 def get_salaire_brut(self
):
939 somme des rémuérations brutes
942 for r
in self
.get_remunerations_brutes():
943 total
+= r
.montant_euros()
946 def get_salaire_net(self
):
948 salaire brut - charges salariales
951 for r
in self
.get_charges_salariales():
952 total_charges
+= r
.montant_euros()
953 return self
.get_salaire_brut() - total_charges
955 def get_couts_auf(self
):
957 salaire net + charges patronales
960 for r
in self
.get_charges_patronales():
961 total_charges
+= r
.montant_euros()
962 return self
.get_salaire_net() + total_charges
964 def get_total_remunerations_tierces(self
):
966 for r
in self
.get_remunerations_tierces():
967 total
+= r
.montant_euros()
971 class Dossier(Dossier_
):
972 __doc__
= Dossier_
.__doc__
973 poste
= models
.ForeignKey('%s.Poste' % app_context(),
975 related_name
='%(app_label)s_dossiers',
976 help_text
=u
"Taper le nom du poste ou du type de poste",
978 employe
= models
.ForeignKey(
979 'Employe', db_column
='employe',
980 help_text
=u
"Taper le nom de l'employé",
981 related_name
='%(app_label)s_dossiers', verbose_name
=u
"employé")
982 principal
= models
.BooleanField(
983 u
"dossier principal", default
=True,
985 u
"Ce dossier est pour le principal poste occupé par l'employé"
990 class DossierPiece_(models
.Model
):
992 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
993 Ex.: Lettre de motivation.
995 dossier
= models
.ForeignKey(
996 '%s.Dossier' % app_context(),
997 db_column
='dossier', related_name
='%(app_label)s_dossierpieces'
999 nom
= models
.CharField(max_length
=255)
1000 fichier
= models
.FileField(
1001 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
1008 def __unicode__(self
):
1009 return u
'%s' % (self
.nom
)
1012 class DossierPiece(DossierPiece_
):
1016 class DossierCommentaire_(Commentaire
):
1017 dossier
= models
.ForeignKey(
1018 '%s.Dossier' % app_context(), db_column
='dossier', related_name
='+'
1025 class DossierCommentaire(DossierCommentaire_
):
1029 class DossierComparaison_(models
.Model
, DevisableMixin
):
1031 Photo d'une comparaison salariale au moment de l'embauche.
1033 dossier
= models
.ForeignKey(
1034 '%s.Dossier' % app_context(), related_name
='%(app_label)s_comparaisons'
1036 objects
= DossierComparaisonManager()
1038 implantation
= models
.ForeignKey(
1039 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1041 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1042 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1043 montant
= models
.IntegerField(null
=True)
1044 devise
= models
.ForeignKey(
1045 'Devise', related_name
='+', null
=True, blank
=True
1051 def __unicode__(self
):
1052 return "%s (%s)" % (self
.poste
, self
.personne
)
1055 class DossierComparaison(DossierComparaison_
):
1061 class RemunerationMixin(AUFMetadata
):
1062 dossier
= models
.ForeignKey(
1063 '%s.Dossier' % app_context(), db_column
='dossier',
1064 related_name
='%(app_label)s_remunerations'
1068 type = models
.ForeignKey(
1069 'TypeRemuneration', db_column
='type', related_name
='+',
1070 verbose_name
=u
"type de rémunération"
1072 type_revalorisation
= models
.ForeignKey(
1073 'TypeRevalorisation', db_column
='type_revalorisation',
1074 related_name
='+', verbose_name
=u
"type de revalorisation",
1075 null
=True, blank
=True
1077 montant
= models
.DecimalField(
1078 null
=True, blank
=True,
1079 default
=0, max_digits
=12, decimal_places
=2
1080 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1081 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1083 # commentaire = precision
1084 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1086 # date_debut = anciennement date_effectif
1087 date_debut
= models
.DateField(u
"date de début", null
=True, blank
=True)
1088 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1092 ordering
= ['type__nom', '-date_fin']
1094 def __unicode__(self
):
1095 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1098 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1100 Structure de rémunération (données budgétaires) en situation normale
1101 pour un Dossier. Si un Evenement existe, utiliser la structure de
1102 rémunération EvenementRemuneration de cet événement.
1105 def montant_mois(self
):
1106 return round(self
.montant
/ 12, 2)
1108 def montant_avec_regime(self
):
1109 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1111 def montant_euro_mois(self
):
1112 return round(self
.montant_euros() / 12, 2)
1114 def __unicode__(self
):
1116 devise
= self
.devise
.code
1119 return "%s %s" % (self
.montant
, devise
)
1123 verbose_name
= u
"Rémunération"
1124 verbose_name_plural
= u
"Rémunérations"
1127 class Remuneration(Remuneration_
):
1133 class ContratManager(NoDeleteManager
):
1134 def get_query_set(self
):
1135 return super(ContratManager
, self
).get_query_set() \
1136 .select_related('dossier', 'dossier__poste')
1139 class Contrat_(AUFMetadata
):
1141 Document juridique qui encadre la relation de travail d'un Employe
1142 pour un Poste particulier. Pour un Dossier (qui documente cette
1143 relation de travail) plusieurs contrats peuvent être associés.
1145 objects
= ContratManager()
1146 dossier
= models
.ForeignKey(
1147 '%s.Dossier' % app_context(), db_column
='dossier',
1148 related_name
='%(app_label)s_contrats'
1150 type_contrat
= models
.ForeignKey(
1151 'TypeContrat', db_column
='type_contrat',
1152 verbose_name
=u
'type de contrat', related_name
='+'
1154 date_debut
= models
.DateField(u
"date de début")
1155 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1156 fichier
= models
.FileField(
1157 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1163 ordering
= ['dossier__employe__nom']
1164 verbose_name
= u
"Contrat"
1165 verbose_name_plural
= u
"Contrats"
1167 def __unicode__(self
):
1168 return u
'%s - %s' % (self
.dossier
, self
.id)
1171 class Contrat(Contrat_
):
1176 #class Evenement_(AUFMetadata):
1178 # Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
1179 # d'un Dossier qui vient altérer des informations normales liées à un
1180 # Dossier (ex.: la Remuneration).
1182 # Ex.: congé de maternité, maladie...
1184 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
1185 # différent et une rémunération en conséquence. On souhaite toutefois
1186 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
1187 # du retour à la normale.
1189 # dossier = models.ForeignKey(
1190 # '%s.Dossier' % app_context(), db_column='dossier',
1193 # nom = models.CharField(max_length=255)
1194 # date_debut = models.DateField(verbose_name = u"Date de début")
1195 # date_fin = models.DateField(verbose_name = u"Date de fin",
1196 # null=True, blank=True)
1200 # ordering = ['nom']
1201 # verbose_name = u"Évènement"
1202 # verbose_name_plural = u"Évènements"
1204 # def __unicode__(self):
1205 # return u'%s' % (self.nom)
1208 #class Evenement(Evenement_):
1209 # __doc__ = Evenement_.__doc__
1212 #class EvenementRemuneration_(RemunerationMixin):
1214 # Structure de rémunération liée à un Evenement qui remplace
1215 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
1218 # evenement = models.ForeignKey("Evenement", db_column='evenement',
1220 # verbose_name = u"Évènement")
1221 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
1222 # # de l'Evenement associé
1226 # ordering = ['evenement', 'type__nom', '-date_fin']
1227 # verbose_name = u"Évènement - rémunération"
1228 # verbose_name_plural = u"Évènements - rémunérations"
1231 #class EvenementRemuneration(EvenementRemuneration_):
1232 # __doc__ = EvenementRemuneration_.__doc__
1238 #class EvenementRemuneration(EvenementRemuneration_):
1239 # __doc__ = EvenementRemuneration_.__doc__
1240 # TODO? class ContratPiece(models.Model):
1245 class CategorieEmploi(AUFMetadata
):
1247 Catégorie utilisée dans la gestion des Postes.
1248 Catégorie supérieure à TypePoste.
1250 nom
= models
.CharField(max_length
=255)
1254 verbose_name
= u
"catégorie d'emploi"
1255 verbose_name_plural
= u
"catégories d'emploi"
1257 def __unicode__(self
):
1261 class FamilleProfessionnelle(models
.Model
):
1263 Famille professionnelle d'un poste.
1265 nom
= models
.CharField(max_length
=100)
1269 verbose_name
= u
'famille professionnelle'
1270 verbose_name_plural
= u
'familles professionnelles'
1272 def __unicode__(self
):
1276 class TypePoste(AUFMetadata
):
1280 nom
= models
.CharField(max_length
=255)
1281 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1282 is_responsable
= models
.BooleanField(
1283 u
"poste de responsabilité", default
=False
1285 categorie_emploi
= models
.ForeignKey(
1286 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1287 verbose_name
=u
"catégorie d'emploi"
1289 famille_professionnelle
= models
.ForeignKey(
1290 FamilleProfessionnelle
, related_name
='types_de_poste',
1291 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1296 verbose_name
= u
"Type de poste"
1297 verbose_name_plural
= u
"Types de poste"
1299 def __unicode__(self
):
1300 return u
'%s' % (self
.nom
)
1302 TYPE_PAIEMENT_CHOICES
= (
1303 (u
'Régulier', u
'Régulier'),
1304 (u
'Ponctuel', u
'Ponctuel'),
1307 NATURE_REMUNERATION_CHOICES
= (
1308 (u
'Accessoire', u
'Accessoire'),
1309 (u
'Charges', u
'Charges'),
1310 (u
'Indemnité', u
'Indemnité'),
1311 (u
'RAS', u
'Rémunération autre source'),
1312 (u
'Traitement', u
'Traitement'),
1316 class TypeRemuneration(AUFMetadata
):
1318 Catégorie de Remuneration.
1320 objects
= TypeRemunerationManager()
1322 nom
= models
.CharField(max_length
=255)
1323 type_paiement
= models
.CharField(
1324 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1326 nature_remuneration
= models
.CharField(
1327 u
"nature de la rémunération", max_length
=30,
1328 choices
=NATURE_REMUNERATION_CHOICES
1330 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1334 verbose_name
= u
"Type de rémunération"
1335 verbose_name_plural
= u
"Types de rémunération"
1337 def __unicode__(self
):
1339 archive
= u
"(archivé)"
1342 return u
'%s %s' % (self
.nom
, archive
)
1345 class TypeRevalorisation(AUFMetadata
):
1347 Justification du changement de la Remuneration.
1348 (Actuellement utilisé dans aucun traitement informatique.)
1350 nom
= models
.CharField(max_length
=255)
1354 verbose_name
= u
"Type de revalorisation"
1355 verbose_name_plural
= u
"Types de revalorisation"
1357 def __unicode__(self
):
1358 return u
'%s' % (self
.nom
)
1361 class Service(AUFMetadata
):
1363 Unité administrative où les Postes sont rattachés.
1365 objects
= ServiceManager()
1367 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1368 nom
= models
.CharField(max_length
=255)
1372 verbose_name
= u
"Service"
1373 verbose_name_plural
= u
"Services"
1375 def __unicode__(self
):
1377 archive
= u
"(archivé)"
1380 return u
'%s %s' % (self
.nom
, archive
)
1383 TYPE_ORGANISME_CHOICES
= (
1384 ('MAD', 'Mise à disposition'),
1385 ('DET', 'Détachement'),
1389 class OrganismeBstg(AUFMetadata
):
1391 Organisation d'où provient un Employe mis à disposition (MAD) de
1392 ou détaché (DET) à l'AUF à titre gratuit.
1394 (BSTG = bien et service à titre gratuit.)
1396 nom
= models
.CharField(max_length
=255)
1397 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1398 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1400 related_name
='organismes_bstg',
1401 null
=True, blank
=True)
1404 ordering
= ['type', 'nom']
1405 verbose_name
= u
"Organisme BSTG"
1406 verbose_name_plural
= u
"Organismes BSTG"
1408 def __unicode__(self
):
1409 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1411 prefix_implantation
= "pays__region"
1413 def get_regions(self
):
1414 return [self
.pays
.region
]
1417 class Statut(AUFMetadata
):
1419 Statut de l'Employe dans le cadre d'un Dossier particulier.
1422 code
= models
.CharField(
1423 max_length
=25, unique
=True,
1425 u
"Saisir un code court mais lisible pour ce statut : "
1426 u
"le code est utilisé pour associer les statuts aux autres "
1427 u
"données tout en demeurant plus lisible qu'un identifiant "
1431 nom
= models
.CharField(max_length
=255)
1435 verbose_name
= u
"Statut d'employé"
1436 verbose_name_plural
= u
"Statuts d'employé"
1438 def __unicode__(self
):
1439 return u
'%s : %s' % (self
.code
, self
.nom
)
1442 TYPE_CLASSEMENT_CHOICES
= (
1443 ('S', 'S -Soutien'),
1444 ('T', 'T - Technicien'),
1445 ('P', 'P - Professionel'),
1447 ('D', 'D - Direction'),
1448 ('SO', 'SO - Sans objet [expatriés]'),
1449 ('HG', 'HG - Hors grille [direction]'),
1453 class ClassementManager(models
.Manager
):
1455 Ordonner les spcéfiquement les classements.
1457 def get_query_set(self
):
1458 qs
= super(self
.__class__
, self
).get_query_set()
1459 qs
= qs
.extra(select
={
1460 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1462 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1466 class Classement_(AUFMetadata
):
1468 Éléments de classement de la
1469 "Grille générique de classement hiérarchique".
1471 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1472 classement dans la grille. Le classement donne le coefficient utilisé dans:
1474 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1476 objects
= ClassementManager()
1479 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1480 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1481 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1482 coefficient
= models
.FloatField(u
"coefficient", default
=0, null
=True)
1485 # annee # au lieu de date_debut et date_fin
1486 commentaire
= models
.TextField(null
=True, blank
=True)
1490 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1491 verbose_name
= u
"Classement"
1492 verbose_name_plural
= u
"Classements"
1494 def __unicode__(self
):
1495 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1498 class Classement(Classement_
):
1499 __doc__
= Classement_
.__doc__
1502 class TauxChange_(AUFMetadata
):
1504 Taux de change de la devise vers l'euro (EUR)
1505 pour chaque année budgétaire.
1508 devise
= models
.ForeignKey('Devise', db_column
='devise')
1509 annee
= models
.IntegerField(u
"année")
1510 taux
= models
.FloatField(u
"taux vers l'euro")
1514 ordering
= ['-annee', 'devise__code']
1515 verbose_name
= u
"Taux de change"
1516 verbose_name_plural
= u
"Taux de change"
1518 def __unicode__(self
):
1519 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1522 class TauxChange(TauxChange_
):
1523 __doc__
= TauxChange_
.__doc__
1526 class ValeurPointManager(NoDeleteManager
):
1528 def get_query_set(self
):
1529 return super(ValeurPointManager
, self
).get_query_set() \
1530 .select_related('devise', 'implantation')
1533 class ValeurPoint_(AUFMetadata
):
1535 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1536 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1537 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1539 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1542 actuelles
= ValeurPointManager()
1544 valeur
= models
.FloatField(null
=True)
1545 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1546 implantation
= models
.ForeignKey(ref
.Implantation
,
1547 db_column
='implantation',
1548 related_name
='%(app_label)s_valeur_point')
1550 annee
= models
.IntegerField()
1553 ordering
= ['-annee', 'implantation__nom']
1555 verbose_name
= u
"Valeur du point"
1556 verbose_name_plural
= u
"Valeurs du point"
1558 def __unicode__(self
):
1559 return u
'%s %s %s [%s] %s' % (
1560 self
.devise
.code
, self
.annee
, self
.valeur
,
1561 self
.implantation
.nom_court
, self
.devise
.nom
1565 class ValeurPoint(ValeurPoint_
):
1566 __doc__
= ValeurPoint_
.__doc__
1569 class Devise(AUFMetadata
):
1573 objects
= DeviseManager()
1575 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1576 code
= models
.CharField(max_length
=10, unique
=True)
1577 nom
= models
.CharField(max_length
=255)
1581 verbose_name
= u
"Devise"
1582 verbose_name_plural
= u
"Devises"
1584 def __unicode__(self
):
1585 return u
'%s - %s' % (self
.code
, self
.nom
)
1588 class TypeContrat(AUFMetadata
):
1592 nom
= models
.CharField(max_length
=255)
1593 nom_long
= models
.CharField(max_length
=255)
1597 verbose_name
= u
"Type de contrat"
1598 verbose_name_plural
= u
"Types de contrat"
1600 def __unicode__(self
):
1601 return u
'%s' % (self
.nom
)
1606 class ResponsableImplantationProxy(ref
.Implantation
):
1610 verbose_name
= u
"Responsable d'implantation"
1611 verbose_name_plural
= u
"Responsables d'implantation"
1614 class ResponsableImplantation(models
.Model
):
1616 Le responsable d'une implantation.
1617 Anciennement géré sur le Dossier du responsable.
1619 employe
= models
.ForeignKey(
1620 'Employe', db_column
='employe', related_name
='+', null
=True,
1623 implantation
= models
.OneToOneField(
1624 "ResponsableImplantationProxy", db_column
='implantation',
1625 related_name
='responsable', unique
=True
1628 def __unicode__(self
):
1629 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1632 ordering
= ['implantation__nom']
1633 verbose_name
= "Responsable d'implantation"
1634 verbose_name_plural
= "Responsables d'implantation"