1 # -=- encoding: utf-8 -=-
4 from datetime
import date
5 from decimal
import Decimal
8 from auf
.django
.emploi
.models
import \
9 GENRE_CHOICES
, SITUATION_CHOICES
# devrait plutot être dans references
10 from auf
.django
.metadata
.models
import AUFMetadata
11 from auf
.django
.metadata
.managers
import NoDeleteManager
12 from auf
.django
.references
import models
as ref
13 from django
.core
.files
.storage
import FileSystemStorage
14 from django
.db
import models
15 from django
.db
.models
import Q
16 from django
.conf
import settings
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, blank
=True
227 salaire_max
= models
.DecimalField(
228 max_digits
=12, decimal_places
=2, null
=True, blank
=True
230 indemn_min
= models
.DecimalField(
231 max_digits
=12, decimal_places
=2, null
=True, blank
=True
233 indemn_max
= models
.DecimalField(
234 max_digits
=12, decimal_places
=2, null
=True, blank
=True
236 autre_min
= models
.DecimalField(
237 max_digits
=12, decimal_places
=2, null
=True, blank
=True
239 autre_max
= models
.DecimalField(
240 max_digits
=12, decimal_places
=2, null
=True, blank
=True
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())
343 reversion
.register(Poste
, format
='xml', follow
=[
344 'rh_financements', 'rh_pieces', 'rh_comparaisons_internes',
345 'commentaires', 'rh_dossiers'
349 POSTE_FINANCEMENT_CHOICES
= (
350 ('A', 'A - Frais de personnel'),
351 ('B', 'B - Projet(s)-Titre(s)'),
356 class PosteFinancement_(models
.Model
):
358 Pour un Poste, structure d'informations décrivant comment on prévoit
361 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
362 pourcentage
= models
.DecimalField(
363 max_digits
=12, decimal_places
=2,
364 help_text
="ex.: 33.33 % (décimale avec point)"
366 commentaire
= models
.TextField(
367 help_text
="Spécifiez la source de financement."
374 def __unicode__(self
):
375 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
378 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
381 class PosteFinancement(PosteFinancement_
):
382 poste
= models
.ForeignKey(
383 Poste
, db_column
='poste', related_name
='rh_financements'
386 reversion
.register(PosteFinancement
, format
='xml')
389 class PostePiece_(models
.Model
):
391 Documents relatifs au Poste.
392 Ex.: Description de poste
394 nom
= models
.CharField(u
"Nom", max_length
=255)
395 fichier
= models
.FileField(
396 u
"Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
403 def __unicode__(self
):
404 return u
'%s' % (self
.nom
)
407 class PostePiece(PostePiece_
):
408 poste
= models
.ForeignKey(
409 Poste
, db_column
='poste', related_name
='rh_pieces'
412 reversion
.register(PostePiece
, format
='xml')
415 class PosteComparaison_(AUFMetadata
, DevisableMixin
):
417 De la même manière qu'un dossier, un poste peut-être comparé à un autre
420 objects
= PosteComparaisonManager()
422 implantation
= models
.ForeignKey(
423 ref
.Implantation
, null
=True, blank
=True, related_name
="+"
425 nom
= models
.CharField(u
"Poste", max_length
=255, null
=True, blank
=True)
426 montant
= models
.IntegerField(null
=True)
427 devise
= models
.ForeignKey(
428 "Devise", related_name
='+', null
=True, blank
=True
434 def __unicode__(self
):
438 class PosteComparaison(PosteComparaison_
):
439 poste
= models
.ForeignKey(
440 Poste
, related_name
='rh_comparaisons_internes'
443 objects
= NoDeleteManager()
445 reversion
.register(PosteComparaison
, format
='xml')
448 class PosteCommentaire(Commentaire
):
449 poste
= models
.ForeignKey(
450 Poste
, db_column
='poste', related_name
='commentaires'
453 reversion
.register(PosteCommentaire
, format
='xml')
458 class Employe(AUFMetadata
):
460 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
461 Dossiers qu'il occupe ou a occupé de Postes.
463 Cette classe aurait pu avantageusement s'appeler Personne car la notion
464 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
467 objects
= EmployeManager()
470 nom
= models
.CharField(max_length
=255)
471 prenom
= models
.CharField(u
"prénom", max_length
=255)
472 nom_affichage
= models
.CharField(
473 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
475 nationalite
= models
.ForeignKey(
476 ref
.Pays
, to_field
='code', db_column
='nationalite',
477 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
478 blank
=True, null
=True
480 date_naissance
= models
.DateField(
481 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
482 validators
=[validate_date_passee
], null
=True, blank
=True
484 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
487 situation_famille
= models
.CharField(
488 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
489 null
=True, blank
=True
491 date_entree
= models
.DateField(
492 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
497 tel_domicile
= models
.CharField(
498 u
"tél. domicile", max_length
=255, null
=True, blank
=True
500 tel_cellulaire
= models
.CharField(
501 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
503 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
504 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
505 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
506 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
507 pays
= models
.ForeignKey(
508 ref
.Pays
, to_field
='code', db_column
='pays',
509 related_name
='employes', null
=True, blank
=True
511 courriel_perso
= models
.EmailField(
512 u
'adresse courriel personnelle', blank
=True
515 # meta dématérialisation : pour permettre le filtrage
516 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
519 ordering
= ['nom', 'prenom']
520 verbose_name
= u
"Employé"
521 verbose_name_plural
= u
"Employés"
523 def __unicode__(self
):
524 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
528 if self
.genre
.upper() == u
'M':
530 elif self
.genre
.upper() == u
'F':
536 Retourne l'URL du service retournant la photo de l'Employe.
537 Équivalent reverse url 'rh_photo' avec id en param.
539 from django
.core
.urlresolvers
import reverse
540 return reverse('rh_photo', kwargs
={'id': self
.id})
542 def dossiers_passes(self
):
543 params
= {KEY_STATUT
: STATUT_INACTIF
, }
544 search
= RechercheTemporelle(params
, self
.__class__
)
545 search
.purge_params(params
)
546 q
= search
.get_q_temporel(self
.rh_dossiers
)
547 return self
.rh_dossiers
.filter(q
)
549 def dossiers_futurs(self
):
550 params
= {KEY_STATUT
: STATUT_FUTUR
, }
551 search
= RechercheTemporelle(params
, self
.__class__
)
552 search
.purge_params(params
)
553 q
= search
.get_q_temporel(self
.rh_dossiers
)
554 return self
.rh_dossiers
.filter(q
)
556 def dossiers_encours(self
):
557 params
= {KEY_STATUT
: STATUT_ACTIF
, }
558 search
= RechercheTemporelle(params
, self
.__class__
)
559 search
.purge_params(params
)
560 q
= search
.get_q_temporel(self
.rh_dossiers
)
561 return self
.rh_dossiers
.filter(q
)
563 def postes_encours(self
):
564 postes_encours
= set()
565 for d
in self
.dossiers_encours():
566 postes_encours
.add(d
.poste
)
567 return postes_encours
569 def poste_principal(self
):
571 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
573 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
575 poste
= Poste
.objects
.none()
577 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
582 prefix_implantation
= "rh_dossiers__poste__implantation__region"
584 def get_regions(self
):
586 for d
in self
.dossiers
.all():
587 regions
.append(d
.poste
.implantation
.region
)
590 reversion
.register(Employe
, format
='xml', follow
=[
591 'pieces', 'commentaires', 'ayantdroits', 'rh_dossiers'
595 class EmployePiece(models
.Model
):
597 Documents relatifs à un employé.
600 employe
= models
.ForeignKey(
601 'Employe', db_column
='employe', related_name
="pieces",
602 verbose_name
=u
"employé"
604 nom
= models
.CharField(max_length
=255)
605 fichier
= models
.FileField(
606 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
611 verbose_name
= u
"Employé pièce"
612 verbose_name_plural
= u
"Employé pièces"
614 def __unicode__(self
):
615 return u
'%s' % (self
.nom
)
617 reversion
.register(EmployePiece
, format
='xml')
620 class EmployeCommentaire(Commentaire
):
621 employe
= models
.ForeignKey(
622 'Employe', db_column
='employe', related_name
='commentaires'
626 verbose_name
= u
"Employé commentaire"
627 verbose_name_plural
= u
"Employé commentaires"
629 reversion
.register(EmployeCommentaire
, format
='xml')
632 LIEN_PARENTE_CHOICES
= (
633 ('Conjoint', 'Conjoint'),
634 ('Conjointe', 'Conjointe'),
640 class AyantDroit(AUFMetadata
):
642 Personne en relation avec un Employe.
645 nom
= models
.CharField(max_length
=255)
646 prenom
= models
.CharField(u
"prénom", max_length
=255)
647 nom_affichage
= models
.CharField(
648 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
650 nationalite
= models
.ForeignKey(
651 ref
.Pays
, to_field
='code', db_column
='nationalite',
652 related_name
='ayantdroits_nationalite',
653 verbose_name
=u
"nationalité", null
=True, blank
=True
655 date_naissance
= models
.DateField(
656 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
657 validators
=[validate_date_passee
], null
=True, blank
=True
659 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
662 employe
= models
.ForeignKey(
663 'Employe', db_column
='employe', related_name
='ayantdroits',
664 verbose_name
=u
"Employé"
666 lien_parente
= models
.CharField(
667 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
668 null
=True, blank
=True
673 verbose_name
= u
"Ayant droit"
674 verbose_name_plural
= u
"Ayants droit"
676 def __unicode__(self
):
677 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
679 prefix_implantation
= "employe__dossiers__poste__implantation__region"
681 def get_regions(self
):
683 for d
in self
.employe
.dossiers
.all():
684 regions
.append(d
.poste
.implantation
.region
)
687 reversion
.register(AyantDroit
, format
='xml', follow
=['commentaires'])
690 class AyantDroitCommentaire(Commentaire
):
691 ayant_droit
= models
.ForeignKey(
692 'AyantDroit', db_column
='ayant_droit', related_name
='commentaires'
695 reversion
.register(AyantDroitCommentaire
, format
='xml')
700 STATUT_RESIDENCE_CHOICES
= (
702 ('expat', 'Expatrié'),
705 COMPTE_COMPTA_CHOICES
= (
712 class Dossier_(AUFMetadata
, DevisableMixin
):
714 Le Dossier regroupe les informations relatives à l'occupation
715 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
718 Plusieurs Contrats peuvent être associés au Dossier.
719 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
720 lequel aucun Dossier n'existe est un poste vacant.
723 objects
= DossierManager()
726 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
727 organisme_bstg
= models
.ForeignKey(
728 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
729 verbose_name
=u
"organisme",
731 u
"Si détaché (DET) ou mis à disposition (MAD), "
732 u
"préciser l'organisme."
733 ), null
=True, blank
=True
737 remplacement
= models
.BooleanField(default
=False)
738 remplacement_de
= models
.ForeignKey(
739 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
740 null
=True, blank
=True
742 statut_residence
= models
.CharField(
743 u
"statut", max_length
=10, default
='local', null
=True,
744 choices
=STATUT_RESIDENCE_CHOICES
748 classement
= models
.ForeignKey(
749 'Classement', db_column
='classement', related_name
='+', null
=True,
752 regime_travail
= models
.DecimalField(
753 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
754 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
756 regime_travail_nb_heure_semaine
= models
.DecimalField(
757 u
"nb. heures par semaine", max_digits
=12,
758 decimal_places
=2, null
=True,
759 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
760 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
763 # Occupation du Poste par cet Employe (anciennement "mandat")
764 date_debut
= models
.DateField(u
"date de début d'occupation de poste")
765 date_fin
= models
.DateField(
766 u
"Date de fin d'occupation de poste", null
=True, blank
=True
774 ordering
= ['employe__nom', ]
775 verbose_name
= u
"Dossier"
776 verbose_name_plural
= "Dossiers"
778 def salaire_theorique(self
):
779 annee
= date
.today().year
780 coeff
= self
.classement
.coefficient
781 implantation
= self
.poste
.implantation
782 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
784 montant
= coeff
* point
.valeur
785 devise
= point
.devise
786 return {'montant': montant
, 'devise': devise
}
788 def __unicode__(self
):
789 poste
= self
.poste
.nom
790 if self
.employe
.genre
== 'F':
791 poste
= self
.poste
.nom_feminin
792 return u
'%s - %s' % (self
.employe
, poste
)
794 prefix_implantation
= "poste__implantation__region"
796 def get_regions(self
):
797 return [self
.poste
.implantation
.region
]
799 def remunerations(self
):
800 key
= "%s_remunerations" % self
._meta
.app_label
801 remunerations
= getattr(self
, key
)
802 return remunerations
.all().order_by('-date_debut')
804 def remunerations_en_cours(self
):
805 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
806 return self
.remunerations().all().filter(q
).order_by('date_debut')
808 def get_salaire(self
):
810 return [r
for r
in self
.remunerations().order_by('-date_debut')
811 if r
.type_id
== 1][0]
815 def get_salaire_euros(self
):
816 tx
= self
.taux_devise()
817 return (float)(tx
) * (float)(self
.salaire
)
819 def get_remunerations_brutes(self
):
823 4 Indemnité d'expatriation
824 5 Indemnité pour frais
825 6 Indemnité de logement
826 7 Indemnité de fonction
827 8 Indemnité de responsabilité
828 9 Indemnité de transport
829 10 Indemnité compensatrice
830 11 Indemnité de subsistance
831 12 Indemnité différentielle
832 13 Prime d'installation
835 16 Indemnité de départ
836 18 Prime de 13ième mois
839 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
840 return [r
for r
in self
.remunerations_en_cours().all()
843 def get_charges_salariales(self
):
845 20 Charges salariales ?
848 return [r
for r
in self
.remunerations_en_cours().all()
851 def get_charges_patronales(self
):
853 17 Charges patronales
856 return [r
for r
in self
.remunerations_en_cours().all()
859 def get_remunerations_tierces(self
):
863 return [r
for r
in self
.remunerations_en_cours().all()
864 if r
.type_id
in (2,)]
868 def get_total_local_charges_salariales(self
):
869 devise
= self
.poste
.get_devise()
871 for r
in self
.get_charges_salariales():
872 if r
.devise
!= devise
:
874 total
+= float(r
.montant
)
877 def get_total_local_charges_patronales(self
):
878 devise
= self
.poste
.get_devise()
880 for r
in self
.get_charges_patronales():
881 if r
.devise
!= devise
:
883 total
+= float(r
.montant
)
886 def get_local_salaire_brut(self
):
888 somme des rémuérations brutes
890 devise
= self
.poste
.get_devise()
892 for r
in self
.get_remunerations_brutes():
893 if r
.devise
!= devise
:
895 total
+= float(r
.montant
)
898 def get_local_salaire_net(self
):
900 salaire brut - charges salariales
902 devise
= self
.poste
.get_devise()
904 for r
in self
.get_charges_salariales():
905 if r
.devise
!= devise
:
907 total_charges
+= float(r
.montant
)
908 return self
.get_local_salaire_brut() - total_charges
910 def get_local_couts_auf(self
):
912 salaire net + charges patronales
914 devise
= self
.poste
.get_devise()
916 for r
in self
.get_charges_patronales():
917 if r
.devise
!= devise
:
919 total_charges
+= float(r
.montant
)
920 return self
.get_local_salaire_net() + total_charges
922 def get_total_local_remunerations_tierces(self
):
923 devise
= self
.poste
.get_devise()
925 for r
in self
.get_remunerations_tierces():
926 if r
.devise
!= devise
:
928 total
+= float(r
.montant
)
933 def get_total_charges_salariales(self
):
935 for r
in self
.get_charges_salariales():
936 total
+= r
.montant_euros()
939 def get_total_charges_patronales(self
):
941 for r
in self
.get_charges_patronales():
942 total
+= r
.montant_euros()
945 def get_salaire_brut(self
):
947 somme des rémuérations brutes
950 for r
in self
.get_remunerations_brutes():
951 total
+= r
.montant_euros()
954 def get_salaire_net(self
):
956 salaire brut - charges salariales
959 for r
in self
.get_charges_salariales():
960 total_charges
+= r
.montant_euros()
961 return self
.get_salaire_brut() - total_charges
963 def get_couts_auf(self
):
965 salaire net + charges patronales
968 for r
in self
.get_charges_patronales():
969 total_charges
+= r
.montant_euros()
970 return self
.get_salaire_net() + total_charges
972 def get_total_remunerations_tierces(self
):
974 for r
in self
.get_remunerations_tierces():
975 total
+= r
.montant_euros()
980 return (self
.date_debut
is None or self
.date_debut
<= today
) \
981 and (self
.date_fin
is None or self
.date_fin
>= today
) \
982 and not (self
.date_fin
is None and self
.date_debut
is None)
985 class Dossier(Dossier_
):
986 __doc__
= Dossier_
.__doc__
987 poste
= models
.ForeignKey(
988 Poste
, db_column
='poste', related_name
='rh_dossiers',
989 help_text
=u
"Taper le nom du poste ou du type de poste",
991 employe
= models
.ForeignKey(
992 'Employe', db_column
='employe',
993 help_text
=u
"Taper le nom de l'employé",
994 related_name
='rh_dossiers', verbose_name
=u
"employé"
996 principal
= models
.BooleanField(
997 u
"dossier principal", default
=True,
999 u
"Ce dossier est pour le principal poste occupé par l'employé"
1003 reversion
.register(Dossier
, format
='xml', follow
=[
1004 'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
1005 'rh_contrats', 'commentaires'
1009 class DossierPiece_(models
.Model
):
1011 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1012 Ex.: Lettre de motivation.
1014 nom
= models
.CharField(max_length
=255)
1015 fichier
= models
.FileField(
1016 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
1023 def __unicode__(self
):
1024 return u
'%s' % (self
.nom
)
1027 class DossierPiece(DossierPiece_
):
1028 dossier
= models
.ForeignKey(
1029 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
1032 reversion
.register(DossierPiece
, format
='xml')
1035 class DossierCommentaire(Commentaire
):
1036 dossier
= models
.ForeignKey(
1037 Dossier
, db_column
='dossier', related_name
='commentaires'
1040 reversion
.register(DossierCommentaire
, format
='xml')
1043 class DossierComparaison_(models
.Model
, DevisableMixin
):
1045 Photo d'une comparaison salariale au moment de l'embauche.
1047 objects
= DossierComparaisonManager()
1049 implantation
= models
.ForeignKey(
1050 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1052 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1053 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1054 montant
= models
.IntegerField(null
=True)
1055 devise
= models
.ForeignKey(
1056 'Devise', related_name
='+', null
=True, blank
=True
1062 def __unicode__(self
):
1063 return "%s (%s)" % (self
.poste
, self
.personne
)
1066 class DossierComparaison(DossierComparaison_
):
1067 dossier
= models
.ForeignKey(
1068 Dossier
, related_name
='rh_comparaisons'
1071 reversion
.register(DossierComparaison
, format
='xml')
1076 class RemunerationMixin(AUFMetadata
):
1079 type = models
.ForeignKey(
1080 'TypeRemuneration', db_column
='type', related_name
='+',
1081 verbose_name
=u
"type de rémunération"
1083 type_revalorisation
= models
.ForeignKey(
1084 'TypeRevalorisation', db_column
='type_revalorisation',
1085 related_name
='+', verbose_name
=u
"type de revalorisation",
1086 null
=True, blank
=True
1088 montant
= models
.DecimalField(
1089 null
=True, blank
=True, max_digits
=12, decimal_places
=2
1090 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1091 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1093 # commentaire = precision
1094 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1096 # date_debut = anciennement date_effectif
1097 date_debut
= models
.DateField(u
"date de début", null
=True, blank
=True)
1098 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1102 ordering
= ['type__nom', '-date_fin']
1104 def __unicode__(self
):
1105 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1108 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1110 Structure de rémunération (données budgétaires) en situation normale
1111 pour un Dossier. Si un Evenement existe, utiliser la structure de
1112 rémunération EvenementRemuneration de cet événement.
1115 def montant_mois(self
):
1116 return round(self
.montant
/ 12, 2)
1118 def montant_avec_regime(self
):
1119 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1121 def montant_euro_mois(self
):
1122 return round(self
.montant_euros() / 12, 2)
1124 def __unicode__(self
):
1126 devise
= self
.devise
.code
1129 return "%s %s" % (self
.montant
, devise
)
1133 verbose_name
= u
"Rémunération"
1134 verbose_name_plural
= u
"Rémunérations"
1137 class Remuneration(Remuneration_
):
1138 dossier
= models
.ForeignKey(
1139 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1142 reversion
.register(Remuneration
, format
='xml')
1147 class ContratManager(NoDeleteManager
):
1148 def get_query_set(self
):
1149 return super(ContratManager
, self
).get_query_set() \
1150 .select_related('dossier', 'dossier__poste')
1153 class Contrat_(AUFMetadata
):
1155 Document juridique qui encadre la relation de travail d'un Employe
1156 pour un Poste particulier. Pour un Dossier (qui documente cette
1157 relation de travail) plusieurs contrats peuvent être associés.
1159 objects
= ContratManager()
1160 type_contrat
= models
.ForeignKey(
1161 'TypeContrat', db_column
='type_contrat',
1162 verbose_name
=u
'type de contrat', related_name
='+'
1164 date_debut
= models
.DateField(u
"date de début")
1165 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1166 fichier
= models
.FileField(
1167 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1173 ordering
= ['dossier__employe__nom']
1174 verbose_name
= u
"Contrat"
1175 verbose_name_plural
= u
"Contrats"
1177 def __unicode__(self
):
1178 return u
'%s - %s' % (self
.dossier
, self
.id)
1181 class Contrat(Contrat_
):
1182 dossier
= models
.ForeignKey(
1183 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1186 reversion
.register(Contrat
, format
='xml')
1191 class CategorieEmploi(AUFMetadata
):
1193 Catégorie utilisée dans la gestion des Postes.
1194 Catégorie supérieure à TypePoste.
1196 nom
= models
.CharField(max_length
=255)
1200 verbose_name
= u
"catégorie d'emploi"
1201 verbose_name_plural
= u
"catégories d'emploi"
1203 def __unicode__(self
):
1206 reversion
.register(CategorieEmploi
, format
='xml')
1209 class FamilleProfessionnelle(models
.Model
):
1211 Famille professionnelle d'un poste.
1213 nom
= models
.CharField(max_length
=100)
1217 verbose_name
= u
'famille professionnelle'
1218 verbose_name_plural
= u
'familles professionnelles'
1220 def __unicode__(self
):
1223 reversion
.register(FamilleProfessionnelle
, format
='xml')
1226 class TypePoste(AUFMetadata
):
1230 nom
= models
.CharField(max_length
=255)
1231 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1232 is_responsable
= models
.BooleanField(
1233 u
"poste de responsabilité", default
=False
1235 categorie_emploi
= models
.ForeignKey(
1236 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1237 verbose_name
=u
"catégorie d'emploi"
1239 famille_professionnelle
= models
.ForeignKey(
1240 FamilleProfessionnelle
, related_name
='types_de_poste',
1241 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1246 verbose_name
= u
"Type de poste"
1247 verbose_name_plural
= u
"Types de poste"
1249 def __unicode__(self
):
1250 return u
'%s' % (self
.nom
)
1252 reversion
.register(TypePoste
, format
='xml')
1255 TYPE_PAIEMENT_CHOICES
= (
1256 (u
'Régulier', u
'Régulier'),
1257 (u
'Ponctuel', u
'Ponctuel'),
1260 NATURE_REMUNERATION_CHOICES
= (
1261 (u
'Accessoire', u
'Accessoire'),
1262 (u
'Charges', u
'Charges'),
1263 (u
'Indemnité', u
'Indemnité'),
1264 (u
'RAS', u
'Rémunération autre source'),
1265 (u
'Traitement', u
'Traitement'),
1269 class TypeRemuneration(AUFMetadata
):
1271 Catégorie de Remuneration.
1273 objects
= TypeRemunerationManager()
1275 nom
= models
.CharField(max_length
=255)
1276 type_paiement
= models
.CharField(
1277 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1279 nature_remuneration
= models
.CharField(
1280 u
"nature de la rémunération", max_length
=30,
1281 choices
=NATURE_REMUNERATION_CHOICES
1283 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1287 verbose_name
= u
"Type de rémunération"
1288 verbose_name_plural
= u
"Types de rémunération"
1290 def __unicode__(self
):
1292 archive
= u
"(archivé)"
1295 return u
'%s %s' % (self
.nom
, archive
)
1297 reversion
.register(TypeRemuneration
, format
='xml')
1300 class TypeRevalorisation(AUFMetadata
):
1302 Justification du changement de la Remuneration.
1303 (Actuellement utilisé dans aucun traitement informatique.)
1305 nom
= models
.CharField(max_length
=255)
1309 verbose_name
= u
"Type de revalorisation"
1310 verbose_name_plural
= u
"Types de revalorisation"
1312 def __unicode__(self
):
1313 return u
'%s' % (self
.nom
)
1315 reversion
.register(TypeRevalorisation
, format
='xml')
1318 class Service(AUFMetadata
):
1320 Unité administrative où les Postes sont rattachés.
1322 objects
= ServiceManager()
1324 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1325 nom
= models
.CharField(max_length
=255)
1329 verbose_name
= u
"Service"
1330 verbose_name_plural
= u
"Services"
1332 def __unicode__(self
):
1334 archive
= u
"(archivé)"
1337 return u
'%s %s' % (self
.nom
, archive
)
1339 reversion
.register(Service
, format
='xml')
1342 TYPE_ORGANISME_CHOICES
= (
1343 ('MAD', 'Mise à disposition'),
1344 ('DET', 'Détachement'),
1348 class OrganismeBstg(AUFMetadata
):
1350 Organisation d'où provient un Employe mis à disposition (MAD) de
1351 ou détaché (DET) à l'AUF à titre gratuit.
1353 (BSTG = bien et service à titre gratuit.)
1355 nom
= models
.CharField(max_length
=255)
1356 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1357 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1359 related_name
='organismes_bstg',
1360 null
=True, blank
=True)
1363 ordering
= ['type', 'nom']
1364 verbose_name
= u
"Organisme BSTG"
1365 verbose_name_plural
= u
"Organismes BSTG"
1367 def __unicode__(self
):
1368 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1370 prefix_implantation
= "pays__region"
1372 def get_regions(self
):
1373 return [self
.pays
.region
]
1375 reversion
.register(OrganismeBstg
, format
='xml')
1378 class Statut(AUFMetadata
):
1380 Statut de l'Employe dans le cadre d'un Dossier particulier.
1383 code
= models
.CharField(
1384 max_length
=25, unique
=True,
1386 u
"Saisir un code court mais lisible pour ce statut : "
1387 u
"le code est utilisé pour associer les statuts aux autres "
1388 u
"données tout en demeurant plus lisible qu'un identifiant "
1392 nom
= models
.CharField(max_length
=255)
1396 verbose_name
= u
"Statut d'employé"
1397 verbose_name_plural
= u
"Statuts d'employé"
1399 def __unicode__(self
):
1400 return u
'%s : %s' % (self
.code
, self
.nom
)
1402 reversion
.register(Statut
, format
='xml')
1405 TYPE_CLASSEMENT_CHOICES
= (
1406 ('S', 'S -Soutien'),
1407 ('T', 'T - Technicien'),
1408 ('P', 'P - Professionel'),
1410 ('D', 'D - Direction'),
1411 ('SO', 'SO - Sans objet [expatriés]'),
1412 ('HG', 'HG - Hors grille [direction]'),
1416 class ClassementManager(models
.Manager
):
1418 Ordonner les spcéfiquement les classements.
1420 def get_query_set(self
):
1421 qs
= super(self
.__class__
, self
).get_query_set()
1422 qs
= qs
.extra(select
={
1423 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1425 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1429 class Classement_(AUFMetadata
):
1431 Éléments de classement de la
1432 "Grille générique de classement hiérarchique".
1434 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1435 classement dans la grille. Le classement donne le coefficient utilisé dans:
1437 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1439 objects
= ClassementManager()
1442 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1443 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1444 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1445 coefficient
= models
.FloatField(u
"coefficient", blank
=True, null
=True)
1448 # annee # au lieu de date_debut et date_fin
1449 commentaire
= models
.TextField(null
=True, blank
=True)
1453 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1454 verbose_name
= u
"Classement"
1455 verbose_name_plural
= u
"Classements"
1457 def __unicode__(self
):
1458 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1461 class Classement(Classement_
):
1462 __doc__
= Classement_
.__doc__
1464 reversion
.register(Classement
, format
='xml')
1467 class TauxChange_(AUFMetadata
):
1469 Taux de change de la devise vers l'euro (EUR)
1470 pour chaque année budgétaire.
1473 devise
= models
.ForeignKey('Devise', db_column
='devise')
1474 annee
= models
.IntegerField(u
"année")
1475 taux
= models
.FloatField(u
"taux vers l'euro")
1479 ordering
= ['-annee', 'devise__code']
1480 verbose_name
= u
"Taux de change"
1481 verbose_name_plural
= u
"Taux de change"
1483 def __unicode__(self
):
1484 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1487 class TauxChange(TauxChange_
):
1488 __doc__
= TauxChange_
.__doc__
1490 reversion
.register(TauxChange
, format
='xml')
1493 class ValeurPointManager(NoDeleteManager
):
1495 def get_query_set(self
):
1496 return super(ValeurPointManager
, self
).get_query_set() \
1497 .select_related('devise', 'implantation')
1500 class ValeurPoint_(AUFMetadata
):
1502 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1503 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1504 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1506 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1509 actuelles
= ValeurPointManager()
1511 valeur
= models
.FloatField(null
=True)
1512 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1513 implantation
= models
.ForeignKey(ref
.Implantation
,
1514 db_column
='implantation',
1515 related_name
='%(app_label)s_valeur_point')
1517 annee
= models
.IntegerField()
1520 ordering
= ['-annee', 'implantation__nom']
1522 verbose_name
= u
"Valeur du point"
1523 verbose_name_plural
= u
"Valeurs du point"
1525 def __unicode__(self
):
1526 return u
'%s %s %s [%s] %s' % (
1527 self
.devise
.code
, self
.annee
, self
.valeur
,
1528 self
.implantation
.nom_court
, self
.devise
.nom
1532 class ValeurPoint(ValeurPoint_
):
1533 __doc__
= ValeurPoint_
.__doc__
1535 reversion
.register(ValeurPoint
, format
='xml')
1538 class Devise(AUFMetadata
):
1542 objects
= DeviseManager()
1544 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1545 code
= models
.CharField(max_length
=10, unique
=True)
1546 nom
= models
.CharField(max_length
=255)
1550 verbose_name
= u
"Devise"
1551 verbose_name_plural
= u
"Devises"
1553 def __unicode__(self
):
1554 return u
'%s - %s' % (self
.code
, self
.nom
)
1556 reversion
.register(Devise
, format
='xml')
1559 class TypeContrat(AUFMetadata
):
1563 nom
= models
.CharField(max_length
=255)
1564 nom_long
= models
.CharField(max_length
=255)
1568 verbose_name
= u
"Type de contrat"
1569 verbose_name_plural
= u
"Types de contrat"
1571 def __unicode__(self
):
1572 return u
'%s' % (self
.nom
)
1574 reversion
.register(TypeContrat
, format
='xml')
1579 class ResponsableImplantationProxy(ref
.Implantation
):
1586 verbose_name
= u
"Responsable d'implantation"
1587 verbose_name_plural
= u
"Responsables d'implantation"
1590 class ResponsableImplantation(models
.Model
):
1592 Le responsable d'une implantation.
1593 Anciennement géré sur le Dossier du responsable.
1595 employe
= models
.ForeignKey(
1596 'Employe', db_column
='employe', related_name
='+', null
=True,
1599 implantation
= models
.OneToOneField(
1600 "ResponsableImplantationProxy", db_column
='implantation',
1601 related_name
='responsable', unique
=True
1604 def __unicode__(self
):
1605 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1608 ordering
= ['implantation__nom']
1609 verbose_name
= "Responsable d'implantation"
1610 verbose_name_plural
= "Responsables d'implantation"
1612 reversion
.register(ResponsableImplantation
, format
='xml')