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
, DossierComparaisonManager
, \
23 PosteComparaisonManager
, DeviseManager
, ServiceManager
, \
24 TypeRemunerationManager
25 from project
.rh
.validators
import validate_date_passee
29 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
30 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
31 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
32 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
= \
33 "Saisir le nombre d'heure de travail à temps complet (100%), " \
34 "sans tenir compte du régime de travail"
37 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
38 base_url
=settings
.PRIVE_MEDIA_URL
)
41 def poste_piece_dispatch(instance
, filename
):
42 path
= "%s/poste/%s/%s" % (
43 instance
._meta
.app_label
, instance
.poste_id
, filename
48 def dossier_piece_dispatch(instance
, filename
):
49 path
= "%s/dossier/%s/%s" % (
50 instance
._meta
.app_label
, instance
.dossier_id
, filename
55 def employe_piece_dispatch(instance
, filename
):
56 path
= "%s/employe/%s/%s" % (
57 instance
._meta
.app_label
, instance
.employe_id
, filename
62 def contrat_dispatch(instance
, filename
):
63 path
= "%s/contrat/%s/%s" % (
64 instance
._meta
.app_label
, instance
.dossier_id
, filename
69 class DevisableMixin(object):
71 def get_annee_pour_taux_devise(self
):
72 return datetime
.datetime
.now().year
74 def taux_devise(self
, devise
=None):
80 if devise
.code
== "EUR":
83 annee
= self
.get_annee_pour_taux_devise()
86 for tc
in TauxChange
.objects
.filter(devise
=devise
, annee
=annee
)
92 u
"Pas de taux pour %s en %s" % (devise
.code
, annee
)
96 raise Exception(u
"Il existe plusieurs taux de %s en %s" %
101 def montant_euros(self
):
103 taux
= self
.taux_devise()
108 return int(round(float(self
.montant
) * float(taux
), 2))
111 class Commentaire(AUFMetadata
):
112 texte
= models
.TextField()
113 owner
= models
.ForeignKey(
114 'auth.User', db_column
='owner', related_name
='+',
115 verbose_name
=u
"Commentaire de"
120 ordering
= ['-date_creation']
122 def __unicode__(self
):
123 return u
'%s' % (self
.texte
)
128 POSTE_APPEL_CHOICES
= (
129 ('interne', 'Interne'),
130 ('externe', 'Externe'),
134 class Poste_(AUFMetadata
):
136 Un Poste est un emploi (job) à combler dans une implantation.
137 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
138 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
141 objects
= PosteManager()
144 nom
= models
.CharField(u
"Titre du poste", max_length
=255)
145 nom_feminin
= models
.CharField(
146 u
"Titre du poste (au féminin)", max_length
=255, null
=True
148 implantation
= models
.ForeignKey(
150 help_text
=u
"Taper le nom de l'implantation ou sa région",
151 db_column
='implantation', related_name
='+'
153 type_poste
= models
.ForeignKey(
154 'TypePoste', db_column
='type_poste',
155 help_text
=u
"Taper le nom du type de poste", related_name
='+',
156 null
=True, verbose_name
=u
"type de poste"
158 service
= models
.ForeignKey(
159 'Service', db_column
='service', related_name
='%(app_label)s_postes',
160 verbose_name
=u
"direction/service/pôle support", null
=True
162 responsable
= models
.ForeignKey(
163 'Poste', db_column
='responsable',
164 related_name
='+', null
=True,
165 help_text
=u
"Taper le nom du poste ou du type de poste",
166 verbose_name
=u
"Poste du responsable"
170 regime_travail
= models
.DecimalField(
171 u
"temps de travail", max_digits
=12, decimal_places
=2,
172 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
173 help_text
="% du temps complet"
175 regime_travail_nb_heure_semaine
= models
.DecimalField(
176 u
"nb. heures par semaine", max_digits
=12, decimal_places
=2,
177 null
=True, default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
178 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
182 local
= models
.NullBooleanField(
183 u
"local", default
=True, null
=True, blank
=True
185 expatrie
= models
.NullBooleanField(
186 u
"expatrié", default
=False, null
=True, blank
=True
188 mise_a_disposition
= models
.NullBooleanField(
189 u
"mise à disposition", null
=True, default
=False
191 appel
= models
.CharField(
192 u
"Appel à candidature", max_length
=10, null
=True,
193 choices
=POSTE_APPEL_CHOICES
, default
='interne'
197 classement_min
= models
.ForeignKey(
198 'Classement', db_column
='classement_min', related_name
='+',
199 null
=True, blank
=True
201 classement_max
= models
.ForeignKey(
202 'Classement', db_column
='classement_max', related_name
='+',
203 null
=True, blank
=True
205 valeur_point_min
= models
.ForeignKey(
207 help_text
=u
"Taper le code ou le nom de l'implantation",
208 db_column
='valeur_point_min', related_name
='+', null
=True,
211 valeur_point_max
= models
.ForeignKey(
213 help_text
=u
"Taper le code ou le nom de l'implantation",
214 db_column
='valeur_point_max', related_name
='+', null
=True,
217 devise_min
= models
.ForeignKey(
218 'Devise', db_column
='devise_min', null
=True, related_name
='+'
220 devise_max
= models
.ForeignKey(
221 'Devise', db_column
='devise_max', null
=True, related_name
='+'
223 salaire_min
= models
.DecimalField(
224 max_digits
=12, decimal_places
=2, null
=True, default
=0
226 salaire_max
= models
.DecimalField(
227 max_digits
=12, decimal_places
=2, null
=True, default
=0
229 indemn_min
= models
.DecimalField(
230 max_digits
=12, decimal_places
=2, null
=True, default
=0
232 indemn_max
= models
.DecimalField(
233 max_digits
=12, decimal_places
=2, null
=True, default
=0
235 autre_min
= models
.DecimalField(
236 max_digits
=12, decimal_places
=2, null
=True, default
=0
238 autre_max
= models
.DecimalField(
239 max_digits
=12, decimal_places
=2, null
=True, default
=0
242 # Comparatifs de rémunération
243 devise_comparaison
= models
.ForeignKey(
244 'Devise', null
=True, blank
=True, db_column
='devise_comparaison',
247 comp_locale_min
= models
.DecimalField(
248 max_digits
=12, decimal_places
=2, null
=True, blank
=True
250 comp_locale_max
= models
.DecimalField(
251 max_digits
=12, decimal_places
=2, null
=True, blank
=True
253 comp_universite_min
= models
.DecimalField(
254 max_digits
=12, decimal_places
=2, null
=True, blank
=True
256 comp_universite_max
= models
.DecimalField(
257 max_digits
=12, decimal_places
=2, null
=True, blank
=True
259 comp_fonctionpub_min
= models
.DecimalField(
260 max_digits
=12, decimal_places
=2, null
=True, blank
=True
262 comp_fonctionpub_max
= models
.DecimalField(
263 max_digits
=12, decimal_places
=2, null
=True, blank
=True
265 comp_ong_min
= models
.DecimalField(
266 max_digits
=12, decimal_places
=2, null
=True, blank
=True
268 comp_ong_max
= models
.DecimalField(
269 max_digits
=12, decimal_places
=2, null
=True, blank
=True
271 comp_autre_min
= models
.DecimalField(
272 max_digits
=12, decimal_places
=2, null
=True, blank
=True
274 comp_autre_max
= models
.DecimalField(
275 max_digits
=12, decimal_places
=2, null
=True, blank
=True
279 justification
= models
.TextField(null
=True, blank
=True)
282 date_debut
= models
.DateField(
283 u
"date de début", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True
285 date_fin
= models
.DateField(
286 u
"date de fin", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True
291 ordering
= ['implantation__nom', 'nom']
292 verbose_name
= u
"Poste"
293 verbose_name_plural
= u
"Postes"
296 def __unicode__(self
):
297 representation
= u
'%s - %s [%s]' % (
298 self
.implantation
, self
.nom
, self
.id
300 return representation
302 prefix_implantation
= "implantation__region"
304 def get_regions(self
):
305 return [self
.implantation
.region
]
307 def get_devise(self
):
308 vp
= ValeurPoint
.objects
.filter(
309 implantation
=self
.implantation
, devise__archive
=False
314 return Devise
.objects
.get(code
='EUR')
318 __doc__
= Poste_
.__doc__
320 # meta dématérialisation : pour permettre le filtrage
321 vacant
= models
.NullBooleanField(u
"vacant", null
=True, blank
=True)
325 if self
.occupe_par():
329 def occupe_par(self
):
331 Retourne la liste d'employé occupant ce poste.
332 Généralement, retourne une liste d'un élément.
333 Si poste inoccupé, retourne liste vide.
334 UTILISE pour mettre a jour le flag vacant
337 d
.employe
for d
in self
.rh_dossiers
338 .filter(supprime
=False)
339 .exclude(date_fin__lt
=date
.today())
343 POSTE_FINANCEMENT_CHOICES
= (
344 ('A', 'A - Frais de personnel'),
345 ('B', 'B - Projet(s)-Titre(s)'),
350 class PosteFinancement_(models
.Model
):
352 Pour un Poste, structure d'informations décrivant comment on prévoit
355 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
356 pourcentage
= models
.DecimalField(
357 max_digits
=12, decimal_places
=2,
358 help_text
="ex.: 33.33 % (décimale avec point)"
360 commentaire
= models
.TextField(
361 help_text
="Spécifiez la source de financement."
368 def __unicode__(self
):
369 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
372 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
375 class PosteFinancement(PosteFinancement_
):
376 poste
= models
.ForeignKey(
377 Poste
, db_column
='poste', related_name
='rh_financements'
381 class PostePiece_(models
.Model
):
383 Documents relatifs au Poste.
384 Ex.: Description de poste
386 nom
= models
.CharField(u
"Nom", max_length
=255)
387 fichier
= models
.FileField(
388 u
"Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
395 def __unicode__(self
):
396 return u
'%s' % (self
.nom
)
399 class PostePiece(PostePiece_
):
400 poste
= models
.ForeignKey(
401 Poste
, db_column
='poste', related_name
='rh_pieces'
405 class PosteComparaison_(AUFMetadata
, DevisableMixin
):
407 De la même manière qu'un dossier, un poste peut-être comparé à un autre
410 objects
= PosteComparaisonManager()
412 implantation
= models
.ForeignKey(
413 ref
.Implantation
, null
=True, blank
=True, related_name
="+"
415 nom
= models
.CharField(u
"Poste", max_length
=255, null
=True, blank
=True)
416 montant
= models
.IntegerField(null
=True)
417 devise
= models
.ForeignKey(
418 "Devise", related_name
='+', null
=True, blank
=True
424 def __unicode__(self
):
428 class PosteComparaison(PosteComparaison_
):
429 poste
= models
.ForeignKey(
430 Poste
, related_name
='rh_comparaisons_internes'
433 objects
= NoDeleteManager()
436 class PosteCommentaire(Commentaire
):
437 poste
= models
.ForeignKey(
438 Poste
, db_column
='poste', related_name
='commentaires'
445 class Employe(AUFMetadata
):
447 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
448 Dossiers qu'il occupe ou a occupé de Postes.
450 Cette classe aurait pu avantageusement s'appeler Personne car la notion
451 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
454 nom
= models
.CharField(max_length
=255)
455 prenom
= models
.CharField(u
"prénom", max_length
=255)
456 nom_affichage
= models
.CharField(
457 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
459 nationalite
= models
.ForeignKey(
460 ref
.Pays
, to_field
='code', db_column
='nationalite',
461 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
462 blank
=True, null
=True
464 date_naissance
= models
.DateField(
465 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
466 validators
=[validate_date_passee
], null
=True, blank
=True
468 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
471 situation_famille
= models
.CharField(
472 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
473 null
=True, blank
=True
475 date_entree
= models
.DateField(
476 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
481 tel_domicile
= models
.CharField(
482 u
"tél. domicile", max_length
=255, null
=True, blank
=True
484 tel_cellulaire
= models
.CharField(
485 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
487 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
488 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
489 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
490 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
491 pays
= models
.ForeignKey(
492 ref
.Pays
, to_field
='code', db_column
='pays',
493 related_name
='employes', null
=True, blank
=True
495 courriel_perso
= models
.EmailField(
496 u
'adresse courriel personnelle', blank
=True
499 # meta dématérialisation : pour permettre le filtrage
500 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
503 ordering
= ['nom', 'prenom']
504 verbose_name
= u
"Employé"
505 verbose_name_plural
= u
"Employés"
507 def __unicode__(self
):
508 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
512 if self
.genre
.upper() == u
'M':
514 elif self
.genre
.upper() == u
'F':
520 Retourne l'URL du service retournant la photo de l'Employe.
521 Équivalent reverse url 'rh_photo' avec id en param.
523 from django
.core
.urlresolvers
import reverse
524 return reverse('rh_photo', kwargs
={'id': self
.id})
526 def dossiers_passes(self
):
527 params
= {KEY_STATUT
: STATUT_INACTIF
, }
528 search
= RechercheTemporelle(params
, self
.__class__
)
529 search
.purge_params(params
)
530 q
= search
.get_q_temporel(self
.rh_dossiers
)
531 return self
.rh_dossiers
.filter(q
)
533 def dossiers_futurs(self
):
534 params
= {KEY_STATUT
: STATUT_FUTUR
, }
535 search
= RechercheTemporelle(params
, self
.__class__
)
536 search
.purge_params(params
)
537 q
= search
.get_q_temporel(self
.rh_dossiers
)
538 return self
.rh_dossiers
.filter(q
)
540 def dossiers_encours(self
):
541 params
= {KEY_STATUT
: STATUT_ACTIF
, }
542 search
= RechercheTemporelle(params
, self
.__class__
)
543 search
.purge_params(params
)
544 q
= search
.get_q_temporel(self
.rh_dossiers
)
545 return self
.rh_dossiers
.filter(q
)
547 def postes_encours(self
):
548 postes_encours
= set()
549 for d
in self
.dossiers_encours():
550 postes_encours
.add(d
.poste
)
551 return postes_encours
553 def poste_principal(self
):
555 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
557 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
559 poste
= Poste
.objects
.none()
561 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
566 prefix_implantation
= "rh_dossiers__poste__implantation__region"
568 def get_regions(self
):
570 for d
in self
.dossiers
.all():
571 regions
.append(d
.poste
.implantation
.region
)
575 class EmployePiece(models
.Model
):
577 Documents relatifs à un employé.
580 employe
= models
.ForeignKey(
581 'Employe', db_column
='employe', related_name
="pieces",
582 verbose_name
=u
"employé"
584 nom
= models
.CharField(max_length
=255)
585 fichier
= models
.FileField(
586 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
591 verbose_name
= u
"Employé pièce"
592 verbose_name_plural
= u
"Employé pièces"
594 def __unicode__(self
):
595 return u
'%s' % (self
.nom
)
598 class EmployeCommentaire(Commentaire
):
599 employe
= models
.ForeignKey(
600 'Employe', db_column
='employe', related_name
='+'
604 verbose_name
= u
"Employé commentaire"
605 verbose_name_plural
= u
"Employé commentaires"
608 LIEN_PARENTE_CHOICES
= (
609 ('Conjoint', 'Conjoint'),
610 ('Conjointe', 'Conjointe'),
616 class AyantDroit(AUFMetadata
):
618 Personne en relation avec un Employe.
621 nom
= models
.CharField(max_length
=255)
622 prenom
= models
.CharField(u
"prénom", max_length
=255)
623 nom_affichage
= models
.CharField(
624 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
626 nationalite
= models
.ForeignKey(
627 ref
.Pays
, to_field
='code', db_column
='nationalite',
628 related_name
='ayantdroits_nationalite',
629 verbose_name
=u
"nationalité", null
=True, blank
=True
631 date_naissance
= models
.DateField(
632 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
633 validators
=[validate_date_passee
], null
=True, blank
=True
635 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
638 employe
= models
.ForeignKey(
639 'Employe', db_column
='employe', related_name
='ayantdroits',
640 verbose_name
=u
"Employé"
642 lien_parente
= models
.CharField(
643 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
644 null
=True, blank
=True
649 verbose_name
= u
"Ayant droit"
650 verbose_name_plural
= u
"Ayants droit"
652 def __unicode__(self
):
653 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
655 prefix_implantation
= "employe__dossiers__poste__implantation__region"
657 def get_regions(self
):
659 for d
in self
.employe
.dossiers
.all():
660 regions
.append(d
.poste
.implantation
.region
)
664 class AyantDroitCommentaire(Commentaire
):
665 ayant_droit
= models
.ForeignKey(
666 'AyantDroit', db_column
='ayant_droit', related_name
='+'
672 STATUT_RESIDENCE_CHOICES
= (
674 ('expat', 'Expatrié'),
677 COMPTE_COMPTA_CHOICES
= (
684 class Dossier_(AUFMetadata
, DevisableMixin
):
686 Le Dossier regroupe les informations relatives à l'occupation
687 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
690 Plusieurs Contrats peuvent être associés au Dossier.
691 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
692 lequel aucun Dossier n'existe est un poste vacant.
695 objects
= DossierManager()
698 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
699 organisme_bstg
= models
.ForeignKey(
700 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
701 verbose_name
=u
"organisme",
703 u
"Si détaché (DET) ou mis à disposition (MAD), "
704 u
"préciser l'organisme."
705 ), null
=True, blank
=True
709 remplacement
= models
.BooleanField(default
=False)
710 remplacement_de
= models
.ForeignKey(
711 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
712 null
=True, blank
=True
714 statut_residence
= models
.CharField(
715 u
"statut", max_length
=10, default
='local', null
=True,
716 choices
=STATUT_RESIDENCE_CHOICES
720 classement
= models
.ForeignKey(
721 'Classement', db_column
='classement', related_name
='+', null
=True,
724 regime_travail
= models
.DecimalField(
725 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
726 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
728 regime_travail_nb_heure_semaine
= models
.DecimalField(
729 u
"nb. heures par semaine", max_digits
=12,
730 decimal_places
=2, null
=True,
731 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
732 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
735 # Occupation du Poste par cet Employe (anciennement "mandat")
736 date_debut
= models
.DateField(u
"date de début d'occupation de poste")
737 date_fin
= models
.DateField(
738 u
"Date de fin d'occupation de poste", null
=True, blank
=True
746 ordering
= ['employe__nom', ]
747 verbose_name
= u
"Dossier"
748 verbose_name_plural
= "Dossiers"
750 def salaire_theorique(self
):
751 annee
= date
.today().year
752 coeff
= self
.classement
.coefficient
753 implantation
= self
.poste
.implantation
754 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
756 montant
= coeff
* point
.valeur
757 devise
= point
.devise
758 return {'montant': montant
, 'devise': devise
}
760 def __unicode__(self
):
761 poste
= self
.poste
.nom
762 if self
.employe
.genre
== 'F':
763 poste
= self
.poste
.nom_feminin
764 return u
'%s - %s' % (self
.employe
, poste
)
766 prefix_implantation
= "poste__implantation__region"
768 def get_regions(self
):
769 return [self
.poste
.implantation
.region
]
771 def remunerations(self
):
772 key
= "%s_remunerations" % self
._meta
.app_label
773 remunerations
= getattr(self
, key
)
774 return remunerations
.all().order_by('-date_debut')
776 def remunerations_en_cours(self
):
777 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
778 return self
.remunerations().all().filter(q
).order_by('date_debut')
780 def get_salaire(self
):
782 return [r
for r
in self
.remunerations().order_by('-date_debut')
783 if r
.type_id
== 1][0]
787 def get_salaire_euros(self
):
788 tx
= self
.taux_devise()
789 return (float)(tx
) * (float)(self
.salaire
)
791 def get_remunerations_brutes(self
):
795 4 Indemnité d'expatriation
796 5 Indemnité pour frais
797 6 Indemnité de logement
798 7 Indemnité de fonction
799 8 Indemnité de responsabilité
800 9 Indemnité de transport
801 10 Indemnité compensatrice
802 11 Indemnité de subsistance
803 12 Indemnité différentielle
804 13 Prime d'installation
807 16 Indemnité de départ
808 18 Prime de 13ième mois
811 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
812 return [r
for r
in self
.remunerations_en_cours().all()
815 def get_charges_salariales(self
):
817 20 Charges salariales ?
820 return [r
for r
in self
.remunerations_en_cours().all()
823 def get_charges_patronales(self
):
825 17 Charges patronales
828 return [r
for r
in self
.remunerations_en_cours().all()
831 def get_remunerations_tierces(self
):
835 return [r
for r
in self
.remunerations_en_cours().all()
836 if r
.type_id
in (2,)]
840 def get_total_local_charges_salariales(self
):
841 devise
= self
.poste
.get_devise()
843 for r
in self
.get_charges_salariales():
844 if r
.devise
!= devise
:
846 total
+= float(r
.montant
)
849 def get_total_local_charges_patronales(self
):
850 devise
= self
.poste
.get_devise()
852 for r
in self
.get_charges_patronales():
853 if r
.devise
!= devise
:
855 total
+= float(r
.montant
)
858 def get_local_salaire_brut(self
):
860 somme des rémuérations brutes
862 devise
= self
.poste
.get_devise()
864 for r
in self
.get_remunerations_brutes():
865 if r
.devise
!= devise
:
867 total
+= float(r
.montant
)
870 def get_local_salaire_net(self
):
872 salaire brut - charges salariales
874 devise
= self
.poste
.get_devise()
876 for r
in self
.get_charges_salariales():
877 if r
.devise
!= devise
:
879 total_charges
+= float(r
.montant
)
880 return self
.get_local_salaire_brut() - total_charges
882 def get_local_couts_auf(self
):
884 salaire net + charges patronales
886 devise
= self
.poste
.get_devise()
888 for r
in self
.get_charges_patronales():
889 if r
.devise
!= devise
:
891 total_charges
+= float(r
.montant
)
892 return self
.get_local_salaire_net() + total_charges
894 def get_total_local_remunerations_tierces(self
):
895 devise
= self
.poste
.get_devise()
897 for r
in self
.get_remunerations_tierces():
898 if r
.devise
!= devise
:
900 total
+= float(r
.montant
)
905 def get_total_charges_salariales(self
):
907 for r
in self
.get_charges_salariales():
908 total
+= r
.montant_euros()
911 def get_total_charges_patronales(self
):
913 for r
in self
.get_charges_patronales():
914 total
+= r
.montant_euros()
917 def get_salaire_brut(self
):
919 somme des rémuérations brutes
922 for r
in self
.get_remunerations_brutes():
923 total
+= r
.montant_euros()
926 def get_salaire_net(self
):
928 salaire brut - charges salariales
931 for r
in self
.get_charges_salariales():
932 total_charges
+= r
.montant_euros()
933 return self
.get_salaire_brut() - total_charges
935 def get_couts_auf(self
):
937 salaire net + charges patronales
940 for r
in self
.get_charges_patronales():
941 total_charges
+= r
.montant_euros()
942 return self
.get_salaire_net() + total_charges
944 def get_total_remunerations_tierces(self
):
946 for r
in self
.get_remunerations_tierces():
947 total
+= r
.montant_euros()
952 return (self
.date_debut
is None or self
.date_debut
<= today
) \
953 and (self
.date_fin
is None or self
.date_fin
>= today
) \
954 and not (self
.date_fin
is None and self
.date_debut
is None)
957 class Dossier(Dossier_
):
958 __doc__
= Dossier_
.__doc__
959 poste
= models
.ForeignKey(
960 Poste
, db_column
='poste', related_name
='rh_dossiers',
961 help_text
=u
"Taper le nom du poste ou du type de poste",
963 employe
= models
.ForeignKey(
964 'Employe', db_column
='employe',
965 help_text
=u
"Taper le nom de l'employé",
966 related_name
='rh_dossiers', verbose_name
=u
"employé"
968 principal
= models
.BooleanField(
969 u
"dossier principal", default
=True,
971 u
"Ce dossier est pour le principal poste occupé par l'employé"
976 class DossierPiece_(models
.Model
):
978 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
979 Ex.: Lettre de motivation.
981 nom
= models
.CharField(max_length
=255)
982 fichier
= models
.FileField(
983 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
990 def __unicode__(self
):
991 return u
'%s' % (self
.nom
)
994 class DossierPiece(DossierPiece_
):
995 dossier
= models
.ForeignKey(
996 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
1001 class DossierCommentaire(Commentaire
):
1002 dossier
= models
.ForeignKey(
1003 Dossier
, db_column
='dossier', related_name
='commentaires'
1007 class DossierComparaison_(models
.Model
, DevisableMixin
):
1009 Photo d'une comparaison salariale au moment de l'embauche.
1011 objects
= DossierComparaisonManager()
1013 implantation
= models
.ForeignKey(
1014 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1016 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1017 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1018 montant
= models
.IntegerField(null
=True)
1019 devise
= models
.ForeignKey(
1020 'Devise', related_name
='+', null
=True, blank
=True
1026 def __unicode__(self
):
1027 return "%s (%s)" % (self
.poste
, self
.personne
)
1030 class DossierComparaison(DossierComparaison_
):
1031 dossier
= models
.ForeignKey(
1032 Dossier
, related_name
='rh_comparaisons'
1038 class RemunerationMixin(AUFMetadata
):
1041 type = models
.ForeignKey(
1042 'TypeRemuneration', db_column
='type', related_name
='+',
1043 verbose_name
=u
"type de rémunération"
1045 type_revalorisation
= models
.ForeignKey(
1046 'TypeRevalorisation', db_column
='type_revalorisation',
1047 related_name
='+', verbose_name
=u
"type de revalorisation",
1048 null
=True, blank
=True
1050 montant
= models
.DecimalField(
1051 null
=True, blank
=True,
1052 default
=0, max_digits
=12, decimal_places
=2
1053 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1054 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1056 # commentaire = precision
1057 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1059 # date_debut = anciennement date_effectif
1060 date_debut
= models
.DateField(u
"date de début", null
=True, blank
=True)
1061 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1065 ordering
= ['type__nom', '-date_fin']
1067 def __unicode__(self
):
1068 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1071 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1073 Structure de rémunération (données budgétaires) en situation normale
1074 pour un Dossier. Si un Evenement existe, utiliser la structure de
1075 rémunération EvenementRemuneration de cet événement.
1078 def montant_mois(self
):
1079 return round(self
.montant
/ 12, 2)
1081 def montant_avec_regime(self
):
1082 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1084 def montant_euro_mois(self
):
1085 return round(self
.montant_euros() / 12, 2)
1087 def __unicode__(self
):
1089 devise
= self
.devise
.code
1092 return "%s %s" % (self
.montant
, devise
)
1096 verbose_name
= u
"Rémunération"
1097 verbose_name_plural
= u
"Rémunérations"
1100 class Remuneration(Remuneration_
):
1101 dossier
= models
.ForeignKey(
1102 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1108 class ContratManager(NoDeleteManager
):
1109 def get_query_set(self
):
1110 return super(ContratManager
, self
).get_query_set() \
1111 .select_related('dossier', 'dossier__poste')
1114 class Contrat_(AUFMetadata
):
1116 Document juridique qui encadre la relation de travail d'un Employe
1117 pour un Poste particulier. Pour un Dossier (qui documente cette
1118 relation de travail) plusieurs contrats peuvent être associés.
1120 objects
= ContratManager()
1121 type_contrat
= models
.ForeignKey(
1122 'TypeContrat', db_column
='type_contrat',
1123 verbose_name
=u
'type de contrat', related_name
='+'
1125 date_debut
= models
.DateField(u
"date de début")
1126 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1127 fichier
= models
.FileField(
1128 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1134 ordering
= ['dossier__employe__nom']
1135 verbose_name
= u
"Contrat"
1136 verbose_name_plural
= u
"Contrats"
1138 def __unicode__(self
):
1139 return u
'%s - %s' % (self
.dossier
, self
.id)
1142 class Contrat(Contrat_
):
1143 dossier
= models
.ForeignKey(
1144 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1150 class CategorieEmploi(AUFMetadata
):
1152 Catégorie utilisée dans la gestion des Postes.
1153 Catégorie supérieure à TypePoste.
1155 nom
= models
.CharField(max_length
=255)
1159 verbose_name
= u
"catégorie d'emploi"
1160 verbose_name_plural
= u
"catégories d'emploi"
1162 def __unicode__(self
):
1166 class FamilleProfessionnelle(models
.Model
):
1168 Famille professionnelle d'un poste.
1170 nom
= models
.CharField(max_length
=100)
1174 verbose_name
= u
'famille professionnelle'
1175 verbose_name_plural
= u
'familles professionnelles'
1177 def __unicode__(self
):
1181 class TypePoste(AUFMetadata
):
1185 nom
= models
.CharField(max_length
=255)
1186 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1187 is_responsable
= models
.BooleanField(
1188 u
"poste de responsabilité", default
=False
1190 categorie_emploi
= models
.ForeignKey(
1191 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1192 verbose_name
=u
"catégorie d'emploi"
1194 famille_professionnelle
= models
.ForeignKey(
1195 FamilleProfessionnelle
, related_name
='types_de_poste',
1196 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1201 verbose_name
= u
"Type de poste"
1202 verbose_name_plural
= u
"Types de poste"
1204 def __unicode__(self
):
1205 return u
'%s' % (self
.nom
)
1207 TYPE_PAIEMENT_CHOICES
= (
1208 (u
'Régulier', u
'Régulier'),
1209 (u
'Ponctuel', u
'Ponctuel'),
1212 NATURE_REMUNERATION_CHOICES
= (
1213 (u
'Accessoire', u
'Accessoire'),
1214 (u
'Charges', u
'Charges'),
1215 (u
'Indemnité', u
'Indemnité'),
1216 (u
'RAS', u
'Rémunération autre source'),
1217 (u
'Traitement', u
'Traitement'),
1221 class TypeRemuneration(AUFMetadata
):
1223 Catégorie de Remuneration.
1225 objects
= TypeRemunerationManager()
1227 nom
= models
.CharField(max_length
=255)
1228 type_paiement
= models
.CharField(
1229 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1231 nature_remuneration
= models
.CharField(
1232 u
"nature de la rémunération", max_length
=30,
1233 choices
=NATURE_REMUNERATION_CHOICES
1235 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1239 verbose_name
= u
"Type de rémunération"
1240 verbose_name_plural
= u
"Types de rémunération"
1242 def __unicode__(self
):
1244 archive
= u
"(archivé)"
1247 return u
'%s %s' % (self
.nom
, archive
)
1250 class TypeRevalorisation(AUFMetadata
):
1252 Justification du changement de la Remuneration.
1253 (Actuellement utilisé dans aucun traitement informatique.)
1255 nom
= models
.CharField(max_length
=255)
1259 verbose_name
= u
"Type de revalorisation"
1260 verbose_name_plural
= u
"Types de revalorisation"
1262 def __unicode__(self
):
1263 return u
'%s' % (self
.nom
)
1266 class Service(AUFMetadata
):
1268 Unité administrative où les Postes sont rattachés.
1270 objects
= ServiceManager()
1272 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1273 nom
= models
.CharField(max_length
=255)
1277 verbose_name
= u
"Service"
1278 verbose_name_plural
= u
"Services"
1280 def __unicode__(self
):
1282 archive
= u
"(archivé)"
1285 return u
'%s %s' % (self
.nom
, archive
)
1288 TYPE_ORGANISME_CHOICES
= (
1289 ('MAD', 'Mise à disposition'),
1290 ('DET', 'Détachement'),
1294 class OrganismeBstg(AUFMetadata
):
1296 Organisation d'où provient un Employe mis à disposition (MAD) de
1297 ou détaché (DET) à l'AUF à titre gratuit.
1299 (BSTG = bien et service à titre gratuit.)
1301 nom
= models
.CharField(max_length
=255)
1302 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1303 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1305 related_name
='organismes_bstg',
1306 null
=True, blank
=True)
1309 ordering
= ['type', 'nom']
1310 verbose_name
= u
"Organisme BSTG"
1311 verbose_name_plural
= u
"Organismes BSTG"
1313 def __unicode__(self
):
1314 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1316 prefix_implantation
= "pays__region"
1318 def get_regions(self
):
1319 return [self
.pays
.region
]
1322 class Statut(AUFMetadata
):
1324 Statut de l'Employe dans le cadre d'un Dossier particulier.
1327 code
= models
.CharField(
1328 max_length
=25, unique
=True,
1330 u
"Saisir un code court mais lisible pour ce statut : "
1331 u
"le code est utilisé pour associer les statuts aux autres "
1332 u
"données tout en demeurant plus lisible qu'un identifiant "
1336 nom
= models
.CharField(max_length
=255)
1340 verbose_name
= u
"Statut d'employé"
1341 verbose_name_plural
= u
"Statuts d'employé"
1343 def __unicode__(self
):
1344 return u
'%s : %s' % (self
.code
, self
.nom
)
1347 TYPE_CLASSEMENT_CHOICES
= (
1348 ('S', 'S -Soutien'),
1349 ('T', 'T - Technicien'),
1350 ('P', 'P - Professionel'),
1352 ('D', 'D - Direction'),
1353 ('SO', 'SO - Sans objet [expatriés]'),
1354 ('HG', 'HG - Hors grille [direction]'),
1358 class ClassementManager(models
.Manager
):
1360 Ordonner les spcéfiquement les classements.
1362 def get_query_set(self
):
1363 qs
= super(self
.__class__
, self
).get_query_set()
1364 qs
= qs
.extra(select
={
1365 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1367 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1371 class Classement_(AUFMetadata
):
1373 Éléments de classement de la
1374 "Grille générique de classement hiérarchique".
1376 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1377 classement dans la grille. Le classement donne le coefficient utilisé dans:
1379 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1381 objects
= ClassementManager()
1384 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1385 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1386 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1387 coefficient
= models
.FloatField(u
"coefficient", default
=0, null
=True)
1390 # annee # au lieu de date_debut et date_fin
1391 commentaire
= models
.TextField(null
=True, blank
=True)
1395 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1396 verbose_name
= u
"Classement"
1397 verbose_name_plural
= u
"Classements"
1399 def __unicode__(self
):
1400 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1403 class Classement(Classement_
):
1404 __doc__
= Classement_
.__doc__
1407 class TauxChange_(AUFMetadata
):
1409 Taux de change de la devise vers l'euro (EUR)
1410 pour chaque année budgétaire.
1413 devise
= models
.ForeignKey('Devise', db_column
='devise')
1414 annee
= models
.IntegerField(u
"année")
1415 taux
= models
.FloatField(u
"taux vers l'euro")
1419 ordering
= ['-annee', 'devise__code']
1420 verbose_name
= u
"Taux de change"
1421 verbose_name_plural
= u
"Taux de change"
1423 def __unicode__(self
):
1424 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1427 class TauxChange(TauxChange_
):
1428 __doc__
= TauxChange_
.__doc__
1431 class ValeurPointManager(NoDeleteManager
):
1433 def get_query_set(self
):
1434 return super(ValeurPointManager
, self
).get_query_set() \
1435 .select_related('devise', 'implantation')
1438 class ValeurPoint_(AUFMetadata
):
1440 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1441 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1442 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1444 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1447 actuelles
= ValeurPointManager()
1449 valeur
= models
.FloatField(null
=True)
1450 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1451 implantation
= models
.ForeignKey(ref
.Implantation
,
1452 db_column
='implantation',
1453 related_name
='%(app_label)s_valeur_point')
1455 annee
= models
.IntegerField()
1458 ordering
= ['-annee', 'implantation__nom']
1460 verbose_name
= u
"Valeur du point"
1461 verbose_name_plural
= u
"Valeurs du point"
1463 def __unicode__(self
):
1464 return u
'%s %s %s [%s] %s' % (
1465 self
.devise
.code
, self
.annee
, self
.valeur
,
1466 self
.implantation
.nom_court
, self
.devise
.nom
1470 class ValeurPoint(ValeurPoint_
):
1471 __doc__
= ValeurPoint_
.__doc__
1474 class Devise(AUFMetadata
):
1478 objects
= DeviseManager()
1480 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1481 code
= models
.CharField(max_length
=10, unique
=True)
1482 nom
= models
.CharField(max_length
=255)
1486 verbose_name
= u
"Devise"
1487 verbose_name_plural
= u
"Devises"
1489 def __unicode__(self
):
1490 return u
'%s - %s' % (self
.code
, self
.nom
)
1493 class TypeContrat(AUFMetadata
):
1497 nom
= models
.CharField(max_length
=255)
1498 nom_long
= models
.CharField(max_length
=255)
1502 verbose_name
= u
"Type de contrat"
1503 verbose_name_plural
= u
"Types de contrat"
1505 def __unicode__(self
):
1506 return u
'%s' % (self
.nom
)
1511 class ResponsableImplantationProxy(ref
.Implantation
):
1516 verbose_name
= u
"Responsable d'implantation"
1517 verbose_name_plural
= u
"Responsables d'implantation"
1520 class ResponsableImplantation(models
.Model
):
1522 Le responsable d'une implantation.
1523 Anciennement géré sur le Dossier du responsable.
1525 employe
= models
.ForeignKey(
1526 'Employe', db_column
='employe', related_name
='+', null
=True,
1529 implantation
= models
.OneToOneField(
1530 "ResponsableImplantationProxy", db_column
='implantation',
1531 related_name
='responsable', unique
=True
1534 def __unicode__(self
):
1535 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1538 ordering
= ['implantation__nom']
1539 verbose_name
= "Responsable d'implantation"
1540 verbose_name_plural
= "Responsables d'implantation"