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
, EmployeManager
, \
21 DossierComparaisonManager
, \
22 PosteComparaisonManager
, DeviseManager
, ServiceManager
, \
23 TypeRemunerationManager
24 from project
.rh
.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(models
.Model
):
111 texte
= models
.TextField()
112 owner
= models
.ForeignKey(
113 'auth.User', db_column
='owner', related_name
='+',
114 verbose_name
=u
"Commentaire de"
116 date_creation
= models
.DateTimeField(
117 u
'date', auto_now_add
=True, blank
=True, null
=True
122 ordering
= ['-date_creation']
124 def __unicode__(self
):
125 return u
'%s' % (self
.texte
)
130 POSTE_APPEL_CHOICES
= (
131 ('interne', 'Interne'),
132 ('externe', 'Externe'),
136 class Poste_(models
.Model
):
138 Un Poste est un emploi (job) à combler dans une implantation.
139 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
140 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
143 objects
= PosteManager()
146 nom
= models
.CharField(u
"Titre du poste", max_length
=255)
147 nom_feminin
= models
.CharField(
148 u
"Titre du poste (au féminin)", max_length
=255, null
=True
150 implantation
= models
.ForeignKey(
152 help_text
=u
"Taper le nom de l'implantation ou sa région",
153 db_column
='implantation', related_name
='+'
155 type_poste
= models
.ForeignKey(
156 'TypePoste', db_column
='type_poste',
157 help_text
=u
"Taper le nom du type de poste", related_name
='+',
158 null
=True, verbose_name
=u
"type de poste"
160 service
= models
.ForeignKey(
161 'Service', db_column
='service', related_name
='%(app_label)s_postes',
162 verbose_name
=u
"direction/service/pôle support", null
=True
164 responsable
= models
.ForeignKey(
165 'Poste', db_column
='responsable',
166 related_name
='+', null
=True,
167 help_text
=u
"Taper le nom du poste ou du type de poste",
168 verbose_name
=u
"Poste du responsable"
172 regime_travail
= models
.DecimalField(
173 u
"temps de travail", max_digits
=12, decimal_places
=2,
174 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
175 help_text
="% du temps complet"
177 regime_travail_nb_heure_semaine
= models
.DecimalField(
178 u
"nb. heures par semaine", max_digits
=12, decimal_places
=2,
179 null
=True, default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
180 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
184 local
= models
.NullBooleanField(
185 u
"local", default
=True, null
=True, blank
=True
187 expatrie
= models
.NullBooleanField(
188 u
"expatrié", default
=False, null
=True, blank
=True
190 mise_a_disposition
= models
.NullBooleanField(
191 u
"mise à disposition", null
=True, default
=False
193 appel
= models
.CharField(
194 u
"Appel à candidature", max_length
=10, null
=True,
195 choices
=POSTE_APPEL_CHOICES
, default
='interne'
199 classement_min
= models
.ForeignKey(
200 'Classement', db_column
='classement_min', related_name
='+',
201 null
=True, blank
=True
203 classement_max
= models
.ForeignKey(
204 'Classement', db_column
='classement_max', related_name
='+',
205 null
=True, blank
=True
207 valeur_point_min
= models
.ForeignKey(
209 help_text
=u
"Taper le code ou le nom de l'implantation",
210 db_column
='valeur_point_min', related_name
='+', null
=True,
213 valeur_point_max
= models
.ForeignKey(
215 help_text
=u
"Taper le code ou le nom de l'implantation",
216 db_column
='valeur_point_max', related_name
='+', null
=True,
219 devise_min
= models
.ForeignKey(
220 'Devise', db_column
='devise_min', null
=True, related_name
='+'
222 devise_max
= models
.ForeignKey(
223 'Devise', db_column
='devise_max', null
=True, related_name
='+'
225 salaire_min
= models
.DecimalField(
226 max_digits
=12, decimal_places
=2, null
=True, blank
=True
228 salaire_max
= models
.DecimalField(
229 max_digits
=12, decimal_places
=2, null
=True, blank
=True
231 indemn_min
= models
.DecimalField(
232 max_digits
=12, decimal_places
=2, null
=True, blank
=True
234 indemn_max
= models
.DecimalField(
235 max_digits
=12, decimal_places
=2, null
=True, blank
=True
237 autre_min
= models
.DecimalField(
238 max_digits
=12, decimal_places
=2, null
=True, blank
=True
240 autre_max
= models
.DecimalField(
241 max_digits
=12, decimal_places
=2, null
=True, blank
=True
244 # Comparatifs de rémunération
245 devise_comparaison
= models
.ForeignKey(
246 'Devise', null
=True, blank
=True, db_column
='devise_comparaison',
249 comp_locale_min
= models
.DecimalField(
250 max_digits
=12, decimal_places
=2, null
=True, blank
=True
252 comp_locale_max
= models
.DecimalField(
253 max_digits
=12, decimal_places
=2, null
=True, blank
=True
255 comp_universite_min
= models
.DecimalField(
256 max_digits
=12, decimal_places
=2, null
=True, blank
=True
258 comp_universite_max
= models
.DecimalField(
259 max_digits
=12, decimal_places
=2, null
=True, blank
=True
261 comp_fonctionpub_min
= models
.DecimalField(
262 max_digits
=12, decimal_places
=2, null
=True, blank
=True
264 comp_fonctionpub_max
= models
.DecimalField(
265 max_digits
=12, decimal_places
=2, null
=True, blank
=True
267 comp_ong_min
= models
.DecimalField(
268 max_digits
=12, decimal_places
=2, null
=True, blank
=True
270 comp_ong_max
= models
.DecimalField(
271 max_digits
=12, decimal_places
=2, null
=True, blank
=True
273 comp_autre_min
= models
.DecimalField(
274 max_digits
=12, decimal_places
=2, null
=True, blank
=True
276 comp_autre_max
= models
.DecimalField(
277 max_digits
=12, decimal_places
=2, null
=True, blank
=True
281 justification
= models
.TextField(null
=True, blank
=True)
284 date_debut
= models
.DateField(
285 u
"date de début", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True
287 date_fin
= models
.DateField(
288 u
"date de fin", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True
293 ordering
= ['implantation__nom', 'nom']
294 verbose_name
= u
"Poste"
295 verbose_name_plural
= u
"Postes"
298 def __unicode__(self
):
299 representation
= u
'%s - %s [%s]' % (
300 self
.implantation
, self
.nom
, self
.id
302 return representation
304 prefix_implantation
= "implantation__region"
306 def get_regions(self
):
307 return [self
.implantation
.region
]
309 def get_devise(self
):
310 vp
= ValeurPoint
.objects
.filter(
311 implantation
=self
.implantation
, devise__archive
=False
316 return Devise
.objects
.get(code
='EUR')
320 __doc__
= Poste_
.__doc__
322 # meta dématérialisation : pour permettre le filtrage
323 vacant
= models
.NullBooleanField(u
"vacant", null
=True, blank
=True)
327 if self
.occupe_par():
331 def occupe_par(self
):
333 Retourne la liste d'employé occupant ce poste.
334 Généralement, retourne une liste d'un élément.
335 Si poste inoccupé, retourne liste vide.
336 UTILISE pour mettre a jour le flag vacant
340 for d
in self
.rh_dossiers
.exclude(date_fin__lt
=date
.today())
343 reversion
.register(Poste
, format
='xml', follow
=[
344 'rh_financements', 'rh_pieces', 'rh_comparaisons_internes',
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_(models
.Model
, 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 reversion
.register(PosteComparaison
, format
='xml')
446 class PosteCommentaire(Commentaire
):
447 poste
= models
.ForeignKey(
448 Poste
, db_column
='poste', related_name
='commentaires'
451 reversion
.register(PosteCommentaire
, format
='xml')
456 class Employe(models
.Model
):
458 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
459 Dossiers qu'il occupe ou a occupé de Postes.
461 Cette classe aurait pu avantageusement s'appeler Personne car la notion
462 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
465 objects
= EmployeManager()
468 nom
= models
.CharField(max_length
=255)
469 prenom
= models
.CharField(u
"prénom", max_length
=255)
470 nom_affichage
= models
.CharField(
471 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
473 nationalite
= models
.ForeignKey(
474 ref
.Pays
, to_field
='code', db_column
='nationalite',
475 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
476 blank
=True, null
=True
478 date_naissance
= models
.DateField(
479 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
480 validators
=[validate_date_passee
], null
=True, blank
=True
482 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
485 situation_famille
= models
.CharField(
486 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
487 null
=True, blank
=True
489 date_entree
= models
.DateField(
490 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
495 tel_domicile
= models
.CharField(
496 u
"tél. domicile", max_length
=255, null
=True, blank
=True
498 tel_cellulaire
= models
.CharField(
499 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
501 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
502 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
503 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
504 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
505 pays
= models
.ForeignKey(
506 ref
.Pays
, to_field
='code', db_column
='pays',
507 related_name
='employes', null
=True, blank
=True
509 courriel_perso
= models
.EmailField(
510 u
'adresse courriel personnelle', blank
=True
513 # meta dématérialisation : pour permettre le filtrage
514 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
517 ordering
= ['nom', 'prenom']
518 verbose_name
= u
"Employé"
519 verbose_name_plural
= u
"Employés"
521 def __unicode__(self
):
522 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
526 if self
.genre
.upper() == u
'M':
528 elif self
.genre
.upper() == u
'F':
534 Retourne l'URL du service retournant la photo de l'Employe.
535 Équivalent reverse url 'rh_photo' avec id en param.
537 from django
.core
.urlresolvers
import reverse
538 return reverse('rh_photo', kwargs
={'id': self
.id})
540 def dossiers_passes(self
):
541 params
= {KEY_STATUT
: STATUT_INACTIF
, }
542 search
= RechercheTemporelle(params
, self
.__class__
)
543 search
.purge_params(params
)
544 q
= search
.get_q_temporel(self
.rh_dossiers
)
545 return self
.rh_dossiers
.filter(q
)
547 def dossiers_futurs(self
):
548 params
= {KEY_STATUT
: STATUT_FUTUR
, }
549 search
= RechercheTemporelle(params
, self
.__class__
)
550 search
.purge_params(params
)
551 q
= search
.get_q_temporel(self
.rh_dossiers
)
552 return self
.rh_dossiers
.filter(q
)
554 def dossiers_encours(self
):
555 params
= {KEY_STATUT
: STATUT_ACTIF
, }
556 search
= RechercheTemporelle(params
, self
.__class__
)
557 search
.purge_params(params
)
558 q
= search
.get_q_temporel(self
.rh_dossiers
)
559 return self
.rh_dossiers
.filter(q
)
561 def postes_encours(self
):
562 postes_encours
= set()
563 for d
in self
.dossiers_encours():
564 postes_encours
.add(d
.poste
)
565 return postes_encours
567 def poste_principal(self
):
569 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
571 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
573 poste
= Poste
.objects
.none()
575 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
580 prefix_implantation
= "rh_dossiers__poste__implantation__region"
582 def get_regions(self
):
584 for d
in self
.dossiers
.all():
585 regions
.append(d
.poste
.implantation
.region
)
588 reversion
.register(Employe
, format
='xml', follow
=[
589 'pieces', 'commentaires', 'ayantdroits'
593 class EmployePiece(models
.Model
):
595 Documents relatifs à un employé.
598 employe
= models
.ForeignKey(
599 'Employe', db_column
='employe', related_name
="pieces",
600 verbose_name
=u
"employé"
602 nom
= models
.CharField(max_length
=255)
603 fichier
= models
.FileField(
604 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
609 verbose_name
= u
"Employé pièce"
610 verbose_name_plural
= u
"Employé pièces"
612 def __unicode__(self
):
613 return u
'%s' % (self
.nom
)
615 reversion
.register(EmployePiece
, format
='xml')
618 class EmployeCommentaire(Commentaire
):
619 employe
= models
.ForeignKey(
620 'Employe', db_column
='employe', related_name
='commentaires'
624 verbose_name
= u
"Employé commentaire"
625 verbose_name_plural
= u
"Employé commentaires"
627 reversion
.register(EmployeCommentaire
, format
='xml')
630 LIEN_PARENTE_CHOICES
= (
631 ('Conjoint', 'Conjoint'),
632 ('Conjointe', 'Conjointe'),
638 class AyantDroit(models
.Model
):
640 Personne en relation avec un Employe.
643 nom
= models
.CharField(max_length
=255)
644 prenom
= models
.CharField(u
"prénom", max_length
=255)
645 nom_affichage
= models
.CharField(
646 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
648 nationalite
= models
.ForeignKey(
649 ref
.Pays
, to_field
='code', db_column
='nationalite',
650 related_name
='ayantdroits_nationalite',
651 verbose_name
=u
"nationalité", null
=True, blank
=True
653 date_naissance
= models
.DateField(
654 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
655 validators
=[validate_date_passee
], null
=True, blank
=True
657 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
660 employe
= models
.ForeignKey(
661 'Employe', db_column
='employe', related_name
='ayantdroits',
662 verbose_name
=u
"Employé"
664 lien_parente
= models
.CharField(
665 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
666 null
=True, blank
=True
671 verbose_name
= u
"Ayant droit"
672 verbose_name_plural
= u
"Ayants droit"
674 def __unicode__(self
):
675 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
677 prefix_implantation
= "employe__dossiers__poste__implantation__region"
679 def get_regions(self
):
681 for d
in self
.employe
.dossiers
.all():
682 regions
.append(d
.poste
.implantation
.region
)
685 reversion
.register(AyantDroit
, format
='xml', follow
=['commentaires'])
688 class AyantDroitCommentaire(Commentaire
):
689 ayant_droit
= models
.ForeignKey(
690 'AyantDroit', db_column
='ayant_droit', related_name
='commentaires'
693 reversion
.register(AyantDroitCommentaire
, format
='xml')
698 STATUT_RESIDENCE_CHOICES
= (
700 ('expat', 'Expatrié'),
703 COMPTE_COMPTA_CHOICES
= (
710 class Dossier_(models
.Model
, DevisableMixin
):
712 Le Dossier regroupe les informations relatives à l'occupation
713 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
716 Plusieurs Contrats peuvent être associés au Dossier.
717 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
718 lequel aucun Dossier n'existe est un poste vacant.
721 objects
= DossierManager()
724 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
725 organisme_bstg
= models
.ForeignKey(
726 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
727 verbose_name
=u
"organisme",
729 u
"Si détaché (DET) ou mis à disposition (MAD), "
730 u
"préciser l'organisme."
731 ), null
=True, blank
=True
735 remplacement
= models
.BooleanField(default
=False)
736 remplacement_de
= models
.ForeignKey(
737 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
738 null
=True, blank
=True
740 statut_residence
= models
.CharField(
741 u
"statut", max_length
=10, default
='local', null
=True,
742 choices
=STATUT_RESIDENCE_CHOICES
746 classement
= models
.ForeignKey(
747 'Classement', db_column
='classement', related_name
='+', null
=True,
750 regime_travail
= models
.DecimalField(
751 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
752 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
754 regime_travail_nb_heure_semaine
= models
.DecimalField(
755 u
"nb. heures par semaine", max_digits
=12,
756 decimal_places
=2, null
=True,
757 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
758 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
761 # Occupation du Poste par cet Employe (anciennement "mandat")
762 date_debut
= models
.DateField(u
"date de début d'occupation de poste")
763 date_fin
= models
.DateField(
764 u
"Date de fin d'occupation de poste", null
=True, blank
=True
772 ordering
= ['employe__nom', ]
773 verbose_name
= u
"Dossier"
774 verbose_name_plural
= "Dossiers"
776 def salaire_theorique(self
):
777 annee
= date
.today().year
778 coeff
= self
.classement
.coefficient
779 implantation
= self
.poste
.implantation
780 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
782 montant
= coeff
* point
.valeur
783 devise
= point
.devise
784 return {'montant': montant
, 'devise': devise
}
786 def __unicode__(self
):
787 poste
= self
.poste
.nom
788 if self
.employe
.genre
== 'F':
789 poste
= self
.poste
.nom_feminin
790 return u
'%s - %s' % (self
.employe
, poste
)
792 prefix_implantation
= "poste__implantation__region"
794 def get_regions(self
):
795 return [self
.poste
.implantation
.region
]
797 def remunerations(self
):
798 key
= "%s_remunerations" % self
._meta
.app_label
799 remunerations
= getattr(self
, key
)
800 return remunerations
.all().order_by('-date_debut')
802 def remunerations_en_cours(self
):
803 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
804 return self
.remunerations().all().filter(q
).order_by('date_debut')
806 def get_salaire(self
):
808 return [r
for r
in self
.remunerations().order_by('-date_debut')
809 if r
.type_id
== 1][0]
813 def get_salaire_euros(self
):
814 tx
= self
.taux_devise()
815 return (float)(tx
) * (float)(self
.salaire
)
817 def get_remunerations_brutes(self
):
821 4 Indemnité d'expatriation
822 5 Indemnité pour frais
823 6 Indemnité de logement
824 7 Indemnité de fonction
825 8 Indemnité de responsabilité
826 9 Indemnité de transport
827 10 Indemnité compensatrice
828 11 Indemnité de subsistance
829 12 Indemnité différentielle
830 13 Prime d'installation
833 16 Indemnité de départ
834 18 Prime de 13ième mois
837 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
838 return [r
for r
in self
.remunerations_en_cours().all()
841 def get_charges_salariales(self
):
843 20 Charges salariales ?
846 return [r
for r
in self
.remunerations_en_cours().all()
849 def get_charges_patronales(self
):
851 17 Charges patronales
854 return [r
for r
in self
.remunerations_en_cours().all()
857 def get_remunerations_tierces(self
):
861 return [r
for r
in self
.remunerations_en_cours().all()
862 if r
.type_id
in (2,)]
866 def get_total_local_charges_salariales(self
):
867 devise
= self
.poste
.get_devise()
869 for r
in self
.get_charges_salariales():
870 if r
.devise
!= devise
:
872 total
+= float(r
.montant
)
875 def get_total_local_charges_patronales(self
):
876 devise
= self
.poste
.get_devise()
878 for r
in self
.get_charges_patronales():
879 if r
.devise
!= devise
:
881 total
+= float(r
.montant
)
884 def get_local_salaire_brut(self
):
886 somme des rémuérations brutes
888 devise
= self
.poste
.get_devise()
890 for r
in self
.get_remunerations_brutes():
891 if r
.devise
!= devise
:
893 total
+= float(r
.montant
)
896 def get_local_salaire_net(self
):
898 salaire brut - charges salariales
900 devise
= self
.poste
.get_devise()
902 for r
in self
.get_charges_salariales():
903 if r
.devise
!= devise
:
905 total_charges
+= float(r
.montant
)
906 return self
.get_local_salaire_brut() - total_charges
908 def get_local_couts_auf(self
):
910 salaire net + charges patronales
912 devise
= self
.poste
.get_devise()
914 for r
in self
.get_charges_patronales():
915 if r
.devise
!= devise
:
917 total_charges
+= float(r
.montant
)
918 return self
.get_local_salaire_net() + total_charges
920 def get_total_local_remunerations_tierces(self
):
921 devise
= self
.poste
.get_devise()
923 for r
in self
.get_remunerations_tierces():
924 if r
.devise
!= devise
:
926 total
+= float(r
.montant
)
931 def get_total_charges_salariales(self
):
933 for r
in self
.get_charges_salariales():
934 total
+= r
.montant_euros()
937 def get_total_charges_patronales(self
):
939 for r
in self
.get_charges_patronales():
940 total
+= r
.montant_euros()
943 def get_salaire_brut(self
):
945 somme des rémuérations brutes
948 for r
in self
.get_remunerations_brutes():
949 total
+= r
.montant_euros()
952 def get_salaire_net(self
):
954 salaire brut - charges salariales
957 for r
in self
.get_charges_salariales():
958 total_charges
+= r
.montant_euros()
959 return self
.get_salaire_brut() - total_charges
961 def get_couts_auf(self
):
963 salaire net + charges patronales
966 for r
in self
.get_charges_patronales():
967 total_charges
+= r
.montant_euros()
968 return self
.get_salaire_net() + total_charges
970 def get_total_remunerations_tierces(self
):
972 for r
in self
.get_remunerations_tierces():
973 total
+= r
.montant_euros()
978 return (self
.date_debut
is None or self
.date_debut
<= today
) \
979 and (self
.date_fin
is None or self
.date_fin
>= today
) \
980 and not (self
.date_fin
is None and self
.date_debut
is None)
983 class Dossier(Dossier_
):
984 __doc__
= Dossier_
.__doc__
985 poste
= models
.ForeignKey(
986 Poste
, db_column
='poste', related_name
='rh_dossiers',
987 help_text
=u
"Taper le nom du poste ou du type de poste",
989 employe
= models
.ForeignKey(
990 'Employe', db_column
='employe',
991 help_text
=u
"Taper le nom de l'employé",
992 related_name
='rh_dossiers', verbose_name
=u
"employé"
994 principal
= models
.BooleanField(
995 u
"dossier principal", default
=True,
997 u
"Ce dossier est pour le principal poste occupé par l'employé"
1001 reversion
.register(Dossier
, format
='xml', follow
=[
1002 'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
1003 'rh_contrats', 'commentaires'
1007 class DossierPiece_(models
.Model
):
1009 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1010 Ex.: Lettre de motivation.
1012 nom
= models
.CharField(max_length
=255)
1013 fichier
= models
.FileField(
1014 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
1021 def __unicode__(self
):
1022 return u
'%s' % (self
.nom
)
1025 class DossierPiece(DossierPiece_
):
1026 dossier
= models
.ForeignKey(
1027 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
1030 reversion
.register(DossierPiece
, format
='xml')
1033 class DossierCommentaire(Commentaire
):
1034 dossier
= models
.ForeignKey(
1035 Dossier
, db_column
='dossier', related_name
='commentaires'
1038 reversion
.register(DossierCommentaire
, format
='xml')
1041 class DossierComparaison_(models
.Model
, DevisableMixin
):
1043 Photo d'une comparaison salariale au moment de l'embauche.
1045 objects
= DossierComparaisonManager()
1047 implantation
= models
.ForeignKey(
1048 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1050 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1051 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1052 montant
= models
.IntegerField(null
=True)
1053 devise
= models
.ForeignKey(
1054 'Devise', related_name
='+', null
=True, blank
=True
1060 def __unicode__(self
):
1061 return "%s (%s)" % (self
.poste
, self
.personne
)
1064 class DossierComparaison(DossierComparaison_
):
1065 dossier
= models
.ForeignKey(
1066 Dossier
, related_name
='rh_comparaisons'
1069 reversion
.register(DossierComparaison
, format
='xml')
1074 class RemunerationMixin(models
.Model
):
1077 type = models
.ForeignKey(
1078 'TypeRemuneration', db_column
='type', related_name
='+',
1079 verbose_name
=u
"type de rémunération"
1081 type_revalorisation
= models
.ForeignKey(
1082 'TypeRevalorisation', db_column
='type_revalorisation',
1083 related_name
='+', verbose_name
=u
"type de revalorisation",
1084 null
=True, blank
=True
1086 montant
= models
.DecimalField(
1087 null
=True, blank
=True, max_digits
=12, decimal_places
=2
1088 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1089 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1091 # commentaire = precision
1092 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1094 # date_debut = anciennement date_effectif
1095 date_debut
= models
.DateField(u
"date de début", null
=True, blank
=True)
1096 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1100 ordering
= ['type__nom', '-date_fin']
1102 def __unicode__(self
):
1103 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1106 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1108 Structure de rémunération (données budgétaires) en situation normale
1109 pour un Dossier. Si un Evenement existe, utiliser la structure de
1110 rémunération EvenementRemuneration de cet événement.
1113 def montant_mois(self
):
1114 return round(self
.montant
/ 12, 2)
1116 def montant_avec_regime(self
):
1117 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1119 def montant_euro_mois(self
):
1120 return round(self
.montant_euros() / 12, 2)
1122 def __unicode__(self
):
1124 devise
= self
.devise
.code
1127 return "%s %s" % (self
.montant
, devise
)
1131 verbose_name
= u
"Rémunération"
1132 verbose_name_plural
= u
"Rémunérations"
1135 class Remuneration(Remuneration_
):
1136 dossier
= models
.ForeignKey(
1137 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1140 reversion
.register(Remuneration
, format
='xml')
1145 class ContratManager(models
.Manager
):
1147 def get_query_set(self
):
1148 return super(ContratManager
, self
).get_query_set() \
1149 .select_related('dossier', 'dossier__poste')
1152 class Contrat_(models
.Model
):
1154 Document juridique qui encadre la relation de travail d'un Employe
1155 pour un Poste particulier. Pour un Dossier (qui documente cette
1156 relation de travail) plusieurs contrats peuvent être associés.
1158 objects
= ContratManager()
1159 type_contrat
= models
.ForeignKey(
1160 'TypeContrat', db_column
='type_contrat',
1161 verbose_name
=u
'type de contrat', related_name
='+'
1163 date_debut
= models
.DateField(u
"date de début")
1164 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1165 fichier
= models
.FileField(
1166 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1172 ordering
= ['dossier__employe__nom']
1173 verbose_name
= u
"Contrat"
1174 verbose_name_plural
= u
"Contrats"
1176 def __unicode__(self
):
1177 return u
'%s - %s' % (self
.dossier
, self
.id)
1180 class Contrat(Contrat_
):
1181 dossier
= models
.ForeignKey(
1182 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1185 reversion
.register(Contrat
, format
='xml')
1190 class CategorieEmploi(models
.Model
):
1192 Catégorie utilisée dans la gestion des Postes.
1193 Catégorie supérieure à TypePoste.
1195 nom
= models
.CharField(max_length
=255)
1199 verbose_name
= u
"catégorie d'emploi"
1200 verbose_name_plural
= u
"catégories d'emploi"
1202 def __unicode__(self
):
1205 reversion
.register(CategorieEmploi
, format
='xml')
1208 class FamilleProfessionnelle(models
.Model
):
1210 Famille professionnelle d'un poste.
1212 nom
= models
.CharField(max_length
=100)
1216 verbose_name
= u
'famille professionnelle'
1217 verbose_name_plural
= u
'familles professionnelles'
1219 def __unicode__(self
):
1222 reversion
.register(FamilleProfessionnelle
, format
='xml')
1225 class TypePoste(models
.Model
):
1229 nom
= models
.CharField(max_length
=255)
1230 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1231 is_responsable
= models
.BooleanField(
1232 u
"poste de responsabilité", default
=False
1234 categorie_emploi
= models
.ForeignKey(
1235 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1236 verbose_name
=u
"catégorie d'emploi"
1238 famille_professionnelle
= models
.ForeignKey(
1239 FamilleProfessionnelle
, related_name
='types_de_poste',
1240 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1245 verbose_name
= u
"Type de poste"
1246 verbose_name_plural
= u
"Types de poste"
1248 def __unicode__(self
):
1249 return u
'%s' % (self
.nom
)
1251 reversion
.register(TypePoste
, format
='xml')
1254 TYPE_PAIEMENT_CHOICES
= (
1255 (u
'Régulier', u
'Régulier'),
1256 (u
'Ponctuel', u
'Ponctuel'),
1259 NATURE_REMUNERATION_CHOICES
= (
1260 (u
'Accessoire', u
'Accessoire'),
1261 (u
'Charges', u
'Charges'),
1262 (u
'Indemnité', u
'Indemnité'),
1263 (u
'RAS', u
'Rémunération autre source'),
1264 (u
'Traitement', u
'Traitement'),
1268 class TypeRemuneration(models
.Model
):
1270 Catégorie de Remuneration.
1272 objects
= TypeRemunerationManager()
1274 nom
= models
.CharField(max_length
=255)
1275 type_paiement
= models
.CharField(
1276 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1278 nature_remuneration
= models
.CharField(
1279 u
"nature de la rémunération", max_length
=30,
1280 choices
=NATURE_REMUNERATION_CHOICES
1282 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1286 verbose_name
= u
"Type de rémunération"
1287 verbose_name_plural
= u
"Types de rémunération"
1289 def __unicode__(self
):
1291 archive
= u
"(archivé)"
1294 return u
'%s %s' % (self
.nom
, archive
)
1296 reversion
.register(TypeRemuneration
, format
='xml')
1299 class TypeRevalorisation(models
.Model
):
1301 Justification du changement de la Remuneration.
1302 (Actuellement utilisé dans aucun traitement informatique.)
1304 nom
= models
.CharField(max_length
=255)
1308 verbose_name
= u
"Type de revalorisation"
1309 verbose_name_plural
= u
"Types de revalorisation"
1311 def __unicode__(self
):
1312 return u
'%s' % (self
.nom
)
1314 reversion
.register(TypeRevalorisation
, format
='xml')
1317 class Service(models
.Model
):
1319 Unité administrative où les Postes sont rattachés.
1321 objects
= ServiceManager()
1323 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1324 nom
= models
.CharField(max_length
=255)
1328 verbose_name
= u
"Service"
1329 verbose_name_plural
= u
"Services"
1331 def __unicode__(self
):
1333 archive
= u
"(archivé)"
1336 return u
'%s %s' % (self
.nom
, archive
)
1338 reversion
.register(Service
, format
='xml')
1341 TYPE_ORGANISME_CHOICES
= (
1342 ('MAD', 'Mise à disposition'),
1343 ('DET', 'Détachement'),
1347 class OrganismeBstg(models
.Model
):
1349 Organisation d'où provient un Employe mis à disposition (MAD) de
1350 ou détaché (DET) à l'AUF à titre gratuit.
1352 (BSTG = bien et service à titre gratuit.)
1354 nom
= models
.CharField(max_length
=255)
1355 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1356 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1358 related_name
='organismes_bstg',
1359 null
=True, blank
=True)
1362 ordering
= ['type', 'nom']
1363 verbose_name
= u
"Organisme BSTG"
1364 verbose_name_plural
= u
"Organismes BSTG"
1366 def __unicode__(self
):
1367 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1369 prefix_implantation
= "pays__region"
1371 def get_regions(self
):
1372 return [self
.pays
.region
]
1374 reversion
.register(OrganismeBstg
, format
='xml')
1377 class Statut(models
.Model
):
1379 Statut de l'Employe dans le cadre d'un Dossier particulier.
1382 code
= models
.CharField(
1383 max_length
=25, unique
=True,
1385 u
"Saisir un code court mais lisible pour ce statut : "
1386 u
"le code est utilisé pour associer les statuts aux autres "
1387 u
"données tout en demeurant plus lisible qu'un identifiant "
1391 nom
= models
.CharField(max_length
=255)
1395 verbose_name
= u
"Statut d'employé"
1396 verbose_name_plural
= u
"Statuts d'employé"
1398 def __unicode__(self
):
1399 return u
'%s : %s' % (self
.code
, self
.nom
)
1401 reversion
.register(Statut
, format
='xml')
1404 TYPE_CLASSEMENT_CHOICES
= (
1405 ('S', 'S -Soutien'),
1406 ('T', 'T - Technicien'),
1407 ('P', 'P - Professionel'),
1409 ('D', 'D - Direction'),
1410 ('SO', 'SO - Sans objet [expatriés]'),
1411 ('HG', 'HG - Hors grille [direction]'),
1415 class ClassementManager(models
.Manager
):
1417 Ordonner les spcéfiquement les classements.
1419 def get_query_set(self
):
1420 qs
= super(self
.__class__
, self
).get_query_set()
1421 qs
= qs
.extra(select
={
1422 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1424 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1428 class Classement_(models
.Model
):
1430 Éléments de classement de la
1431 "Grille générique de classement hiérarchique".
1433 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1434 classement dans la grille. Le classement donne le coefficient utilisé dans:
1436 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1438 objects
= ClassementManager()
1441 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1442 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1443 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1444 coefficient
= models
.FloatField(u
"coefficient", blank
=True, null
=True)
1447 # annee # au lieu de date_debut et date_fin
1448 commentaire
= models
.TextField(null
=True, blank
=True)
1452 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1453 verbose_name
= u
"Classement"
1454 verbose_name_plural
= u
"Classements"
1456 def __unicode__(self
):
1457 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1460 class Classement(Classement_
):
1461 __doc__
= Classement_
.__doc__
1463 reversion
.register(Classement
, format
='xml')
1466 class TauxChange_(models
.Model
):
1468 Taux de change de la devise vers l'euro (EUR)
1469 pour chaque année budgétaire.
1472 devise
= models
.ForeignKey('Devise', db_column
='devise')
1473 annee
= models
.IntegerField(u
"année")
1474 taux
= models
.FloatField(u
"taux vers l'euro")
1478 ordering
= ['-annee', 'devise__code']
1479 verbose_name
= u
"Taux de change"
1480 verbose_name_plural
= u
"Taux de change"
1482 def __unicode__(self
):
1483 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1486 class TauxChange(TauxChange_
):
1487 __doc__
= TauxChange_
.__doc__
1489 reversion
.register(TauxChange
, format
='xml')
1492 class ValeurPointManager(models
.Manager
):
1494 def get_query_set(self
):
1495 return super(ValeurPointManager
, self
).get_query_set() \
1496 .select_related('devise', 'implantation')
1499 class ValeurPoint_(models
.Model
):
1501 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1502 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1503 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1505 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1508 objects
= models
.Manager()
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(models
.Model
):
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(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')