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
28 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
29 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
30 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
31 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
= \
32 "Saisir le nombre d'heure de travail à temps complet (100%), " \
33 "sans tenir compte du régime de travail"
36 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
37 base_url
=settings
.PRIVE_MEDIA_URL
)
40 def poste_piece_dispatch(instance
, filename
):
41 path
= "%s/poste/%s/%s" % (
42 instance
._meta
.app_label
, instance
.poste_id
, filename
47 def dossier_piece_dispatch(instance
, filename
):
48 path
= "%s/dossier/%s/%s" % (
49 instance
._meta
.app_label
, instance
.dossier_id
, filename
54 def employe_piece_dispatch(instance
, filename
):
55 path
= "%s/employe/%s/%s" % (
56 instance
._meta
.app_label
, instance
.employe_id
, filename
61 def contrat_dispatch(instance
, filename
):
62 path
= "%s/contrat/%s/%s" % (
63 instance
._meta
.app_label
, instance
.dossier_id
, filename
68 class DevisableMixin(object):
70 def get_annee_pour_taux_devise(self
):
71 return datetime
.datetime
.now().year
73 def taux_devise(self
, devise
=None):
79 if devise
.code
== "EUR":
82 annee
= self
.get_annee_pour_taux_devise()
85 for tc
in TauxChange
.objects
.filter(devise
=devise
, annee
=annee
)
91 u
"Pas de taux pour %s en %s" % (devise
.code
, annee
)
95 raise Exception(u
"Il existe plusieurs taux de %s en %s" %
100 def montant_euros(self
):
102 taux
= self
.taux_devise()
107 return int(round(float(self
.montant
) * float(taux
), 2))
110 class Commentaire(AUFMetadata
):
111 texte
= models
.TextField()
112 owner
= models
.ForeignKey(
113 'auth.User', db_column
='owner', related_name
='+',
114 verbose_name
=u
"Commentaire de"
119 ordering
= ['-date_creation']
121 def __unicode__(self
):
122 return u
'%s' % (self
.texte
)
127 POSTE_APPEL_CHOICES
= (
128 ('interne', 'Interne'),
129 ('externe', 'Externe'),
133 class Poste_(AUFMetadata
):
135 Un Poste est un emploi (job) à combler dans une implantation.
136 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
137 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
140 objects
= PosteManager()
143 nom
= models
.CharField(u
"Titre du poste", max_length
=255)
144 nom_feminin
= models
.CharField(
145 u
"Titre du poste (au féminin)", max_length
=255, null
=True
147 implantation
= models
.ForeignKey(
149 help_text
=u
"Taper le nom de l'implantation ou sa région",
150 db_column
='implantation', related_name
='+'
152 type_poste
= models
.ForeignKey(
153 'TypePoste', db_column
='type_poste',
154 help_text
=u
"Taper le nom du type de poste", related_name
='+',
155 null
=True, verbose_name
=u
"type de poste"
157 service
= models
.ForeignKey(
158 'Service', db_column
='service', related_name
='+',
159 verbose_name
=u
"direction/service/pôle support", null
=True
161 responsable
= models
.ForeignKey(
162 'Poste', db_column
='responsable',
163 related_name
='+', null
=True,
164 help_text
=u
"Taper le nom du poste ou du type de poste",
165 verbose_name
=u
"Poste du responsable"
169 regime_travail
= models
.DecimalField(
170 u
"temps de travail", max_digits
=12, decimal_places
=2,
171 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
172 help_text
="% du temps complet"
174 regime_travail_nb_heure_semaine
= models
.DecimalField(
175 u
"nb. heures par semaine", max_digits
=12, decimal_places
=2,
176 null
=True, default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
177 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
181 local
= models
.NullBooleanField(
182 u
"local", default
=True, null
=True, blank
=True
184 expatrie
= models
.NullBooleanField(
185 u
"expatrié", default
=False, null
=True, blank
=True
187 mise_a_disposition
= models
.NullBooleanField(
188 u
"mise à disposition", null
=True, default
=False
190 appel
= models
.CharField(
191 u
"Appel à candidature", max_length
=10, null
=True,
192 choices
=POSTE_APPEL_CHOICES
, default
='interne'
196 classement_min
= models
.ForeignKey(
197 'Classement', db_column
='classement_min', related_name
='+',
198 null
=True, blank
=True
200 classement_max
= models
.ForeignKey(
201 'Classement', db_column
='classement_max', related_name
='+',
202 null
=True, blank
=True
204 valeur_point_min
= models
.ForeignKey(
206 help_text
=u
"Taper le code ou le nom de l'implantation",
207 db_column
='valeur_point_min', related_name
='+', null
=True,
210 valeur_point_max
= models
.ForeignKey(
212 help_text
=u
"Taper le code ou le nom de l'implantation",
213 db_column
='valeur_point_max', related_name
='+', null
=True,
216 devise_min
= models
.ForeignKey(
217 'Devise', db_column
='devise_min', null
=True, related_name
='+'
219 devise_max
= models
.ForeignKey(
220 'Devise', db_column
='devise_max', null
=True, related_name
='+'
222 salaire_min
= models
.DecimalField(
223 max_digits
=12, decimal_places
=2, null
=True, default
=0
225 salaire_max
= models
.DecimalField(
226 max_digits
=12, decimal_places
=2, null
=True, default
=0
228 indemn_min
= models
.DecimalField(
229 max_digits
=12, decimal_places
=2, null
=True, default
=0
231 indemn_max
= models
.DecimalField(
232 max_digits
=12, decimal_places
=2, null
=True, default
=0
234 autre_min
= models
.DecimalField(
235 max_digits
=12, decimal_places
=2, null
=True, default
=0
237 autre_max
= models
.DecimalField(
238 max_digits
=12, decimal_places
=2, null
=True, default
=0
241 # Comparatifs de rémunération
242 devise_comparaison
= models
.ForeignKey(
243 'Devise', null
=True, blank
=True, db_column
='devise_comparaison',
246 comp_locale_min
= models
.DecimalField(
247 max_digits
=12, decimal_places
=2, null
=True, blank
=True
249 comp_locale_max
= models
.DecimalField(
250 max_digits
=12, decimal_places
=2, null
=True, blank
=True
252 comp_universite_min
= models
.DecimalField(
253 max_digits
=12, decimal_places
=2, null
=True, blank
=True
255 comp_universite_max
= models
.DecimalField(
256 max_digits
=12, decimal_places
=2, null
=True, blank
=True
258 comp_fonctionpub_min
= models
.DecimalField(
259 max_digits
=12, decimal_places
=2, null
=True, blank
=True
261 comp_fonctionpub_max
= models
.DecimalField(
262 max_digits
=12, decimal_places
=2, null
=True, blank
=True
264 comp_ong_min
= models
.DecimalField(
265 max_digits
=12, decimal_places
=2, null
=True, blank
=True
267 comp_ong_max
= models
.DecimalField(
268 max_digits
=12, decimal_places
=2, null
=True, blank
=True
270 comp_autre_min
= models
.DecimalField(
271 max_digits
=12, decimal_places
=2, null
=True, blank
=True
273 comp_autre_max
= models
.DecimalField(
274 max_digits
=12, decimal_places
=2, null
=True, blank
=True
278 justification
= models
.TextField(null
=True, blank
=True)
281 date_debut
= models
.DateField(
282 u
"date de début", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True
284 date_fin
= models
.DateField(
285 u
"date de fin", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True
290 ordering
= ['implantation__nom', 'nom']
291 verbose_name
= u
"Poste"
292 verbose_name_plural
= u
"Postes"
295 def __unicode__(self
):
296 representation
= u
'%s - %s [%s]' % (
297 self
.implantation
, self
.nom
, self
.id
299 return representation
301 prefix_implantation
= "implantation__region"
303 def get_regions(self
):
304 return [self
.implantation
.region
]
306 def get_devise(self
):
307 vp
= ValeurPoint
.objects
.filter(
308 implantation
=self
.implantation
, devise__archive
=False
313 return Devise
.objects
.get(code
='EUR')
317 __doc__
= Poste_
.__doc__
319 # meta dématérialisation : pour permettre le filtrage
320 vacant
= models
.NullBooleanField(u
"vacant", null
=True, blank
=True)
324 if self
.occupe_par():
328 def occupe_par(self
):
330 Retourne la liste d'employé occupant ce poste.
331 Généralement, retourne une liste d'un élément.
332 Si poste inoccupé, retourne liste vide.
333 UTILISE pour mettre a jour le flag vacant
336 d
.employe
for d
in self
.rh_dossiers
337 .filter(supprime
=False)
338 .exclude(date_fin__lt
=date
.today())
342 POSTE_FINANCEMENT_CHOICES
= (
343 ('A', 'A - Frais de personnel'),
344 ('B', 'B - Projet(s)-Titre(s)'),
349 class PosteFinancement_(models
.Model
):
351 Pour un Poste, structure d'informations décrivant comment on prévoit
354 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
355 pourcentage
= models
.DecimalField(
356 max_digits
=12, decimal_places
=2,
357 help_text
="ex.: 33.33 % (décimale avec point)"
359 commentaire
= models
.TextField(
360 help_text
="Spécifiez la source de financement."
367 def __unicode__(self
):
368 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
371 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
374 class PosteFinancement(PosteFinancement_
):
375 poste
= models
.ForeignKey(
376 Poste
, db_column
='poste', related_name
='rh_financements'
380 class PostePiece_(models
.Model
):
382 Documents relatifs au Poste.
383 Ex.: Description de poste
385 nom
= models
.CharField(u
"Nom", max_length
=255)
386 fichier
= models
.FileField(
387 u
"Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
394 def __unicode__(self
):
395 return u
'%s' % (self
.nom
)
398 class PostePiece(PostePiece_
):
399 poste
= models
.ForeignKey(
400 Poste
, db_column
='poste', related_name
='rh_pieces'
404 class PosteComparaison_(AUFMetadata
, DevisableMixin
):
406 De la même manière qu'un dossier, un poste peut-être comparé à un autre
409 objects
= PosteComparaisonManager()
411 implantation
= models
.ForeignKey(
412 ref
.Implantation
, null
=True, blank
=True, related_name
="+"
414 nom
= models
.CharField(u
"Poste", max_length
=255, null
=True, blank
=True)
415 montant
= models
.IntegerField(null
=True)
416 devise
= models
.ForeignKey(
417 "Devise", related_name
='+', null
=True, blank
=True
423 def __unicode__(self
):
427 class PosteComparaison(PosteComparaison_
):
428 poste
= models
.ForeignKey(
429 Poste
, related_name
='rh_comparaisons_internes'
432 objects
= NoDeleteManager()
435 class PosteCommentaire(Commentaire
):
436 poste
= models
.ForeignKey(
437 Poste
, db_column
='poste', related_name
='commentaires'
444 class Employe(AUFMetadata
):
446 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
447 Dossiers qu'il occupe ou a occupé de Postes.
449 Cette classe aurait pu avantageusement s'appeler Personne car la notion
450 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
453 nom
= models
.CharField(max_length
=255)
454 prenom
= models
.CharField(u
"prénom", max_length
=255)
455 nom_affichage
= models
.CharField(
456 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
458 nationalite
= models
.ForeignKey(
459 ref
.Pays
, to_field
='code', db_column
='nationalite',
460 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
461 blank
=True, null
=True
463 date_naissance
= models
.DateField(
464 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
465 validators
=[validate_date_passee
], null
=True, blank
=True
467 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
470 situation_famille
= models
.CharField(
471 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
472 null
=True, blank
=True
474 date_entree
= models
.DateField(
475 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
480 tel_domicile
= models
.CharField(
481 u
"tél. domicile", max_length
=255, null
=True, blank
=True
483 tel_cellulaire
= models
.CharField(
484 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
486 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
487 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
488 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
489 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
490 pays
= models
.ForeignKey(
491 ref
.Pays
, to_field
='code', db_column
='pays',
492 related_name
='employes', null
=True, blank
=True
495 # meta dématérialisation : pour permettre le filtrage
496 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
499 ordering
= ['nom', 'prenom']
500 verbose_name
= u
"Employé"
501 verbose_name_plural
= u
"Employés"
503 def __unicode__(self
):
504 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
508 if self
.genre
.upper() == u
'M':
510 elif self
.genre
.upper() == u
'F':
516 Retourne l'URL du service retournant la photo de l'Employe.
517 Équivalent reverse url 'rh_photo' avec id en param.
519 from django
.core
.urlresolvers
import reverse
520 return reverse('rh_photo', kwargs
={'id': self
.id})
522 def dossiers_passes(self
):
523 params
= {KEY_STATUT
: STATUT_INACTIF
, }
524 search
= RechercheTemporelle(params
, self
.__class__
)
525 search
.purge_params(params
)
526 q
= search
.get_q_temporel(self
.rh_dossiers
)
527 return self
.rh_dossiers
.filter(q
)
529 def dossiers_futurs(self
):
530 params
= {KEY_STATUT
: STATUT_FUTUR
, }
531 search
= RechercheTemporelle(params
, self
.__class__
)
532 search
.purge_params(params
)
533 q
= search
.get_q_temporel(self
.rh_dossiers
)
534 return self
.rh_dossiers
.filter(q
)
536 def dossiers_encours(self
):
537 params
= {KEY_STATUT
: STATUT_ACTIF
, }
538 search
= RechercheTemporelle(params
, self
.__class__
)
539 search
.purge_params(params
)
540 q
= search
.get_q_temporel(self
.rh_dossiers
)
541 return self
.rh_dossiers
.filter(q
)
543 def postes_encours(self
):
544 postes_encours
= set()
545 for d
in self
.dossiers_encours():
546 postes_encours
.add(d
.poste
)
547 return postes_encours
549 def poste_principal(self
):
551 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
553 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
555 poste
= Poste
.objects
.none()
557 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
562 prefix_implantation
= "rh_dossiers__poste__implantation__region"
564 def get_regions(self
):
566 for d
in self
.dossiers
.all():
567 regions
.append(d
.poste
.implantation
.region
)
571 class EmployePiece(models
.Model
):
573 Documents relatifs à un employé.
576 employe
= models
.ForeignKey(
577 'Employe', db_column
='employe', related_name
="pieces",
578 verbose_name
=u
"employé"
580 nom
= models
.CharField(max_length
=255)
581 fichier
= models
.FileField(
582 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
587 verbose_name
= u
"Employé pièce"
588 verbose_name_plural
= u
"Employé pièces"
590 def __unicode__(self
):
591 return u
'%s' % (self
.nom
)
594 class EmployeCommentaire(Commentaire
):
595 employe
= models
.ForeignKey(
596 'Employe', db_column
='employe', related_name
='+'
600 verbose_name
= u
"Employé commentaire"
601 verbose_name_plural
= u
"Employé commentaires"
604 LIEN_PARENTE_CHOICES
= (
605 ('Conjoint', 'Conjoint'),
606 ('Conjointe', 'Conjointe'),
612 class AyantDroit(AUFMetadata
):
614 Personne en relation avec un Employe.
617 nom
= models
.CharField(max_length
=255)
618 prenom
= models
.CharField(u
"prénom", max_length
=255)
619 nom_affichage
= models
.CharField(
620 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
622 nationalite
= models
.ForeignKey(
623 ref
.Pays
, to_field
='code', db_column
='nationalite',
624 related_name
='ayantdroits_nationalite',
625 verbose_name
=u
"nationalité", null
=True, blank
=True
627 date_naissance
= models
.DateField(
628 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
629 validators
=[validate_date_passee
], null
=True, blank
=True
631 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
634 employe
= models
.ForeignKey(
635 'Employe', db_column
='employe', related_name
='ayantdroits',
636 verbose_name
=u
"Employé"
638 lien_parente
= models
.CharField(
639 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
640 null
=True, blank
=True
645 verbose_name
= u
"Ayant droit"
646 verbose_name_plural
= u
"Ayants droit"
648 def __unicode__(self
):
649 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
651 prefix_implantation
= "employe__dossiers__poste__implantation__region"
653 def get_regions(self
):
655 for d
in self
.employe
.dossiers
.all():
656 regions
.append(d
.poste
.implantation
.region
)
660 class AyantDroitCommentaire(Commentaire
):
661 ayant_droit
= models
.ForeignKey(
662 'AyantDroit', db_column
='ayant_droit', related_name
='+'
668 STATUT_RESIDENCE_CHOICES
= (
670 ('expat', 'Expatrié'),
673 COMPTE_COMPTA_CHOICES
= (
680 class Dossier_(AUFMetadata
, DevisableMixin
):
682 Le Dossier regroupe les informations relatives à l'occupation
683 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
686 Plusieurs Contrats peuvent être associés au Dossier.
687 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
688 lequel aucun Dossier n'existe est un poste vacant.
691 objects
= DossierManager()
694 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
695 organisme_bstg
= models
.ForeignKey(
696 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
697 verbose_name
=u
"organisme",
699 u
"Si détaché (DET) ou mis à disposition (MAD), "
700 u
"préciser l'organisme."
701 ), null
=True, blank
=True
705 remplacement
= models
.BooleanField(default
=False)
706 remplacement_de
= models
.ForeignKey(
707 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
708 null
=True, blank
=True
710 statut_residence
= models
.CharField(
711 u
"statut", max_length
=10, default
='local', null
=True,
712 choices
=STATUT_RESIDENCE_CHOICES
716 classement
= models
.ForeignKey(
717 'Classement', db_column
='classement', related_name
='+', null
=True,
720 regime_travail
= models
.DecimalField(
721 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
722 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
724 regime_travail_nb_heure_semaine
= models
.DecimalField(
725 u
"nb. heures par semaine", max_digits
=12,
726 decimal_places
=2, null
=True,
727 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
728 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
731 # Occupation du Poste par cet Employe (anciennement "mandat")
732 date_debut
= models
.DateField(u
"date de début d'occupation de poste")
733 date_fin
= models
.DateField(
734 u
"Date de fin d'occupation de poste", null
=True, blank
=True
742 ordering
= ['employe__nom', ]
743 verbose_name
= u
"Dossier"
744 verbose_name_plural
= "Dossiers"
746 def salaire_theorique(self
):
747 annee
= date
.today().year
748 coeff
= self
.classement
.coefficient
749 implantation
= self
.poste
.implantation
750 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
752 montant
= coeff
* point
.valeur
753 devise
= point
.devise
754 return {'montant': montant
, 'devise': devise
}
756 def __unicode__(self
):
757 poste
= self
.poste
.nom
758 if self
.employe
.genre
== 'F':
759 poste
= self
.poste
.nom_feminin
760 return u
'%s - %s' % (self
.employe
, poste
)
762 prefix_implantation
= "poste__implantation__region"
764 def get_regions(self
):
765 return [self
.poste
.implantation
.region
]
767 def remunerations(self
):
768 key
= "%s_remunerations" % self
._meta
.app_label
769 remunerations
= getattr(self
, key
)
770 return remunerations
.all().order_by('-date_debut')
772 def remunerations_en_cours(self
):
773 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
774 return self
.remunerations().all().filter(q
).order_by('date_debut')
776 def get_salaire(self
):
778 return [r
for r
in self
.remunerations().order_by('-date_debut')
779 if r
.type_id
== 1][0]
783 def get_salaire_euros(self
):
784 tx
= self
.taux_devise()
785 return (float)(tx
) * (float)(self
.salaire
)
787 def get_remunerations_brutes(self
):
791 4 Indemnité d'expatriation
792 5 Indemnité pour frais
793 6 Indemnité de logement
794 7 Indemnité de fonction
795 8 Indemnité de responsabilité
796 9 Indemnité de transport
797 10 Indemnité compensatrice
798 11 Indemnité de subsistance
799 12 Indemnité différentielle
800 13 Prime d'installation
803 16 Indemnité de départ
804 18 Prime de 13ième mois
807 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
808 return [r
for r
in self
.remunerations_en_cours().all()
811 def get_charges_salariales(self
):
813 20 Charges salariales ?
816 return [r
for r
in self
.remunerations_en_cours().all()
819 def get_charges_patronales(self
):
821 17 Charges patronales
824 return [r
for r
in self
.remunerations_en_cours().all()
827 def get_remunerations_tierces(self
):
831 return [r
for r
in self
.remunerations_en_cours().all()
832 if r
.type_id
in (2,)]
836 def get_total_local_charges_salariales(self
):
837 devise
= self
.poste
.get_devise()
839 for r
in self
.get_charges_salariales():
840 if r
.devise
!= devise
:
842 total
+= float(r
.montant
)
845 def get_total_local_charges_patronales(self
):
846 devise
= self
.poste
.get_devise()
848 for r
in self
.get_charges_patronales():
849 if r
.devise
!= devise
:
851 total
+= float(r
.montant
)
854 def get_local_salaire_brut(self
):
856 somme des rémuérations brutes
858 devise
= self
.poste
.get_devise()
860 for r
in self
.get_remunerations_brutes():
861 if r
.devise
!= devise
:
863 total
+= float(r
.montant
)
866 def get_local_salaire_net(self
):
868 salaire brut - charges salariales
870 devise
= self
.poste
.get_devise()
872 for r
in self
.get_charges_salariales():
873 if r
.devise
!= devise
:
875 total_charges
+= float(r
.montant
)
876 return self
.get_local_salaire_brut() - total_charges
878 def get_local_couts_auf(self
):
880 salaire net + charges patronales
882 devise
= self
.poste
.get_devise()
884 for r
in self
.get_charges_patronales():
885 if r
.devise
!= devise
:
887 total_charges
+= float(r
.montant
)
888 return self
.get_local_salaire_net() + total_charges
890 def get_total_local_remunerations_tierces(self
):
891 devise
= self
.poste
.get_devise()
893 for r
in self
.get_remunerations_tierces():
894 if r
.devise
!= devise
:
896 total
+= float(r
.montant
)
901 def get_total_charges_salariales(self
):
903 for r
in self
.get_charges_salariales():
904 total
+= r
.montant_euros()
907 def get_total_charges_patronales(self
):
909 for r
in self
.get_charges_patronales():
910 total
+= r
.montant_euros()
913 def get_salaire_brut(self
):
915 somme des rémuérations brutes
918 for r
in self
.get_remunerations_brutes():
919 total
+= r
.montant_euros()
922 def get_salaire_net(self
):
924 salaire brut - charges salariales
927 for r
in self
.get_charges_salariales():
928 total_charges
+= r
.montant_euros()
929 return self
.get_salaire_brut() - total_charges
931 def get_couts_auf(self
):
933 salaire net + charges patronales
936 for r
in self
.get_charges_patronales():
937 total_charges
+= r
.montant_euros()
938 return self
.get_salaire_net() + total_charges
940 def get_total_remunerations_tierces(self
):
942 for r
in self
.get_remunerations_tierces():
943 total
+= r
.montant_euros()
947 class Dossier(Dossier_
):
948 __doc__
= Dossier_
.__doc__
949 poste
= models
.ForeignKey(
950 Poste
, db_column
='poste', related_name
='rh_dossiers',
951 help_text
=u
"Taper le nom du poste ou du type de poste",
953 employe
= models
.ForeignKey(
954 'Employe', db_column
='employe',
955 help_text
=u
"Taper le nom de l'employé",
956 related_name
='rh_dossiers', verbose_name
=u
"employé"
958 principal
= models
.BooleanField(
959 u
"Principal?", default
=True,
961 u
"Ce dossier est pour le principal poste occupé par l'employé"
966 class DossierPiece_(models
.Model
):
968 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
969 Ex.: Lettre de motivation.
971 nom
= models
.CharField(max_length
=255)
972 fichier
= models
.FileField(
973 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
980 def __unicode__(self
):
981 return u
'%s' % (self
.nom
)
984 class DossierPiece(DossierPiece_
):
985 dossier
= models
.ForeignKey(
986 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
991 class DossierCommentaire(Commentaire
):
992 dossier
= models
.ForeignKey(
993 Dossier
, db_column
='dossier', related_name
='commentaires'
997 class DossierComparaison_(models
.Model
, DevisableMixin
):
999 Photo d'une comparaison salariale au moment de l'embauche.
1001 objects
= DossierComparaisonManager()
1003 implantation
= models
.ForeignKey(
1004 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1006 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1007 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1008 montant
= models
.IntegerField(null
=True)
1009 devise
= models
.ForeignKey(
1010 'Devise', related_name
='+', null
=True, blank
=True
1016 def __unicode__(self
):
1017 return "%s (%s)" % (self
.poste
, self
.personne
)
1020 class DossierComparaison(DossierComparaison_
):
1021 dossier
= models
.ForeignKey(
1022 Dossier
, related_name
='rh_comparaisons'
1028 class RemunerationMixin(AUFMetadata
):
1031 type = models
.ForeignKey(
1032 'TypeRemuneration', db_column
='type', related_name
='+',
1033 verbose_name
=u
"type de rémunération"
1035 type_revalorisation
= models
.ForeignKey(
1036 'TypeRevalorisation', db_column
='type_revalorisation',
1037 related_name
='+', verbose_name
=u
"type de revalorisation",
1038 null
=True, blank
=True
1040 montant
= models
.DecimalField(
1041 null
=True, blank
=True,
1042 default
=0, max_digits
=12, decimal_places
=2
1043 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1044 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1046 # commentaire = precision
1047 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1049 # date_debut = anciennement date_effectif
1050 date_debut
= models
.DateField(u
"date de début", null
=True, blank
=True)
1051 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1055 ordering
= ['type__nom', '-date_fin']
1057 def __unicode__(self
):
1058 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1061 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1063 Structure de rémunération (données budgétaires) en situation normale
1064 pour un Dossier. Si un Evenement existe, utiliser la structure de
1065 rémunération EvenementRemuneration de cet événement.
1068 def montant_mois(self
):
1069 return round(self
.montant
/ 12, 2)
1071 def montant_avec_regime(self
):
1072 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1074 def montant_euro_mois(self
):
1075 return round(self
.montant_euros() / 12, 2)
1077 def __unicode__(self
):
1079 devise
= self
.devise
.code
1082 return "%s %s" % (self
.montant
, devise
)
1086 verbose_name
= u
"Rémunération"
1087 verbose_name_plural
= u
"Rémunérations"
1090 class Remuneration(Remuneration_
):
1091 dossier
= models
.ForeignKey(
1092 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1098 class ContratManager(NoDeleteManager
):
1099 def get_query_set(self
):
1100 return super(ContratManager
, self
).get_query_set() \
1101 .select_related('dossier', 'dossier__poste')
1104 class Contrat_(AUFMetadata
):
1106 Document juridique qui encadre la relation de travail d'un Employe
1107 pour un Poste particulier. Pour un Dossier (qui documente cette
1108 relation de travail) plusieurs contrats peuvent être associés.
1110 objects
= ContratManager()
1111 type_contrat
= models
.ForeignKey(
1112 'TypeContrat', db_column
='type_contrat',
1113 verbose_name
=u
'type de contrat', related_name
='+'
1115 date_debut
= models
.DateField(u
"date de début")
1116 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1117 fichier
= models
.FileField(
1118 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1124 ordering
= ['dossier__employe__nom']
1125 verbose_name
= u
"Contrat"
1126 verbose_name_plural
= u
"Contrats"
1128 def __unicode__(self
):
1129 return u
'%s - %s' % (self
.dossier
, self
.id)
1132 class Contrat(Contrat_
):
1133 dossier
= models
.ForeignKey(
1134 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1140 class CategorieEmploi(AUFMetadata
):
1142 Catégorie utilisée dans la gestion des Postes.
1143 Catégorie supérieure à TypePoste.
1145 nom
= models
.CharField(max_length
=255)
1149 verbose_name
= u
"catégorie d'emploi"
1150 verbose_name_plural
= u
"catégories d'emploi"
1152 def __unicode__(self
):
1156 class FamilleProfessionnelle(models
.Model
):
1158 Famille professionnelle d'un poste.
1160 nom
= models
.CharField(max_length
=100)
1164 verbose_name
= u
'famille professionnelle'
1165 verbose_name_plural
= u
'familles professionnelles'
1167 def __unicode__(self
):
1171 class TypePoste(AUFMetadata
):
1175 nom
= models
.CharField(max_length
=255)
1176 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1177 is_responsable
= models
.BooleanField(
1178 u
"poste de responsabilité", default
=False
1180 categorie_emploi
= models
.ForeignKey(
1181 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1182 verbose_name
=u
"catégorie d'emploi"
1184 famille_professionnelle
= models
.ForeignKey(
1185 FamilleProfessionnelle
, related_name
='types_de_poste',
1186 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1191 verbose_name
= u
"Type de poste"
1192 verbose_name_plural
= u
"Types de poste"
1194 def __unicode__(self
):
1195 return u
'%s' % (self
.nom
)
1197 TYPE_PAIEMENT_CHOICES
= (
1198 (u
'Régulier', u
'Régulier'),
1199 (u
'Ponctuel', u
'Ponctuel'),
1202 NATURE_REMUNERATION_CHOICES
= (
1203 (u
'Accessoire', u
'Accessoire'),
1204 (u
'Charges', u
'Charges'),
1205 (u
'Indemnité', u
'Indemnité'),
1206 (u
'RAS', u
'Rémunération autre source'),
1207 (u
'Traitement', u
'Traitement'),
1211 class TypeRemuneration(AUFMetadata
):
1213 Catégorie de Remuneration.
1215 objects
= TypeRemunerationManager()
1217 nom
= models
.CharField(max_length
=255)
1218 type_paiement
= models
.CharField(
1219 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1221 nature_remuneration
= models
.CharField(
1222 u
"nature de la rémunération", max_length
=30,
1223 choices
=NATURE_REMUNERATION_CHOICES
1225 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1229 verbose_name
= u
"Type de rémunération"
1230 verbose_name_plural
= u
"Types de rémunération"
1232 def __unicode__(self
):
1234 archive
= u
"(archivé)"
1237 return u
'%s %s' % (self
.nom
, archive
)
1240 class TypeRevalorisation(AUFMetadata
):
1242 Justification du changement de la Remuneration.
1243 (Actuellement utilisé dans aucun traitement informatique.)
1245 nom
= models
.CharField(max_length
=255)
1249 verbose_name
= u
"Type de revalorisation"
1250 verbose_name_plural
= u
"Types de revalorisation"
1252 def __unicode__(self
):
1253 return u
'%s' % (self
.nom
)
1256 class Service(AUFMetadata
):
1258 Unité administrative où les Postes sont rattachés.
1260 objects
= ServiceManager()
1262 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1263 nom
= models
.CharField(max_length
=255)
1267 verbose_name
= u
"Service"
1268 verbose_name_plural
= u
"Services"
1270 def __unicode__(self
):
1272 archive
= u
"(archivé)"
1275 return u
'%s %s' % (self
.nom
, archive
)
1278 TYPE_ORGANISME_CHOICES
= (
1279 ('MAD', 'Mise à disposition'),
1280 ('DET', 'Détachement'),
1284 class OrganismeBstg(AUFMetadata
):
1286 Organisation d'où provient un Employe mis à disposition (MAD) de
1287 ou détaché (DET) à l'AUF à titre gratuit.
1289 (BSTG = bien et service à titre gratuit.)
1291 nom
= models
.CharField(max_length
=255)
1292 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1293 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1295 related_name
='organismes_bstg',
1296 null
=True, blank
=True)
1299 ordering
= ['type', 'nom']
1300 verbose_name
= u
"Organisme BSTG"
1301 verbose_name_plural
= u
"Organismes BSTG"
1303 def __unicode__(self
):
1304 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1306 prefix_implantation
= "pays__region"
1308 def get_regions(self
):
1309 return [self
.pays
.region
]
1312 class Statut(AUFMetadata
):
1314 Statut de l'Employe dans le cadre d'un Dossier particulier.
1317 code
= models
.CharField(
1318 max_length
=25, unique
=True,
1320 u
"Saisir un code court mais lisible pour ce statut : "
1321 u
"le code est utilisé pour associer les statuts aux autres "
1322 u
"données tout en demeurant plus lisible qu'un identifiant "
1326 nom
= models
.CharField(max_length
=255)
1330 verbose_name
= u
"Statut d'employé"
1331 verbose_name_plural
= u
"Statuts d'employé"
1333 def __unicode__(self
):
1334 return u
'%s : %s' % (self
.code
, self
.nom
)
1337 TYPE_CLASSEMENT_CHOICES
= (
1338 ('S', 'S -Soutien'),
1339 ('T', 'T - Technicien'),
1340 ('P', 'P - Professionel'),
1342 ('D', 'D - Direction'),
1343 ('SO', 'SO - Sans objet [expatriés]'),
1344 ('HG', 'HG - Hors grille [direction]'),
1348 class ClassementManager(models
.Manager
):
1350 Ordonner les spcéfiquement les classements.
1352 def get_query_set(self
):
1353 qs
= super(self
.__class__
, self
).get_query_set()
1354 qs
= qs
.extra(select
={
1355 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1357 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1361 class Classement_(AUFMetadata
):
1363 Éléments de classement de la
1364 "Grille générique de classement hiérarchique".
1366 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1367 classement dans la grille. Le classement donne le coefficient utilisé dans:
1369 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1371 objects
= ClassementManager()
1374 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1375 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1376 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1377 coefficient
= models
.FloatField(u
"coefficient", default
=0, null
=True)
1380 # annee # au lieu de date_debut et date_fin
1381 commentaire
= models
.TextField(null
=True, blank
=True)
1385 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1386 verbose_name
= u
"Classement"
1387 verbose_name_plural
= u
"Classements"
1389 def __unicode__(self
):
1390 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1393 class Classement(Classement_
):
1394 __doc__
= Classement_
.__doc__
1397 class TauxChange_(AUFMetadata
):
1399 Taux de change de la devise vers l'euro (EUR)
1400 pour chaque année budgétaire.
1403 devise
= models
.ForeignKey('Devise', db_column
='devise')
1404 annee
= models
.IntegerField(u
"année")
1405 taux
= models
.FloatField(u
"taux vers l'euro")
1409 ordering
= ['-annee', 'devise__code']
1410 verbose_name
= u
"Taux de change"
1411 verbose_name_plural
= u
"Taux de change"
1413 def __unicode__(self
):
1414 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1417 class TauxChange(TauxChange_
):
1418 __doc__
= TauxChange_
.__doc__
1421 class ValeurPointManager(NoDeleteManager
):
1423 def get_query_set(self
):
1424 return super(ValeurPointManager
, self
).get_query_set() \
1425 .select_related('devise', 'implantation')
1428 class ValeurPoint_(AUFMetadata
):
1430 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1431 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1432 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1434 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1437 actuelles
= ValeurPointManager()
1439 valeur
= models
.FloatField(null
=True)
1440 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1441 implantation
= models
.ForeignKey(ref
.Implantation
,
1442 db_column
='implantation',
1443 related_name
='%(app_label)s_valeur_point')
1445 annee
= models
.IntegerField()
1448 ordering
= ['-annee', 'implantation__nom']
1450 verbose_name
= u
"Valeur du point"
1451 verbose_name_plural
= u
"Valeurs du point"
1453 def __unicode__(self
):
1454 return u
'%s %s %s [%s] %s' % (
1455 self
.devise
.code
, self
.annee
, self
.valeur
,
1456 self
.implantation
.nom_court
, self
.devise
.nom
1460 class ValeurPoint(ValeurPoint_
):
1461 __doc__
= ValeurPoint_
.__doc__
1464 class Devise(AUFMetadata
):
1468 objects
= DeviseManager()
1470 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1471 code
= models
.CharField(max_length
=10, unique
=True)
1472 nom
= models
.CharField(max_length
=255)
1476 verbose_name
= u
"Devise"
1477 verbose_name_plural
= u
"Devises"
1479 def __unicode__(self
):
1480 return u
'%s - %s' % (self
.code
, self
.nom
)
1483 class TypeContrat(AUFMetadata
):
1487 nom
= models
.CharField(max_length
=255)
1488 nom_long
= models
.CharField(max_length
=255)
1492 verbose_name
= u
"Type de contrat"
1493 verbose_name_plural
= u
"Types de contrat"
1495 def __unicode__(self
):
1496 return u
'%s' % (self
.nom
)
1501 class ResponsableImplantationProxy(ref
.Implantation
):
1505 verbose_name
= u
"Responsable d'implantation"
1506 verbose_name_plural
= u
"Responsables d'implantation"
1509 class ResponsableImplantation(models
.Model
):
1511 Le responsable d'une implantation.
1512 Anciennement géré sur le Dossier du responsable.
1514 employe
= models
.ForeignKey(
1515 'Employe', db_column
='employe', related_name
='+', null
=True,
1518 implantation
= models
.OneToOneField(
1519 "ResponsableImplantationProxy", db_column
='implantation',
1520 related_name
='responsable', unique
=True
1523 def __unicode__(self
):
1524 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1527 ordering
= ['implantation__nom']
1528 verbose_name
= "Responsable d'implantation"
1529 verbose_name_plural
= "Responsables d'implantation"