1 # -=- encoding: utf-8 -=-
4 from datetime
import date
5 from decimal
import Decimal
7 from django
.core
.files
.storage
import FileSystemStorage
8 from django
.db
import models
9 from django
.db
.models
import Q
10 from django
.conf
import settings
12 from auf
.django
.emploi
.models
import \
13 GENRE_CHOICES
, SITUATION_CHOICES
# devrait plutot être dans references
14 from auf
.django
.metadata
.models
import AUFMetadata
15 from auf
.django
.metadata
.managers
import NoDeleteManager
16 from auf
.django
.references
import models
as ref
18 from project
.rh
.change_list
import \
19 RechercheTemporelle
, KEY_STATUT
, STATUT_ACTIF
, STATUT_INACTIF
, \
21 from project
.rh
.managers
import \
22 PosteManager
, DossierManager
, EmployeManager
, \
23 DossierComparaisonManager
, \
24 PosteComparaisonManager
, DeviseManager
, ServiceManager
, \
25 TypeRemunerationManager
26 from project
.rh
.validators
import validate_date_passee
30 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
31 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
32 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
33 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
= \
34 "Saisir le nombre d'heure de travail à temps complet (100%), " \
35 "sans tenir compte du régime de travail"
38 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
39 base_url
=settings
.PRIVE_MEDIA_URL
)
42 def poste_piece_dispatch(instance
, filename
):
43 path
= "%s/poste/%s/%s" % (
44 instance
._meta
.app_label
, instance
.poste_id
, filename
49 def dossier_piece_dispatch(instance
, filename
):
50 path
= "%s/dossier/%s/%s" % (
51 instance
._meta
.app_label
, instance
.dossier_id
, filename
56 def employe_piece_dispatch(instance
, filename
):
57 path
= "%s/employe/%s/%s" % (
58 instance
._meta
.app_label
, instance
.employe_id
, filename
63 def contrat_dispatch(instance
, filename
):
64 path
= "%s/contrat/%s/%s" % (
65 instance
._meta
.app_label
, instance
.dossier_id
, filename
70 class DevisableMixin(object):
72 def get_annee_pour_taux_devise(self
):
73 return datetime
.datetime
.now().year
75 def taux_devise(self
, devise
=None):
81 if devise
.code
== "EUR":
84 annee
= self
.get_annee_pour_taux_devise()
87 for tc
in TauxChange
.objects
.filter(devise
=devise
, annee
=annee
)
93 u
"Pas de taux pour %s en %s" % (devise
.code
, annee
)
97 raise Exception(u
"Il existe plusieurs taux de %s en %s" %
102 def montant_euros(self
):
104 taux
= self
.taux_devise()
109 return int(round(float(self
.montant
) * float(taux
), 2))
112 class Commentaire(AUFMetadata
):
113 texte
= models
.TextField()
114 owner
= models
.ForeignKey(
115 'auth.User', db_column
='owner', related_name
='+',
116 verbose_name
=u
"Commentaire de"
121 ordering
= ['-date_creation']
123 def __unicode__(self
):
124 return u
'%s' % (self
.texte
)
129 POSTE_APPEL_CHOICES
= (
130 ('interne', 'Interne'),
131 ('externe', 'Externe'),
135 class Poste_(AUFMetadata
):
137 Un Poste est un emploi (job) à combler dans une implantation.
138 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
139 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
142 objects
= PosteManager()
145 nom
= models
.CharField(u
"Titre du poste", max_length
=255)
146 nom_feminin
= models
.CharField(
147 u
"Titre du poste (au féminin)", max_length
=255, null
=True
149 implantation
= models
.ForeignKey(
151 help_text
=u
"Taper le nom de l'implantation ou sa région",
152 db_column
='implantation', related_name
='+'
154 type_poste
= models
.ForeignKey(
155 'TypePoste', db_column
='type_poste',
156 help_text
=u
"Taper le nom du type de poste", related_name
='+',
157 null
=True, verbose_name
=u
"type de poste"
159 service
= models
.ForeignKey(
160 'Service', db_column
='service', related_name
='%(app_label)s_postes',
161 verbose_name
=u
"direction/service/pôle support", null
=True
163 responsable
= models
.ForeignKey(
164 'Poste', db_column
='responsable',
165 related_name
='+', null
=True,
166 help_text
=u
"Taper le nom du poste ou du type de poste",
167 verbose_name
=u
"Poste du responsable"
171 regime_travail
= models
.DecimalField(
172 u
"temps de travail", max_digits
=12, decimal_places
=2,
173 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
174 help_text
="% du temps complet"
176 regime_travail_nb_heure_semaine
= models
.DecimalField(
177 u
"nb. heures par semaine", max_digits
=12, decimal_places
=2,
178 null
=True, default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
179 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
183 local
= models
.NullBooleanField(
184 u
"local", default
=True, null
=True, blank
=True
186 expatrie
= models
.NullBooleanField(
187 u
"expatrié", default
=False, null
=True, blank
=True
189 mise_a_disposition
= models
.NullBooleanField(
190 u
"mise à disposition", null
=True, default
=False
192 appel
= models
.CharField(
193 u
"Appel à candidature", max_length
=10, null
=True,
194 choices
=POSTE_APPEL_CHOICES
, default
='interne'
198 classement_min
= models
.ForeignKey(
199 'Classement', db_column
='classement_min', related_name
='+',
200 null
=True, blank
=True
202 classement_max
= models
.ForeignKey(
203 'Classement', db_column
='classement_max', related_name
='+',
204 null
=True, blank
=True
206 valeur_point_min
= models
.ForeignKey(
208 help_text
=u
"Taper le code ou le nom de l'implantation",
209 db_column
='valeur_point_min', related_name
='+', null
=True,
212 valeur_point_max
= models
.ForeignKey(
214 help_text
=u
"Taper le code ou le nom de l'implantation",
215 db_column
='valeur_point_max', related_name
='+', null
=True,
218 devise_min
= models
.ForeignKey(
219 'Devise', db_column
='devise_min', null
=True, related_name
='+'
221 devise_max
= models
.ForeignKey(
222 'Devise', db_column
='devise_max', null
=True, related_name
='+'
224 salaire_min
= models
.DecimalField(
225 max_digits
=12, decimal_places
=2, null
=True, default
=0
227 salaire_max
= models
.DecimalField(
228 max_digits
=12, decimal_places
=2, null
=True, default
=0
230 indemn_min
= models
.DecimalField(
231 max_digits
=12, decimal_places
=2, null
=True, default
=0
233 indemn_max
= models
.DecimalField(
234 max_digits
=12, decimal_places
=2, null
=True, default
=0
236 autre_min
= models
.DecimalField(
237 max_digits
=12, decimal_places
=2, null
=True, default
=0
239 autre_max
= models
.DecimalField(
240 max_digits
=12, decimal_places
=2, null
=True, default
=0
243 # Comparatifs de rémunération
244 devise_comparaison
= models
.ForeignKey(
245 'Devise', null
=True, blank
=True, db_column
='devise_comparaison',
248 comp_locale_min
= models
.DecimalField(
249 max_digits
=12, decimal_places
=2, null
=True, blank
=True
251 comp_locale_max
= models
.DecimalField(
252 max_digits
=12, decimal_places
=2, null
=True, blank
=True
254 comp_universite_min
= models
.DecimalField(
255 max_digits
=12, decimal_places
=2, null
=True, blank
=True
257 comp_universite_max
= models
.DecimalField(
258 max_digits
=12, decimal_places
=2, null
=True, blank
=True
260 comp_fonctionpub_min
= models
.DecimalField(
261 max_digits
=12, decimal_places
=2, null
=True, blank
=True
263 comp_fonctionpub_max
= models
.DecimalField(
264 max_digits
=12, decimal_places
=2, null
=True, blank
=True
266 comp_ong_min
= models
.DecimalField(
267 max_digits
=12, decimal_places
=2, null
=True, blank
=True
269 comp_ong_max
= models
.DecimalField(
270 max_digits
=12, decimal_places
=2, null
=True, blank
=True
272 comp_autre_min
= models
.DecimalField(
273 max_digits
=12, decimal_places
=2, null
=True, blank
=True
275 comp_autre_max
= models
.DecimalField(
276 max_digits
=12, decimal_places
=2, null
=True, blank
=True
280 justification
= models
.TextField(null
=True, blank
=True)
283 date_debut
= models
.DateField(
284 u
"date de début", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True
286 date_fin
= models
.DateField(
287 u
"date de fin", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True
292 ordering
= ['implantation__nom', 'nom']
293 verbose_name
= u
"Poste"
294 verbose_name_plural
= u
"Postes"
297 def __unicode__(self
):
298 representation
= u
'%s - %s [%s]' % (
299 self
.implantation
, self
.nom
, self
.id
301 return representation
303 prefix_implantation
= "implantation__region"
305 def get_regions(self
):
306 return [self
.implantation
.region
]
308 def get_devise(self
):
309 vp
= ValeurPoint
.objects
.filter(
310 implantation
=self
.implantation
, devise__archive
=False
315 return Devise
.objects
.get(code
='EUR')
319 __doc__
= Poste_
.__doc__
321 # meta dématérialisation : pour permettre le filtrage
322 vacant
= models
.NullBooleanField(u
"vacant", null
=True, blank
=True)
326 if self
.occupe_par():
330 def occupe_par(self
):
332 Retourne la liste d'employé occupant ce poste.
333 Généralement, retourne une liste d'un élément.
334 Si poste inoccupé, retourne liste vide.
335 UTILISE pour mettre a jour le flag vacant
338 d
.employe
for d
in self
.rh_dossiers
339 .filter(supprime
=False)
340 .exclude(date_fin__lt
=date
.today())
344 POSTE_FINANCEMENT_CHOICES
= (
345 ('A', 'A - Frais de personnel'),
346 ('B', 'B - Projet(s)-Titre(s)'),
351 class PosteFinancement_(models
.Model
):
353 Pour un Poste, structure d'informations décrivant comment on prévoit
356 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
357 pourcentage
= models
.DecimalField(
358 max_digits
=12, decimal_places
=2,
359 help_text
="ex.: 33.33 % (décimale avec point)"
361 commentaire
= models
.TextField(
362 help_text
="Spécifiez la source de financement."
369 def __unicode__(self
):
370 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
373 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
376 class PosteFinancement(PosteFinancement_
):
377 poste
= models
.ForeignKey(
378 Poste
, db_column
='poste', related_name
='rh_financements'
382 class PostePiece_(models
.Model
):
384 Documents relatifs au Poste.
385 Ex.: Description de poste
387 nom
= models
.CharField(u
"Nom", max_length
=255)
388 fichier
= models
.FileField(
389 u
"Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
396 def __unicode__(self
):
397 return u
'%s' % (self
.nom
)
400 class PostePiece(PostePiece_
):
401 poste
= models
.ForeignKey(
402 Poste
, db_column
='poste', related_name
='rh_pieces'
406 class PosteComparaison_(AUFMetadata
, DevisableMixin
):
408 De la même manière qu'un dossier, un poste peut-être comparé à un autre
411 objects
= PosteComparaisonManager()
413 implantation
= models
.ForeignKey(
414 ref
.Implantation
, null
=True, blank
=True, related_name
="+"
416 nom
= models
.CharField(u
"Poste", max_length
=255, null
=True, blank
=True)
417 montant
= models
.IntegerField(null
=True)
418 devise
= models
.ForeignKey(
419 "Devise", related_name
='+', null
=True, blank
=True
425 def __unicode__(self
):
429 class PosteComparaison(PosteComparaison_
):
430 poste
= models
.ForeignKey(
431 Poste
, related_name
='rh_comparaisons_internes'
434 objects
= NoDeleteManager()
437 class PosteCommentaire(Commentaire
):
438 poste
= models
.ForeignKey(
439 Poste
, db_column
='poste', related_name
='commentaires'
446 class Employe(AUFMetadata
):
448 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
449 Dossiers qu'il occupe ou a occupé de Postes.
451 Cette classe aurait pu avantageusement s'appeler Personne car la notion
452 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
455 objects
= EmployeManager()
458 nom
= models
.CharField(max_length
=255)
459 prenom
= models
.CharField(u
"prénom", max_length
=255)
460 nom_affichage
= models
.CharField(
461 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
463 nationalite
= models
.ForeignKey(
464 ref
.Pays
, to_field
='code', db_column
='nationalite',
465 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
466 blank
=True, null
=True
468 date_naissance
= models
.DateField(
469 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
470 validators
=[validate_date_passee
], null
=True, blank
=True
472 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
475 situation_famille
= models
.CharField(
476 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
477 null
=True, blank
=True
479 date_entree
= models
.DateField(
480 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
485 tel_domicile
= models
.CharField(
486 u
"tél. domicile", max_length
=255, null
=True, blank
=True
488 tel_cellulaire
= models
.CharField(
489 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
491 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
492 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
493 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
494 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
495 pays
= models
.ForeignKey(
496 ref
.Pays
, to_field
='code', db_column
='pays',
497 related_name
='employes', null
=True, blank
=True
499 courriel_perso
= models
.EmailField(
500 u
'adresse courriel personnelle', blank
=True
503 # meta dématérialisation : pour permettre le filtrage
504 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
507 ordering
= ['nom', 'prenom']
508 verbose_name
= u
"Employé"
509 verbose_name_plural
= u
"Employés"
511 def __unicode__(self
):
512 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
516 if self
.genre
.upper() == u
'M':
518 elif self
.genre
.upper() == u
'F':
524 Retourne l'URL du service retournant la photo de l'Employe.
525 Équivalent reverse url 'rh_photo' avec id en param.
527 from django
.core
.urlresolvers
import reverse
528 return reverse('rh_photo', kwargs
={'id': self
.id})
530 def dossiers_passes(self
):
531 params
= {KEY_STATUT
: STATUT_INACTIF
, }
532 search
= RechercheTemporelle(params
, self
.__class__
)
533 search
.purge_params(params
)
534 q
= search
.get_q_temporel(self
.rh_dossiers
)
535 return self
.rh_dossiers
.filter(q
)
537 def dossiers_futurs(self
):
538 params
= {KEY_STATUT
: STATUT_FUTUR
, }
539 search
= RechercheTemporelle(params
, self
.__class__
)
540 search
.purge_params(params
)
541 q
= search
.get_q_temporel(self
.rh_dossiers
)
542 return self
.rh_dossiers
.filter(q
)
544 def dossiers_encours(self
):
545 params
= {KEY_STATUT
: STATUT_ACTIF
, }
546 search
= RechercheTemporelle(params
, self
.__class__
)
547 search
.purge_params(params
)
548 q
= search
.get_q_temporel(self
.rh_dossiers
)
549 return self
.rh_dossiers
.filter(q
)
551 def dossier_principal(self
):
552 """Retourne le dossier principal
553 (ou le plus ancien si il y en a plusieurs)
556 dossier
= self
.rh_dossiers
.filter(principal
=True).order_by('date_debut')[0]
557 except IndexError, Dossier
.DoesNotExist
:
561 def postes_encours(self
):
562 postes_encours
= set()
563 for d
in self
.dossiers_encours():
564 postes_encours
.add(d
.poste
)
565 return postes_encours
567 def poste_principal(self
):
569 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
571 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
573 # DEPRECATED : on a maintenant Dossier.principal
574 poste
= Poste
.objects
.none()
576 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
581 prefix_implantation
= "rh_dossiers__poste__implantation__region"
583 def get_regions(self
):
585 for d
in self
.dossiers
.all():
586 regions
.append(d
.poste
.implantation
.region
)
590 class EmployePiece(models
.Model
):
592 Documents relatifs à un employé.
595 employe
= models
.ForeignKey(
596 'Employe', db_column
='employe', related_name
="pieces",
597 verbose_name
=u
"employé"
599 nom
= models
.CharField(max_length
=255)
600 fichier
= models
.FileField(
601 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
606 verbose_name
= u
"Employé pièce"
607 verbose_name_plural
= u
"Employé pièces"
609 def __unicode__(self
):
610 return u
'%s' % (self
.nom
)
613 class EmployeCommentaire(Commentaire
):
614 employe
= models
.ForeignKey(
615 'Employe', db_column
='employe', related_name
='+'
619 verbose_name
= u
"Employé commentaire"
620 verbose_name_plural
= u
"Employé commentaires"
623 LIEN_PARENTE_CHOICES
= (
624 ('Conjoint', 'Conjoint'),
625 ('Conjointe', 'Conjointe'),
631 class AyantDroit(AUFMetadata
):
633 Personne en relation avec un Employe.
636 nom
= models
.CharField(max_length
=255)
637 prenom
= models
.CharField(u
"prénom", max_length
=255)
638 nom_affichage
= models
.CharField(
639 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
641 nationalite
= models
.ForeignKey(
642 ref
.Pays
, to_field
='code', db_column
='nationalite',
643 related_name
='ayantdroits_nationalite',
644 verbose_name
=u
"nationalité", null
=True, blank
=True
646 date_naissance
= models
.DateField(
647 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
648 validators
=[validate_date_passee
], null
=True, blank
=True
650 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
653 employe
= models
.ForeignKey(
654 'Employe', db_column
='employe', related_name
='ayantdroits',
655 verbose_name
=u
"Employé"
657 lien_parente
= models
.CharField(
658 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
659 null
=True, blank
=True
664 verbose_name
= u
"Ayant droit"
665 verbose_name_plural
= u
"Ayants droit"
667 def __unicode__(self
):
668 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
670 prefix_implantation
= "employe__dossiers__poste__implantation__region"
672 def get_regions(self
):
674 for d
in self
.employe
.dossiers
.all():
675 regions
.append(d
.poste
.implantation
.region
)
679 class AyantDroitCommentaire(Commentaire
):
680 ayant_droit
= models
.ForeignKey(
681 'AyantDroit', db_column
='ayant_droit', related_name
='+'
687 STATUT_RESIDENCE_CHOICES
= (
689 ('expat', 'Expatrié'),
692 COMPTE_COMPTA_CHOICES
= (
699 class Dossier_(AUFMetadata
, DevisableMixin
):
701 Le Dossier regroupe les informations relatives à l'occupation
702 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
705 Plusieurs Contrats peuvent être associés au Dossier.
706 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
707 lequel aucun Dossier n'existe est un poste vacant.
710 objects
= DossierManager()
713 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
714 organisme_bstg
= models
.ForeignKey(
715 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
716 verbose_name
=u
"organisme",
718 u
"Si détaché (DET) ou mis à disposition (MAD), "
719 u
"préciser l'organisme."
720 ), null
=True, blank
=True
724 remplacement
= models
.BooleanField(default
=False)
725 remplacement_de
= models
.ForeignKey(
726 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
727 null
=True, blank
=True
729 statut_residence
= models
.CharField(
730 u
"statut", max_length
=10, default
='local', null
=True,
731 choices
=STATUT_RESIDENCE_CHOICES
735 classement
= models
.ForeignKey(
736 'Classement', db_column
='classement', related_name
='+', null
=True,
739 regime_travail
= models
.DecimalField(
740 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
741 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
743 regime_travail_nb_heure_semaine
= models
.DecimalField(
744 u
"nb. heures par semaine", max_digits
=12,
745 decimal_places
=2, null
=True,
746 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
747 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
750 # Occupation du Poste par cet Employe (anciennement "mandat")
751 date_debut
= models
.DateField(u
"date de début d'occupation de poste")
752 date_fin
= models
.DateField(
753 u
"Date de fin d'occupation de poste", null
=True, blank
=True
761 ordering
= ['employe__nom', ]
762 verbose_name
= u
"Dossier"
763 verbose_name_plural
= "Dossiers"
765 def salaire_theorique(self
):
766 annee
= date
.today().year
767 coeff
= self
.classement
.coefficient
768 implantation
= self
.poste
.implantation
769 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
771 montant
= coeff
* point
.valeur
772 devise
= point
.devise
773 return {'montant': montant
, 'devise': devise
}
775 def __unicode__(self
):
776 poste
= self
.poste
.nom
777 if self
.employe
.genre
== 'F':
778 poste
= self
.poste
.nom_feminin
779 return u
'%s - %s' % (self
.employe
, poste
)
781 prefix_implantation
= "poste__implantation__region"
783 def get_regions(self
):
784 return [self
.poste
.implantation
.region
]
786 def remunerations(self
):
787 key
= "%s_remunerations" % self
._meta
.app_label
788 remunerations
= getattr(self
, key
)
789 return remunerations
.all().order_by('-date_debut')
791 def remunerations_en_cours(self
):
792 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
793 return self
.remunerations().all().filter(q
).order_by('date_debut')
795 def get_salaire(self
):
797 return [r
for r
in self
.remunerations().order_by('-date_debut')
798 if r
.type_id
== 1][0]
802 def get_salaire_euros(self
):
803 tx
= self
.taux_devise()
804 return (float)(tx
) * (float)(self
.salaire
)
806 def get_remunerations_brutes(self
):
810 4 Indemnité d'expatriation
811 5 Indemnité pour frais
812 6 Indemnité de logement
813 7 Indemnité de fonction
814 8 Indemnité de responsabilité
815 9 Indemnité de transport
816 10 Indemnité compensatrice
817 11 Indemnité de subsistance
818 12 Indemnité différentielle
819 13 Prime d'installation
822 16 Indemnité de départ
823 18 Prime de 13ième mois
826 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
827 return [r
for r
in self
.remunerations_en_cours().all()
830 def get_charges_salariales(self
):
832 20 Charges salariales ?
835 return [r
for r
in self
.remunerations_en_cours().all()
838 def get_charges_patronales(self
):
840 17 Charges patronales
843 return [r
for r
in self
.remunerations_en_cours().all()
846 def get_remunerations_tierces(self
):
850 return [r
for r
in self
.remunerations_en_cours().all()
851 if r
.type_id
in (2,)]
855 def get_total_local_charges_salariales(self
):
856 devise
= self
.poste
.get_devise()
858 for r
in self
.get_charges_salariales():
859 if r
.devise
!= devise
:
861 total
+= float(r
.montant
)
864 def get_total_local_charges_patronales(self
):
865 devise
= self
.poste
.get_devise()
867 for r
in self
.get_charges_patronales():
868 if r
.devise
!= devise
:
870 total
+= float(r
.montant
)
873 def get_local_salaire_brut(self
):
875 somme des rémuérations brutes
877 devise
= self
.poste
.get_devise()
879 for r
in self
.get_remunerations_brutes():
880 if r
.devise
!= devise
:
882 total
+= float(r
.montant
)
885 def get_local_salaire_net(self
):
887 salaire brut - charges salariales
889 devise
= self
.poste
.get_devise()
891 for r
in self
.get_charges_salariales():
892 if r
.devise
!= devise
:
894 total_charges
+= float(r
.montant
)
895 return self
.get_local_salaire_brut() - total_charges
897 def get_local_couts_auf(self
):
899 salaire net + charges patronales
901 devise
= self
.poste
.get_devise()
903 for r
in self
.get_charges_patronales():
904 if r
.devise
!= devise
:
906 total_charges
+= float(r
.montant
)
907 return self
.get_local_salaire_net() + total_charges
909 def get_total_local_remunerations_tierces(self
):
910 devise
= self
.poste
.get_devise()
912 for r
in self
.get_remunerations_tierces():
913 if r
.devise
!= devise
:
915 total
+= float(r
.montant
)
920 def get_total_charges_salariales(self
):
922 for r
in self
.get_charges_salariales():
923 total
+= r
.montant_euros()
926 def get_total_charges_patronales(self
):
928 for r
in self
.get_charges_patronales():
929 total
+= r
.montant_euros()
932 def get_salaire_brut(self
):
934 somme des rémuérations brutes
937 for r
in self
.get_remunerations_brutes():
938 total
+= r
.montant_euros()
941 def get_salaire_net(self
):
943 salaire brut - charges salariales
946 for r
in self
.get_charges_salariales():
947 total_charges
+= r
.montant_euros()
948 return self
.get_salaire_brut() - total_charges
950 def get_couts_auf(self
):
952 salaire net + charges patronales
955 for r
in self
.get_charges_patronales():
956 total_charges
+= r
.montant_euros()
957 return self
.get_salaire_net() + total_charges
959 def get_total_remunerations_tierces(self
):
961 for r
in self
.get_remunerations_tierces():
962 total
+= r
.montant_euros()
965 def premier_contrat(self
):
966 """contrat avec plus petite date de début"""
968 contrat
= self
.rh_contrats
.exclude(date_debut
=None).order_by('date_debut')[0]
969 except IndexError, Contrat
.DoesNotExist
:
973 def dernier_contrat(self
):
974 """contrat avec plus grande date de fin"""
976 contrat
= self
.rh_contrats
.exclude(date_debut
=None).order_by('-date_debut')[0]
977 except IndexError, Contrat
.DoesNotExist
:
983 return (self
.date_debut
is None or self
.date_debut
<= today
) \
984 and (self
.date_fin
is None or self
.date_fin
>= today
) \
985 and not (self
.date_fin
is None and self
.date_debut
is None)
988 class Dossier(Dossier_
):
989 __doc__
= Dossier_
.__doc__
990 poste
= models
.ForeignKey(
991 Poste
, db_column
='poste', related_name
='rh_dossiers',
992 help_text
=u
"Taper le nom du poste ou du type de poste",
994 employe
= models
.ForeignKey(
995 'Employe', db_column
='employe',
996 help_text
=u
"Taper le nom de l'employé",
997 related_name
='rh_dossiers', verbose_name
=u
"employé"
999 principal
= models
.BooleanField(
1000 u
"dossier principal", default
=True,
1002 u
"Ce dossier est pour le principal poste occupé par l'employé"
1007 class DossierPiece_(models
.Model
):
1009 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1010 Ex.: Lettre de motivation.
1012 nom
= models
.CharField(max_length
=255)
1013 fichier
= models
.FileField(
1014 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
1021 def __unicode__(self
):
1022 return u
'%s' % (self
.nom
)
1025 class DossierPiece(DossierPiece_
):
1026 dossier
= models
.ForeignKey(
1027 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
1032 class DossierCommentaire(Commentaire
):
1033 dossier
= models
.ForeignKey(
1034 Dossier
, db_column
='dossier', related_name
='commentaires'
1038 class DossierComparaison_(models
.Model
, DevisableMixin
):
1040 Photo d'une comparaison salariale au moment de l'embauche.
1042 objects
= DossierComparaisonManager()
1044 implantation
= models
.ForeignKey(
1045 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1047 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1048 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1049 montant
= models
.IntegerField(null
=True)
1050 devise
= models
.ForeignKey(
1051 'Devise', related_name
='+', null
=True, blank
=True
1057 def __unicode__(self
):
1058 return "%s (%s)" % (self
.poste
, self
.personne
)
1061 class DossierComparaison(DossierComparaison_
):
1062 dossier
= models
.ForeignKey(
1063 Dossier
, related_name
='rh_comparaisons'
1069 class RemunerationMixin(AUFMetadata
):
1072 type = models
.ForeignKey(
1073 'TypeRemuneration', db_column
='type', related_name
='+',
1074 verbose_name
=u
"type de rémunération"
1076 type_revalorisation
= models
.ForeignKey(
1077 'TypeRevalorisation', db_column
='type_revalorisation',
1078 related_name
='+', verbose_name
=u
"type de revalorisation",
1079 null
=True, blank
=True
1081 montant
= models
.DecimalField(
1082 null
=True, blank
=True,
1083 default
=0, max_digits
=12, decimal_places
=2
1084 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1085 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1087 # commentaire = precision
1088 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1090 # date_debut = anciennement date_effectif
1091 date_debut
= models
.DateField(u
"date de début", null
=True, blank
=True)
1092 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1096 ordering
= ['type__nom', '-date_fin']
1098 def __unicode__(self
):
1099 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1102 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1104 Structure de rémunération (données budgétaires) en situation normale
1105 pour un Dossier. Si un Evenement existe, utiliser la structure de
1106 rémunération EvenementRemuneration de cet événement.
1109 def montant_mois(self
):
1110 return round(self
.montant
/ 12, 2)
1112 def montant_avec_regime(self
):
1113 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1115 def montant_euro_mois(self
):
1116 return round(self
.montant_euros() / 12, 2)
1118 def __unicode__(self
):
1120 devise
= self
.devise
.code
1123 return "%s %s" % (self
.montant
, devise
)
1127 verbose_name
= u
"Rémunération"
1128 verbose_name_plural
= u
"Rémunérations"
1131 class Remuneration(Remuneration_
):
1132 dossier
= models
.ForeignKey(
1133 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1139 class ContratManager(NoDeleteManager
):
1140 def get_query_set(self
):
1141 return super(ContratManager
, self
).get_query_set() \
1142 .select_related('dossier', 'dossier__poste')
1145 class Contrat_(AUFMetadata
):
1147 Document juridique qui encadre la relation de travail d'un Employe
1148 pour un Poste particulier. Pour un Dossier (qui documente cette
1149 relation de travail) plusieurs contrats peuvent être associés.
1151 objects
= ContratManager()
1152 type_contrat
= models
.ForeignKey(
1153 'TypeContrat', db_column
='type_contrat',
1154 verbose_name
=u
'type de contrat', related_name
='+'
1156 date_debut
= models
.DateField(u
"date de début")
1157 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1158 fichier
= models
.FileField(
1159 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1165 ordering
= ['dossier__employe__nom']
1166 verbose_name
= u
"Contrat"
1167 verbose_name_plural
= u
"Contrats"
1169 def __unicode__(self
):
1170 return u
'%s - %s' % (self
.dossier
, self
.id)
1173 class Contrat(Contrat_
):
1174 dossier
= models
.ForeignKey(
1175 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1181 class CategorieEmploi(AUFMetadata
):
1183 Catégorie utilisée dans la gestion des Postes.
1184 Catégorie supérieure à TypePoste.
1186 nom
= models
.CharField(max_length
=255)
1190 verbose_name
= u
"catégorie d'emploi"
1191 verbose_name_plural
= u
"catégories d'emploi"
1193 def __unicode__(self
):
1197 class FamilleProfessionnelle(models
.Model
):
1199 Famille professionnelle d'un poste.
1201 nom
= models
.CharField(max_length
=100)
1205 verbose_name
= u
'famille professionnelle'
1206 verbose_name_plural
= u
'familles professionnelles'
1208 def __unicode__(self
):
1212 class TypePoste(AUFMetadata
):
1216 nom
= models
.CharField(max_length
=255)
1217 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1218 is_responsable
= models
.BooleanField(
1219 u
"poste de responsabilité", default
=False
1221 categorie_emploi
= models
.ForeignKey(
1222 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1223 verbose_name
=u
"catégorie d'emploi"
1225 famille_professionnelle
= models
.ForeignKey(
1226 FamilleProfessionnelle
, related_name
='types_de_poste',
1227 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1232 verbose_name
= u
"Type de poste"
1233 verbose_name_plural
= u
"Types de poste"
1235 def __unicode__(self
):
1236 return u
'%s' % (self
.nom
)
1238 TYPE_PAIEMENT_CHOICES
= (
1239 (u
'Régulier', u
'Régulier'),
1240 (u
'Ponctuel', u
'Ponctuel'),
1243 NATURE_REMUNERATION_CHOICES
= (
1244 (u
'Accessoire', u
'Accessoire'),
1245 (u
'Charges', u
'Charges'),
1246 (u
'Indemnité', u
'Indemnité'),
1247 (u
'RAS', u
'Rémunération autre source'),
1248 (u
'Traitement', u
'Traitement'),
1252 class TypeRemuneration(AUFMetadata
):
1254 Catégorie de Remuneration.
1256 objects
= TypeRemunerationManager()
1258 nom
= models
.CharField(max_length
=255)
1259 type_paiement
= models
.CharField(
1260 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1262 nature_remuneration
= models
.CharField(
1263 u
"nature de la rémunération", max_length
=30,
1264 choices
=NATURE_REMUNERATION_CHOICES
1266 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1270 verbose_name
= u
"Type de rémunération"
1271 verbose_name_plural
= u
"Types de rémunération"
1273 def __unicode__(self
):
1275 archive
= u
"(archivé)"
1278 return u
'%s %s' % (self
.nom
, archive
)
1281 class TypeRevalorisation(AUFMetadata
):
1283 Justification du changement de la Remuneration.
1284 (Actuellement utilisé dans aucun traitement informatique.)
1286 nom
= models
.CharField(max_length
=255)
1290 verbose_name
= u
"Type de revalorisation"
1291 verbose_name_plural
= u
"Types de revalorisation"
1293 def __unicode__(self
):
1294 return u
'%s' % (self
.nom
)
1297 class Service(AUFMetadata
):
1299 Unité administrative où les Postes sont rattachés.
1301 objects
= ServiceManager()
1303 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1304 nom
= models
.CharField(max_length
=255)
1308 verbose_name
= u
"Service"
1309 verbose_name_plural
= u
"Services"
1311 def __unicode__(self
):
1313 archive
= u
"(archivé)"
1316 return u
'%s %s' % (self
.nom
, archive
)
1319 TYPE_ORGANISME_CHOICES
= (
1320 ('MAD', 'Mise à disposition'),
1321 ('DET', 'Détachement'),
1325 class OrganismeBstg(AUFMetadata
):
1327 Organisation d'où provient un Employe mis à disposition (MAD) de
1328 ou détaché (DET) à l'AUF à titre gratuit.
1330 (BSTG = bien et service à titre gratuit.)
1332 nom
= models
.CharField(max_length
=255)
1333 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1334 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1336 related_name
='organismes_bstg',
1337 null
=True, blank
=True)
1340 ordering
= ['type', 'nom']
1341 verbose_name
= u
"Organisme BSTG"
1342 verbose_name_plural
= u
"Organismes BSTG"
1344 def __unicode__(self
):
1345 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1347 prefix_implantation
= "pays__region"
1349 def get_regions(self
):
1350 return [self
.pays
.region
]
1353 class Statut(AUFMetadata
):
1355 Statut de l'Employe dans le cadre d'un Dossier particulier.
1358 code
= models
.CharField(
1359 max_length
=25, unique
=True,
1361 u
"Saisir un code court mais lisible pour ce statut : "
1362 u
"le code est utilisé pour associer les statuts aux autres "
1363 u
"données tout en demeurant plus lisible qu'un identifiant "
1367 nom
= models
.CharField(max_length
=255)
1371 verbose_name
= u
"Statut d'employé"
1372 verbose_name_plural
= u
"Statuts d'employé"
1374 def __unicode__(self
):
1375 return u
'%s : %s' % (self
.code
, self
.nom
)
1378 TYPE_CLASSEMENT_CHOICES
= (
1379 ('S', 'S -Soutien'),
1380 ('T', 'T - Technicien'),
1381 ('P', 'P - Professionel'),
1383 ('D', 'D - Direction'),
1384 ('SO', 'SO - Sans objet [expatriés]'),
1385 ('HG', 'HG - Hors grille [direction]'),
1389 class ClassementManager(models
.Manager
):
1391 Ordonner les spcéfiquement les classements.
1393 def get_query_set(self
):
1394 qs
= super(self
.__class__
, self
).get_query_set()
1395 qs
= qs
.extra(select
={
1396 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1398 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1402 class Classement_(AUFMetadata
):
1404 Éléments de classement de la
1405 "Grille générique de classement hiérarchique".
1407 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1408 classement dans la grille. Le classement donne le coefficient utilisé dans:
1410 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1412 objects
= ClassementManager()
1415 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1416 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1417 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1418 coefficient
= models
.FloatField(u
"coefficient", default
=0, null
=True)
1421 # annee # au lieu de date_debut et date_fin
1422 commentaire
= models
.TextField(null
=True, blank
=True)
1426 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1427 verbose_name
= u
"Classement"
1428 verbose_name_plural
= u
"Classements"
1430 def __unicode__(self
):
1431 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1434 class Classement(Classement_
):
1435 __doc__
= Classement_
.__doc__
1438 class TauxChange_(AUFMetadata
):
1440 Taux de change de la devise vers l'euro (EUR)
1441 pour chaque année budgétaire.
1444 devise
= models
.ForeignKey('Devise', db_column
='devise')
1445 annee
= models
.IntegerField(u
"année")
1446 taux
= models
.FloatField(u
"taux vers l'euro")
1450 ordering
= ['-annee', 'devise__code']
1451 verbose_name
= u
"Taux de change"
1452 verbose_name_plural
= u
"Taux de change"
1454 def __unicode__(self
):
1455 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1458 class TauxChange(TauxChange_
):
1459 __doc__
= TauxChange_
.__doc__
1462 class ValeurPointManager(NoDeleteManager
):
1464 def get_query_set(self
):
1465 return super(ValeurPointManager
, self
).get_query_set() \
1466 .select_related('devise', 'implantation')
1469 class ValeurPoint_(AUFMetadata
):
1471 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1472 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1473 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1475 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1478 actuelles
= ValeurPointManager()
1480 valeur
= models
.FloatField(null
=True)
1481 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1482 implantation
= models
.ForeignKey(ref
.Implantation
,
1483 db_column
='implantation',
1484 related_name
='%(app_label)s_valeur_point')
1486 annee
= models
.IntegerField()
1489 ordering
= ['-annee', 'implantation__nom']
1491 verbose_name
= u
"Valeur du point"
1492 verbose_name_plural
= u
"Valeurs du point"
1494 def __unicode__(self
):
1495 return u
'%s %s %s [%s] %s' % (
1496 self
.devise
.code
, self
.annee
, self
.valeur
,
1497 self
.implantation
.nom_court
, self
.devise
.nom
1501 class ValeurPoint(ValeurPoint_
):
1502 __doc__
= ValeurPoint_
.__doc__
1505 class Devise(AUFMetadata
):
1509 objects
= DeviseManager()
1511 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1512 code
= models
.CharField(max_length
=10, unique
=True)
1513 nom
= models
.CharField(max_length
=255)
1517 verbose_name
= u
"Devise"
1518 verbose_name_plural
= u
"Devises"
1520 def __unicode__(self
):
1521 return u
'%s - %s' % (self
.code
, self
.nom
)
1524 class TypeContrat(AUFMetadata
):
1528 nom
= models
.CharField(max_length
=255)
1529 nom_long
= models
.CharField(max_length
=255)
1533 verbose_name
= u
"Type de contrat"
1534 verbose_name_plural
= u
"Types de contrat"
1536 def __unicode__(self
):
1537 return u
'%s' % (self
.nom
)
1542 class ResponsableImplantationProxy(ref
.Implantation
):
1549 verbose_name
= u
"Responsable d'implantation"
1550 verbose_name_plural
= u
"Responsables d'implantation"
1553 class ResponsableImplantation(models
.Model
):
1555 Le responsable d'une implantation.
1556 Anciennement géré sur le Dossier du responsable.
1558 employe
= models
.ForeignKey(
1559 'Employe', db_column
='employe', related_name
='+', null
=True,
1562 implantation
= models
.OneToOneField(
1563 "ResponsableImplantationProxy", db_column
='implantation',
1564 related_name
='responsable', unique
=True
1567 def __unicode__(self
):
1568 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1571 ordering
= ['implantation__nom']
1572 verbose_name
= "Responsable d'implantation"
1573 verbose_name_plural
= "Responsables d'implantation"