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
.references
import models
as ref
11 from django
.core
.files
.storage
import FileSystemStorage
12 from django
.db
import models
13 from django
.db
.models
import Q
14 from django
.conf
import settings
16 from project
.rh
.change_list
import \
17 RechercheTemporelle
, KEY_STATUT
, STATUT_ACTIF
, STATUT_INACTIF
, \
19 from project
.rh
.managers
import \
20 PosteManager
, DossierManager
, DossierComparaisonManager
, \
21 PosteComparaisonManager
, TypeRemunerationManager
, EmployeManager
22 from project
.rh
.validators
import validate_date_passee
26 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
27 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
28 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
29 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
= \
30 "Saisir le nombre d'heure de travail à temps complet (100%), " \
31 "sans tenir compte du régime de travail"
34 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
35 base_url
=settings
.PRIVE_MEDIA_URL
)
38 def poste_piece_dispatch(instance
, filename
):
39 path
= "%s/poste/%s/%s" % (
40 instance
._meta
.app_label
, instance
.poste_id
, filename
45 def dossier_piece_dispatch(instance
, filename
):
46 path
= "%s/dossier/%s/%s" % (
47 instance
._meta
.app_label
, instance
.dossier_id
, filename
52 def employe_piece_dispatch(instance
, filename
):
53 path
= "%s/employe/%s/%s" % (
54 instance
._meta
.app_label
, instance
.employe_id
, filename
59 def contrat_dispatch(instance
, filename
):
60 path
= "%s/contrat/%s/%s" % (
61 instance
._meta
.app_label
, instance
.dossier_id
, filename
66 class ArchivableManager(models
.Manager
):
68 def get_query_set(self
):
69 return super(ArchivableManager
, self
).get_query_set() \
70 .filter(archive
=False)
73 class Archivable(models
.Model
):
74 archive
= models
.BooleanField(u
'archivé', default
=False)
76 objects
= ArchivableManager()
77 avec_archives
= models
.Manager()
83 class DevisableMixin(object):
85 def get_annee_pour_taux_devise(self
):
86 return datetime
.datetime
.now().year
88 def taux_devise(self
, devise
=None):
94 if devise
.code
== "EUR":
97 annee
= self
.get_annee_pour_taux_devise()
100 for tc
in TauxChange
.objects
.filter(devise
=devise
, annee
=annee
)
106 u
"Pas de taux pour %s en %s" % (devise
.code
, annee
)
110 raise Exception(u
"Il existe plusieurs taux de %s en %s" %
111 (devise
.code
, annee
))
115 def montant_euros(self
):
117 taux
= self
.taux_devise()
122 return int(round(float(self
.montant
) * float(taux
), 2))
125 class Commentaire(models
.Model
):
126 texte
= models
.TextField()
127 owner
= models
.ForeignKey(
128 'auth.User', db_column
='owner', related_name
='+',
129 verbose_name
=u
"Commentaire de"
131 date_creation
= models
.DateTimeField(
132 u
'date', auto_now_add
=True, blank
=True, null
=True
137 ordering
= ['-date_creation']
139 def __unicode__(self
):
140 return u
'%s' % (self
.texte
)
145 POSTE_APPEL_CHOICES
= (
146 ('interne', 'Interne'),
147 ('externe', 'Externe'),
151 class Poste_(models
.Model
):
153 Un Poste est un emploi (job) à combler dans une implantation.
154 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
155 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
158 objects
= PosteManager()
161 nom
= models
.CharField(u
"Titre du poste", max_length
=255)
162 nom_feminin
= models
.CharField(
163 u
"Titre du poste (au féminin)", max_length
=255, null
=True
165 implantation
= models
.ForeignKey(
167 help_text
=u
"Taper le nom de l'implantation ou sa région",
168 db_column
='implantation', related_name
='+'
170 type_poste
= models
.ForeignKey(
171 'TypePoste', db_column
='type_poste',
172 help_text
=u
"Taper le nom du type de poste", related_name
='+',
173 null
=True, verbose_name
=u
"type de poste"
175 service
= models
.ForeignKey(
176 'Service', db_column
='service', related_name
='%(app_label)s_postes',
177 verbose_name
=u
"direction/service/pôle support", null
=True
179 responsable
= models
.ForeignKey(
180 'Poste', db_column
='responsable',
181 related_name
='+', null
=True,
182 help_text
=u
"Taper le nom du poste ou du type de poste",
183 verbose_name
=u
"Poste du responsable"
187 regime_travail
= models
.DecimalField(
188 u
"temps de travail", max_digits
=12, decimal_places
=2,
189 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
190 help_text
="% du temps complet"
192 regime_travail_nb_heure_semaine
= models
.DecimalField(
193 u
"nb. heures par semaine", max_digits
=12, decimal_places
=2,
194 null
=True, default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
195 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
199 local
= models
.NullBooleanField(
200 u
"local", default
=True, null
=True, blank
=True
202 expatrie
= models
.NullBooleanField(
203 u
"expatrié", default
=False, null
=True, blank
=True
205 mise_a_disposition
= models
.NullBooleanField(
206 u
"mise à disposition", null
=True, default
=False
208 appel
= models
.CharField(
209 u
"Appel à candidature", max_length
=10, null
=True,
210 choices
=POSTE_APPEL_CHOICES
, default
='interne'
214 classement_min
= models
.ForeignKey(
215 'Classement', db_column
='classement_min', related_name
='+',
216 null
=True, blank
=True
218 classement_max
= models
.ForeignKey(
219 'Classement', db_column
='classement_max', related_name
='+',
220 null
=True, blank
=True
222 valeur_point_min
= models
.ForeignKey(
224 help_text
=u
"Taper le code ou le nom de l'implantation",
225 db_column
='valeur_point_min', related_name
='+', null
=True,
228 valeur_point_max
= models
.ForeignKey(
230 help_text
=u
"Taper le code ou le nom de l'implantation",
231 db_column
='valeur_point_max', related_name
='+', null
=True,
234 devise_min
= models
.ForeignKey(
235 'Devise', db_column
='devise_min', null
=True, related_name
='+'
237 devise_max
= models
.ForeignKey(
238 'Devise', db_column
='devise_max', null
=True, related_name
='+'
240 salaire_min
= models
.DecimalField(
241 max_digits
=12, decimal_places
=2, null
=True, blank
=True
243 salaire_max
= models
.DecimalField(
244 max_digits
=12, decimal_places
=2, null
=True, blank
=True
246 indemn_min
= models
.DecimalField(
247 max_digits
=12, decimal_places
=2, null
=True, blank
=True
249 indemn_max
= models
.DecimalField(
250 max_digits
=12, decimal_places
=2, null
=True, blank
=True
252 autre_min
= models
.DecimalField(
253 max_digits
=12, decimal_places
=2, null
=True, blank
=True
255 autre_max
= models
.DecimalField(
256 max_digits
=12, decimal_places
=2, null
=True, blank
=True
259 # Comparatifs de rémunération
260 devise_comparaison
= models
.ForeignKey(
261 'Devise', null
=True, blank
=True, db_column
='devise_comparaison',
264 comp_locale_min
= models
.DecimalField(
265 max_digits
=12, decimal_places
=2, null
=True, blank
=True
267 comp_locale_max
= models
.DecimalField(
268 max_digits
=12, decimal_places
=2, null
=True, blank
=True
270 comp_universite_min
= models
.DecimalField(
271 max_digits
=12, decimal_places
=2, null
=True, blank
=True
273 comp_universite_max
= models
.DecimalField(
274 max_digits
=12, decimal_places
=2, null
=True, blank
=True
276 comp_fonctionpub_min
= models
.DecimalField(
277 max_digits
=12, decimal_places
=2, null
=True, blank
=True
279 comp_fonctionpub_max
= models
.DecimalField(
280 max_digits
=12, decimal_places
=2, null
=True, blank
=True
282 comp_ong_min
= models
.DecimalField(
283 max_digits
=12, decimal_places
=2, null
=True, blank
=True
285 comp_ong_max
= models
.DecimalField(
286 max_digits
=12, decimal_places
=2, null
=True, blank
=True
288 comp_autre_min
= models
.DecimalField(
289 max_digits
=12, decimal_places
=2, null
=True, blank
=True
291 comp_autre_max
= models
.DecimalField(
292 max_digits
=12, decimal_places
=2, null
=True, blank
=True
296 justification
= models
.TextField(null
=True, blank
=True)
299 date_debut
= models
.DateField(
300 u
"date de début", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True
302 date_fin
= models
.DateField(
303 u
"date de fin", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True
308 ordering
= ['implantation__nom', 'nom']
309 verbose_name
= u
"Poste"
310 verbose_name_plural
= u
"Postes"
313 def __unicode__(self
):
314 representation
= u
'%s - %s [%s]' % (
315 self
.implantation
, self
.nom
, self
.id
317 return representation
319 prefix_implantation
= "implantation__region"
321 def get_regions(self
):
322 return [self
.implantation
.region
]
324 def get_devise(self
):
325 vp
= ValeurPoint
.objects
.filter(
326 implantation
=self
.implantation
, devise__archive
=False
331 return Devise
.objects
.get(code
='EUR')
335 __doc__
= Poste_
.__doc__
337 # meta dématérialisation : pour permettre le filtrage
338 vacant
= models
.NullBooleanField(u
"vacant", null
=True, blank
=True)
342 if self
.occupe_par():
346 def occupe_par(self
):
348 Retourne la liste d'employé occupant ce poste.
349 Généralement, retourne une liste d'un élément.
350 Si poste inoccupé, retourne liste vide.
351 UTILISE pour mettre a jour le flag vacant
355 for d
in self
.rh_dossiers
.exclude(date_fin__lt
=date
.today())
358 reversion
.register(Poste
, format
='xml', follow
=[
359 'rh_financements', 'rh_pieces', 'rh_comparaisons_internes',
364 POSTE_FINANCEMENT_CHOICES
= (
365 ('A', 'A - Frais de personnel'),
366 ('B', 'B - Projet(s)-Titre(s)'),
371 class PosteFinancement_(models
.Model
):
373 Pour un Poste, structure d'informations décrivant comment on prévoit
376 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
377 pourcentage
= models
.DecimalField(
378 max_digits
=12, decimal_places
=2,
379 help_text
="ex.: 33.33 % (décimale avec point)"
381 commentaire
= models
.TextField(
382 help_text
="Spécifiez la source de financement."
389 def __unicode__(self
):
390 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
393 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
396 class PosteFinancement(PosteFinancement_
):
397 poste
= models
.ForeignKey(
398 Poste
, db_column
='poste', related_name
='rh_financements'
401 reversion
.register(PosteFinancement
, format
='xml')
404 class PostePiece_(models
.Model
):
406 Documents relatifs au Poste.
407 Ex.: Description de poste
409 nom
= models
.CharField(u
"Nom", max_length
=255)
410 fichier
= models
.FileField(
411 u
"Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
418 def __unicode__(self
):
419 return u
'%s' % (self
.nom
)
422 class PostePiece(PostePiece_
):
423 poste
= models
.ForeignKey(
424 Poste
, db_column
='poste', related_name
='rh_pieces'
427 reversion
.register(PostePiece
, format
='xml')
430 class PosteComparaison_(models
.Model
, DevisableMixin
):
432 De la même manière qu'un dossier, un poste peut-être comparé à un autre
435 objects
= PosteComparaisonManager()
437 implantation
= models
.ForeignKey(
438 ref
.Implantation
, null
=True, blank
=True, related_name
="+"
440 nom
= models
.CharField(u
"Poste", max_length
=255, null
=True, blank
=True)
441 montant
= models
.IntegerField(null
=True)
442 devise
= models
.ForeignKey(
443 "Devise", related_name
='+', null
=True, blank
=True
449 def __unicode__(self
):
453 class PosteComparaison(PosteComparaison_
):
454 poste
= models
.ForeignKey(
455 Poste
, related_name
='rh_comparaisons_internes'
458 reversion
.register(PosteComparaison
, format
='xml')
461 class PosteCommentaire(Commentaire
):
462 poste
= models
.ForeignKey(
463 Poste
, db_column
='poste', related_name
='commentaires'
466 reversion
.register(PosteCommentaire
, format
='xml')
471 class Employe(models
.Model
):
473 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
474 Dossiers qu'il occupe ou a occupé de Postes.
476 Cette classe aurait pu avantageusement s'appeler Personne car la notion
477 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
480 objects
= EmployeManager()
483 nom
= models
.CharField(max_length
=255)
484 prenom
= models
.CharField(u
"prénom", max_length
=255)
485 nom_affichage
= models
.CharField(
486 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
488 nationalite
= models
.ForeignKey(
489 ref
.Pays
, to_field
='code', db_column
='nationalite',
490 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
491 blank
=True, null
=True
493 date_naissance
= models
.DateField(
494 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
495 validators
=[validate_date_passee
], null
=True, blank
=True
497 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
500 situation_famille
= models
.CharField(
501 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
502 null
=True, blank
=True
504 date_entree
= models
.DateField(
505 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
510 tel_domicile
= models
.CharField(
511 u
"tél. domicile", max_length
=255, null
=True, blank
=True
513 tel_cellulaire
= models
.CharField(
514 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
516 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
517 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
518 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
519 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
520 pays
= models
.ForeignKey(
521 ref
.Pays
, to_field
='code', db_column
='pays',
522 related_name
='employes', null
=True, blank
=True
524 courriel_perso
= models
.EmailField(
525 u
'adresse courriel personnelle', blank
=True
528 # meta dématérialisation : pour permettre le filtrage
529 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
532 ordering
= ['nom', 'prenom']
533 verbose_name
= u
"Employé"
534 verbose_name_plural
= u
"Employés"
536 def __unicode__(self
):
537 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
541 if self
.genre
.upper() == u
'M':
543 elif self
.genre
.upper() == u
'F':
549 Retourne l'URL du service retournant la photo de l'Employe.
550 Équivalent reverse url 'rh_photo' avec id en param.
552 from django
.core
.urlresolvers
import reverse
553 return reverse('rh_photo', kwargs
={'id': self
.id})
555 def dossiers_passes(self
):
556 params
= {KEY_STATUT
: STATUT_INACTIF
, }
557 search
= RechercheTemporelle(params
, self
.__class__
)
558 search
.purge_params(params
)
559 q
= search
.get_q_temporel(self
.rh_dossiers
)
560 return self
.rh_dossiers
.filter(q
)
562 def dossiers_futurs(self
):
563 params
= {KEY_STATUT
: STATUT_FUTUR
, }
564 search
= RechercheTemporelle(params
, self
.__class__
)
565 search
.purge_params(params
)
566 q
= search
.get_q_temporel(self
.rh_dossiers
)
567 return self
.rh_dossiers
.filter(q
)
569 def dossiers_encours(self
):
570 params
= {KEY_STATUT
: STATUT_ACTIF
, }
571 search
= RechercheTemporelle(params
, self
.__class__
)
572 search
.purge_params(params
)
573 q
= search
.get_q_temporel(self
.rh_dossiers
)
574 return self
.rh_dossiers
.filter(q
)
576 def postes_encours(self
):
577 postes_encours
= set()
578 for d
in self
.dossiers_encours():
579 postes_encours
.add(d
.poste
)
580 return postes_encours
582 def poste_principal(self
):
584 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
586 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
588 poste
= Poste
.objects
.none()
590 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
595 prefix_implantation
= "rh_dossiers__poste__implantation__region"
597 def get_regions(self
):
599 for d
in self
.dossiers
.all():
600 regions
.append(d
.poste
.implantation
.region
)
603 reversion
.register(Employe
, format
='xml', follow
=[
604 'pieces', 'commentaires', 'ayantdroits'
608 class EmployePiece(models
.Model
):
610 Documents relatifs à un employé.
613 employe
= models
.ForeignKey(
614 'Employe', db_column
='employe', related_name
="pieces",
615 verbose_name
=u
"employé"
617 nom
= models
.CharField(max_length
=255)
618 fichier
= models
.FileField(
619 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
624 verbose_name
= u
"Employé pièce"
625 verbose_name_plural
= u
"Employé pièces"
627 def __unicode__(self
):
628 return u
'%s' % (self
.nom
)
630 reversion
.register(EmployePiece
, format
='xml')
633 class EmployeCommentaire(Commentaire
):
634 employe
= models
.ForeignKey(
635 'Employe', db_column
='employe', related_name
='commentaires'
639 verbose_name
= u
"Employé commentaire"
640 verbose_name_plural
= u
"Employé commentaires"
642 reversion
.register(EmployeCommentaire
, format
='xml')
645 LIEN_PARENTE_CHOICES
= (
646 ('Conjoint', 'Conjoint'),
647 ('Conjointe', 'Conjointe'),
653 class AyantDroit(models
.Model
):
655 Personne en relation avec un Employe.
658 nom
= models
.CharField(max_length
=255)
659 prenom
= models
.CharField(u
"prénom", max_length
=255)
660 nom_affichage
= models
.CharField(
661 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
663 nationalite
= models
.ForeignKey(
664 ref
.Pays
, to_field
='code', db_column
='nationalite',
665 related_name
='ayantdroits_nationalite',
666 verbose_name
=u
"nationalité", null
=True, blank
=True
668 date_naissance
= models
.DateField(
669 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
670 validators
=[validate_date_passee
], null
=True, blank
=True
672 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
675 employe
= models
.ForeignKey(
676 'Employe', db_column
='employe', related_name
='ayantdroits',
677 verbose_name
=u
"Employé"
679 lien_parente
= models
.CharField(
680 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
681 null
=True, blank
=True
686 verbose_name
= u
"Ayant droit"
687 verbose_name_plural
= u
"Ayants droit"
689 def __unicode__(self
):
690 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
692 prefix_implantation
= "employe__dossiers__poste__implantation__region"
694 def get_regions(self
):
696 for d
in self
.employe
.dossiers
.all():
697 regions
.append(d
.poste
.implantation
.region
)
700 reversion
.register(AyantDroit
, format
='xml', follow
=['commentaires'])
703 class AyantDroitCommentaire(Commentaire
):
704 ayant_droit
= models
.ForeignKey(
705 'AyantDroit', db_column
='ayant_droit', related_name
='commentaires'
708 reversion
.register(AyantDroitCommentaire
, format
='xml')
713 STATUT_RESIDENCE_CHOICES
= (
715 ('expat', 'Expatrié'),
718 COMPTE_COMPTA_CHOICES
= (
725 class Dossier_(models
.Model
, DevisableMixin
):
727 Le Dossier regroupe les informations relatives à l'occupation
728 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
731 Plusieurs Contrats peuvent être associés au Dossier.
732 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
733 lequel aucun Dossier n'existe est un poste vacant.
736 objects
= DossierManager()
739 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
740 organisme_bstg
= models
.ForeignKey(
741 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
742 verbose_name
=u
"organisme",
744 u
"Si détaché (DET) ou mis à disposition (MAD), "
745 u
"préciser l'organisme."
746 ), null
=True, blank
=True
750 remplacement
= models
.BooleanField(default
=False)
751 remplacement_de
= models
.ForeignKey(
752 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
753 null
=True, blank
=True
755 statut_residence
= models
.CharField(
756 u
"statut", max_length
=10, default
='local', null
=True,
757 choices
=STATUT_RESIDENCE_CHOICES
761 classement
= models
.ForeignKey(
762 'Classement', db_column
='classement', related_name
='+', null
=True,
765 regime_travail
= models
.DecimalField(
766 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
767 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
769 regime_travail_nb_heure_semaine
= models
.DecimalField(
770 u
"nb. heures par semaine", max_digits
=12,
771 decimal_places
=2, null
=True,
772 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
773 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
776 # Occupation du Poste par cet Employe (anciennement "mandat")
777 date_debut
= models
.DateField(u
"date de début d'occupation de poste")
778 date_fin
= models
.DateField(
779 u
"Date de fin d'occupation de poste", null
=True, blank
=True
787 ordering
= ['employe__nom', ]
788 verbose_name
= u
"Dossier"
789 verbose_name_plural
= "Dossiers"
791 def salaire_theorique(self
):
792 annee
= date
.today().year
793 coeff
= self
.classement
.coefficient
794 implantation
= self
.poste
.implantation
795 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
797 montant
= coeff
* point
.valeur
798 devise
= point
.devise
799 return {'montant': montant
, 'devise': devise
}
801 def __unicode__(self
):
802 poste
= self
.poste
.nom
803 if self
.employe
.genre
== 'F':
804 poste
= self
.poste
.nom_feminin
805 return u
'%s - %s' % (self
.employe
, poste
)
807 prefix_implantation
= "poste__implantation__region"
809 def get_regions(self
):
810 return [self
.poste
.implantation
.region
]
812 def remunerations(self
):
813 key
= "%s_remunerations" % self
._meta
.app_label
814 remunerations
= getattr(self
, key
)
815 return remunerations
.all().order_by('-date_debut')
817 def remunerations_en_cours(self
):
818 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
819 return self
.remunerations().all().filter(q
).order_by('date_debut')
821 def get_salaire(self
):
823 return [r
for r
in self
.remunerations().order_by('-date_debut')
824 if r
.type_id
== 1][0]
828 def get_salaire_euros(self
):
829 tx
= self
.taux_devise()
830 return (float)(tx
) * (float)(self
.salaire
)
832 def get_remunerations_brutes(self
):
836 4 Indemnité d'expatriation
837 5 Indemnité pour frais
838 6 Indemnité de logement
839 7 Indemnité de fonction
840 8 Indemnité de responsabilité
841 9 Indemnité de transport
842 10 Indemnité compensatrice
843 11 Indemnité de subsistance
844 12 Indemnité différentielle
845 13 Prime d'installation
848 16 Indemnité de départ
849 18 Prime de 13ième mois
852 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
853 return [r
for r
in self
.remunerations_en_cours().all()
856 def get_charges_salariales(self
):
858 20 Charges salariales ?
861 return [r
for r
in self
.remunerations_en_cours().all()
864 def get_charges_patronales(self
):
866 17 Charges patronales
869 return [r
for r
in self
.remunerations_en_cours().all()
872 def get_remunerations_tierces(self
):
876 return [r
for r
in self
.remunerations_en_cours().all()
877 if r
.type_id
in (2,)]
881 def get_total_local_charges_salariales(self
):
882 devise
= self
.poste
.get_devise()
884 for r
in self
.get_charges_salariales():
885 if r
.devise
!= devise
:
887 total
+= float(r
.montant
)
890 def get_total_local_charges_patronales(self
):
891 devise
= self
.poste
.get_devise()
893 for r
in self
.get_charges_patronales():
894 if r
.devise
!= devise
:
896 total
+= float(r
.montant
)
899 def get_local_salaire_brut(self
):
901 somme des rémuérations brutes
903 devise
= self
.poste
.get_devise()
905 for r
in self
.get_remunerations_brutes():
906 if r
.devise
!= devise
:
908 total
+= float(r
.montant
)
911 def get_local_salaire_net(self
):
913 salaire brut - charges salariales
915 devise
= self
.poste
.get_devise()
917 for r
in self
.get_charges_salariales():
918 if r
.devise
!= devise
:
920 total_charges
+= float(r
.montant
)
921 return self
.get_local_salaire_brut() - total_charges
923 def get_local_couts_auf(self
):
925 salaire net + charges patronales
927 devise
= self
.poste
.get_devise()
929 for r
in self
.get_charges_patronales():
930 if r
.devise
!= devise
:
932 total_charges
+= float(r
.montant
)
933 return self
.get_local_salaire_net() + total_charges
935 def get_total_local_remunerations_tierces(self
):
936 devise
= self
.poste
.get_devise()
938 for r
in self
.get_remunerations_tierces():
939 if r
.devise
!= devise
:
941 total
+= float(r
.montant
)
946 def get_total_charges_salariales(self
):
948 for r
in self
.get_charges_salariales():
949 total
+= r
.montant_euros()
952 def get_total_charges_patronales(self
):
954 for r
in self
.get_charges_patronales():
955 total
+= r
.montant_euros()
958 def get_salaire_brut(self
):
960 somme des rémuérations brutes
963 for r
in self
.get_remunerations_brutes():
964 total
+= r
.montant_euros()
967 def get_salaire_net(self
):
969 salaire brut - charges salariales
972 for r
in self
.get_charges_salariales():
973 total_charges
+= r
.montant_euros()
974 return self
.get_salaire_brut() - total_charges
976 def get_couts_auf(self
):
978 salaire net + charges patronales
981 for r
in self
.get_charges_patronales():
982 total_charges
+= r
.montant_euros()
983 return self
.get_salaire_net() + total_charges
985 def get_total_remunerations_tierces(self
):
987 for r
in self
.get_remunerations_tierces():
988 total
+= r
.montant_euros()
993 return (self
.date_debut
is None or self
.date_debut
<= today
) \
994 and (self
.date_fin
is None or self
.date_fin
>= today
) \
995 and not (self
.date_fin
is None and self
.date_debut
is None)
998 class Dossier(Dossier_
):
999 __doc__
= Dossier_
.__doc__
1000 poste
= models
.ForeignKey(
1001 Poste
, db_column
='poste', related_name
='rh_dossiers',
1002 help_text
=u
"Taper le nom du poste ou du type de poste",
1004 employe
= models
.ForeignKey(
1005 'Employe', db_column
='employe',
1006 help_text
=u
"Taper le nom de l'employé",
1007 related_name
='rh_dossiers', verbose_name
=u
"employé"
1009 principal
= models
.BooleanField(
1010 u
"dossier principal", default
=True,
1012 u
"Ce dossier est pour le principal poste occupé par l'employé"
1016 reversion
.register(Dossier
, format
='xml', follow
=[
1017 'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
1018 'rh_contrats', 'commentaires'
1022 class DossierPiece_(models
.Model
):
1024 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1025 Ex.: Lettre de motivation.
1027 nom
= models
.CharField(max_length
=255)
1028 fichier
= models
.FileField(
1029 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
1036 def __unicode__(self
):
1037 return u
'%s' % (self
.nom
)
1040 class DossierPiece(DossierPiece_
):
1041 dossier
= models
.ForeignKey(
1042 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
1045 reversion
.register(DossierPiece
, format
='xml')
1048 class DossierCommentaire(Commentaire
):
1049 dossier
= models
.ForeignKey(
1050 Dossier
, db_column
='dossier', related_name
='commentaires'
1053 reversion
.register(DossierCommentaire
, format
='xml')
1056 class DossierComparaison_(models
.Model
, DevisableMixin
):
1058 Photo d'une comparaison salariale au moment de l'embauche.
1060 objects
= DossierComparaisonManager()
1062 implantation
= models
.ForeignKey(
1063 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1065 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1066 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1067 montant
= models
.IntegerField(null
=True)
1068 devise
= models
.ForeignKey(
1069 'Devise', related_name
='+', null
=True, blank
=True
1075 def __unicode__(self
):
1076 return "%s (%s)" % (self
.poste
, self
.personne
)
1079 class DossierComparaison(DossierComparaison_
):
1080 dossier
= models
.ForeignKey(
1081 Dossier
, related_name
='rh_comparaisons'
1084 reversion
.register(DossierComparaison
, format
='xml')
1089 class RemunerationMixin(models
.Model
):
1092 type = models
.ForeignKey(
1093 'TypeRemuneration', db_column
='type', related_name
='+',
1094 verbose_name
=u
"type de rémunération"
1096 type_revalorisation
= models
.ForeignKey(
1097 'TypeRevalorisation', db_column
='type_revalorisation',
1098 related_name
='+', verbose_name
=u
"type de revalorisation",
1099 null
=True, blank
=True
1101 montant
= models
.DecimalField(
1102 null
=True, blank
=True, max_digits
=12, decimal_places
=2
1103 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1104 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1106 # commentaire = precision
1107 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1109 # date_debut = anciennement date_effectif
1110 date_debut
= models
.DateField(u
"date de début", null
=True, blank
=True)
1111 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1115 ordering
= ['type__nom', '-date_fin']
1117 def __unicode__(self
):
1118 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1121 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1123 Structure de rémunération (données budgétaires) en situation normale
1124 pour un Dossier. Si un Evenement existe, utiliser la structure de
1125 rémunération EvenementRemuneration de cet événement.
1128 def montant_mois(self
):
1129 return round(self
.montant
/ 12, 2)
1131 def montant_avec_regime(self
):
1132 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1134 def montant_euro_mois(self
):
1135 return round(self
.montant_euros() / 12, 2)
1137 def __unicode__(self
):
1139 devise
= self
.devise
.code
1142 return "%s %s" % (self
.montant
, devise
)
1146 verbose_name
= u
"Rémunération"
1147 verbose_name_plural
= u
"Rémunérations"
1150 class Remuneration(Remuneration_
):
1151 dossier
= models
.ForeignKey(
1152 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1155 reversion
.register(Remuneration
, format
='xml')
1160 class ContratManager(models
.Manager
):
1162 def get_query_set(self
):
1163 return super(ContratManager
, self
).get_query_set() \
1164 .select_related('dossier', 'dossier__poste')
1167 class Contrat_(models
.Model
):
1169 Document juridique qui encadre la relation de travail d'un Employe
1170 pour un Poste particulier. Pour un Dossier (qui documente cette
1171 relation de travail) plusieurs contrats peuvent être associés.
1173 objects
= ContratManager()
1174 type_contrat
= models
.ForeignKey(
1175 'TypeContrat', db_column
='type_contrat',
1176 verbose_name
=u
'type de contrat', related_name
='+'
1178 date_debut
= models
.DateField(u
"date de début")
1179 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1180 fichier
= models
.FileField(
1181 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1187 ordering
= ['dossier__employe__nom']
1188 verbose_name
= u
"Contrat"
1189 verbose_name_plural
= u
"Contrats"
1191 def __unicode__(self
):
1192 return u
'%s - %s' % (self
.dossier
, self
.id)
1195 class Contrat(Contrat_
):
1196 dossier
= models
.ForeignKey(
1197 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1200 reversion
.register(Contrat
, format
='xml')
1205 class CategorieEmploi(models
.Model
):
1207 Catégorie utilisée dans la gestion des Postes.
1208 Catégorie supérieure à TypePoste.
1210 nom
= models
.CharField(max_length
=255)
1214 verbose_name
= u
"catégorie d'emploi"
1215 verbose_name_plural
= u
"catégories d'emploi"
1217 def __unicode__(self
):
1220 reversion
.register(CategorieEmploi
, format
='xml')
1223 class FamilleProfessionnelle(models
.Model
):
1225 Famille professionnelle d'un poste.
1227 nom
= models
.CharField(max_length
=100)
1231 verbose_name
= u
'famille professionnelle'
1232 verbose_name_plural
= u
'familles professionnelles'
1234 def __unicode__(self
):
1237 reversion
.register(FamilleProfessionnelle
, format
='xml')
1240 class TypePoste(models
.Model
):
1244 nom
= models
.CharField(max_length
=255)
1245 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1246 is_responsable
= models
.BooleanField(
1247 u
"poste de responsabilité", default
=False
1249 categorie_emploi
= models
.ForeignKey(
1250 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1251 verbose_name
=u
"catégorie d'emploi"
1253 famille_professionnelle
= models
.ForeignKey(
1254 FamilleProfessionnelle
, related_name
='types_de_poste',
1255 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1260 verbose_name
= u
"Type de poste"
1261 verbose_name_plural
= u
"Types de poste"
1263 def __unicode__(self
):
1264 return u
'%s' % (self
.nom
)
1266 reversion
.register(TypePoste
, format
='xml')
1269 TYPE_PAIEMENT_CHOICES
= (
1270 (u
'Régulier', u
'Régulier'),
1271 (u
'Ponctuel', u
'Ponctuel'),
1274 NATURE_REMUNERATION_CHOICES
= (
1275 (u
'Accessoire', u
'Accessoire'),
1276 (u
'Charges', u
'Charges'),
1277 (u
'Indemnité', u
'Indemnité'),
1278 (u
'RAS', u
'Rémunération autre source'),
1279 (u
'Traitement', u
'Traitement'),
1283 class TypeRemuneration(Archivable
):
1285 Catégorie de Remuneration.
1287 objects
= TypeRemunerationManager()
1289 nom
= models
.CharField(max_length
=255)
1290 type_paiement
= models
.CharField(
1291 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1293 nature_remuneration
= models
.CharField(
1294 u
"nature de la rémunération", max_length
=30,
1295 choices
=NATURE_REMUNERATION_CHOICES
1300 verbose_name
= u
"Type de rémunération"
1301 verbose_name_plural
= u
"Types de rémunération"
1303 def __unicode__(self
):
1306 reversion
.register(TypeRemuneration
, format
='xml')
1309 class TypeRevalorisation(models
.Model
):
1311 Justification du changement de la Remuneration.
1312 (Actuellement utilisé dans aucun traitement informatique.)
1314 nom
= models
.CharField(max_length
=255)
1318 verbose_name
= u
"Type de revalorisation"
1319 verbose_name_plural
= u
"Types de revalorisation"
1321 def __unicode__(self
):
1322 return u
'%s' % (self
.nom
)
1324 reversion
.register(TypeRevalorisation
, format
='xml')
1327 class Service(Archivable
):
1329 Unité administrative où les Postes sont rattachés.
1331 nom
= models
.CharField(max_length
=255)
1335 verbose_name
= u
"service"
1336 verbose_name_plural
= u
"services"
1338 def __unicode__(self
):
1341 reversion
.register(Service
, format
='xml')
1344 TYPE_ORGANISME_CHOICES
= (
1345 ('MAD', 'Mise à disposition'),
1346 ('DET', 'Détachement'),
1350 class OrganismeBstg(models
.Model
):
1352 Organisation d'où provient un Employe mis à disposition (MAD) de
1353 ou détaché (DET) à l'AUF à titre gratuit.
1355 (BSTG = bien et service à titre gratuit.)
1357 nom
= models
.CharField(max_length
=255)
1358 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1359 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1361 related_name
='organismes_bstg',
1362 null
=True, blank
=True)
1365 ordering
= ['type', 'nom']
1366 verbose_name
= u
"Organisme BSTG"
1367 verbose_name_plural
= u
"Organismes BSTG"
1369 def __unicode__(self
):
1370 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1372 prefix_implantation
= "pays__region"
1374 def get_regions(self
):
1375 return [self
.pays
.region
]
1377 reversion
.register(OrganismeBstg
, format
='xml')
1380 class Statut(models
.Model
):
1382 Statut de l'Employe dans le cadre d'un Dossier particulier.
1385 code
= models
.CharField(
1386 max_length
=25, unique
=True,
1388 u
"Saisir un code court mais lisible pour ce statut : "
1389 u
"le code est utilisé pour associer les statuts aux autres "
1390 u
"données tout en demeurant plus lisible qu'un identifiant "
1394 nom
= models
.CharField(max_length
=255)
1398 verbose_name
= u
"Statut d'employé"
1399 verbose_name_plural
= u
"Statuts d'employé"
1401 def __unicode__(self
):
1402 return u
'%s : %s' % (self
.code
, self
.nom
)
1404 reversion
.register(Statut
, format
='xml')
1407 TYPE_CLASSEMENT_CHOICES
= (
1408 ('S', 'S -Soutien'),
1409 ('T', 'T - Technicien'),
1410 ('P', 'P - Professionel'),
1412 ('D', 'D - Direction'),
1413 ('SO', 'SO - Sans objet [expatriés]'),
1414 ('HG', 'HG - Hors grille [direction]'),
1418 class ClassementManager(models
.Manager
):
1420 Ordonner les spcéfiquement les classements.
1422 def get_query_set(self
):
1423 qs
= super(self
.__class__
, self
).get_query_set()
1424 qs
= qs
.extra(select
={
1425 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1427 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1431 class Classement_(models
.Model
):
1433 Éléments de classement de la
1434 "Grille générique de classement hiérarchique".
1436 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1437 classement dans la grille. Le classement donne le coefficient utilisé dans:
1439 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1441 objects
= ClassementManager()
1444 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1445 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1446 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1447 coefficient
= models
.FloatField(u
"coefficient", blank
=True, null
=True)
1450 # annee # au lieu de date_debut et date_fin
1451 commentaire
= models
.TextField(null
=True, blank
=True)
1455 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1456 verbose_name
= u
"Classement"
1457 verbose_name_plural
= u
"Classements"
1459 def __unicode__(self
):
1460 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1463 class Classement(Classement_
):
1464 __doc__
= Classement_
.__doc__
1466 reversion
.register(Classement
, format
='xml')
1469 class TauxChange_(models
.Model
):
1471 Taux de change de la devise vers l'euro (EUR)
1472 pour chaque année budgétaire.
1475 devise
= models
.ForeignKey('Devise', db_column
='devise')
1476 annee
= models
.IntegerField(u
"année")
1477 taux
= models
.FloatField(u
"taux vers l'euro")
1481 ordering
= ['-annee', 'devise__code']
1482 verbose_name
= u
"Taux de change"
1483 verbose_name_plural
= u
"Taux de change"
1485 def __unicode__(self
):
1486 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1489 class TauxChange(TauxChange_
):
1490 __doc__
= TauxChange_
.__doc__
1492 reversion
.register(TauxChange
, format
='xml')
1495 class ValeurPointManager(models
.Manager
):
1497 def get_query_set(self
):
1498 return super(ValeurPointManager
, self
).get_query_set() \
1499 .select_related('devise', 'implantation')
1502 class ValeurPoint_(models
.Model
):
1504 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1505 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1506 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1508 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1511 objects
= models
.Manager()
1512 actuelles
= ValeurPointManager()
1514 valeur
= models
.FloatField(null
=True)
1515 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1516 implantation
= models
.ForeignKey(ref
.Implantation
,
1517 db_column
='implantation',
1518 related_name
='%(app_label)s_valeur_point')
1520 annee
= models
.IntegerField()
1523 ordering
= ['-annee', 'implantation__nom']
1525 verbose_name
= u
"Valeur du point"
1526 verbose_name_plural
= u
"Valeurs du point"
1528 def __unicode__(self
):
1529 return u
'%s %s %s [%s] %s' % (
1530 self
.devise
.code
, self
.annee
, self
.valeur
,
1531 self
.implantation
.nom_court
, self
.devise
.nom
1535 class ValeurPoint(ValeurPoint_
):
1536 __doc__
= ValeurPoint_
.__doc__
1538 reversion
.register(ValeurPoint
, format
='xml')
1541 class Devise(Archivable
):
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(models
.Model
):
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')