1 # -=- encoding: utf-8 -=-
4 from datetime
import date
5 from decimal
import Decimal
7 from django
.core
.files
.storage
import FileSystemStorage
8 from django
.db
import models
9 from django
.db
.models
import Q
10 from django
.conf
import settings
12 from auf
.django
.emploi
.models
import \
13 GENRE_CHOICES
, SITUATION_CHOICES
# devrait plutot être dans references
14 from auf
.django
.metadata
.models
import AUFMetadata
15 from auf
.django
.metadata
.managers
import NoDeleteManager
16 from auf
.django
.references
import models
as ref
18 from project
.rh
.change_list
import \
19 RechercheTemporelle
, KEY_STATUT
, STATUT_ACTIF
, STATUT_INACTIF
, \
21 from project
.rh
.managers
import \
22 PosteManager
, DossierManager
, EmployeManager
, \
23 DossierComparaisonManager
, \
24 PosteComparaisonManager
, DeviseManager
, ServiceManager
, \
25 TypeRemunerationManager
, RemunerationManager
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, default
=0
227 salaire_max
= models
.DecimalField(
228 max_digits
=12, decimal_places
=2, null
=True, default
=0
230 indemn_min
= models
.DecimalField(
231 max_digits
=12, decimal_places
=2, null
=True, default
=0
233 indemn_max
= models
.DecimalField(
234 max_digits
=12, decimal_places
=2, null
=True, default
=0
236 autre_min
= models
.DecimalField(
237 max_digits
=12, decimal_places
=2, null
=True, default
=0
239 autre_max
= models
.DecimalField(
240 max_digits
=12, decimal_places
=2, null
=True, default
=0
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())
344 POSTE_FINANCEMENT_CHOICES
= (
345 ('A', 'A - Frais de personnel'),
346 ('B', 'B - Projet(s)-Titre(s)'),
351 class PosteFinancement_(models
.Model
):
353 Pour un Poste, structure d'informations décrivant comment on prévoit
356 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
357 pourcentage
= models
.DecimalField(
358 max_digits
=12, decimal_places
=2,
359 help_text
="ex.: 33.33 % (décimale avec point)"
361 commentaire
= models
.TextField(
362 help_text
="Spécifiez la source de financement."
369 def __unicode__(self
):
370 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
373 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
376 class PosteFinancement(PosteFinancement_
):
377 poste
= models
.ForeignKey(
378 Poste
, db_column
='poste', related_name
='rh_financements'
382 class PostePiece_(models
.Model
):
384 Documents relatifs au Poste.
385 Ex.: Description de poste
387 nom
= models
.CharField(u
"Nom", max_length
=255)
388 fichier
= models
.FileField(
389 u
"Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
396 def __unicode__(self
):
397 return u
'%s' % (self
.nom
)
400 class PostePiece(PostePiece_
):
401 poste
= models
.ForeignKey(
402 Poste
, db_column
='poste', related_name
='rh_pieces'
406 class PosteComparaison_(AUFMetadata
, DevisableMixin
):
408 De la même manière qu'un dossier, un poste peut-être comparé à un autre
411 objects
= PosteComparaisonManager()
413 implantation
= models
.ForeignKey(
414 ref
.Implantation
, null
=True, blank
=True, related_name
="+"
416 nom
= models
.CharField(u
"Poste", max_length
=255, null
=True, blank
=True)
417 montant
= models
.IntegerField(null
=True)
418 devise
= models
.ForeignKey(
419 "Devise", related_name
='+', null
=True, blank
=True
425 def __unicode__(self
):
429 class PosteComparaison(PosteComparaison_
):
430 poste
= models
.ForeignKey(
431 Poste
, related_name
='rh_comparaisons_internes'
434 objects
= NoDeleteManager()
437 class PosteCommentaire(Commentaire
):
438 poste
= models
.ForeignKey(
439 Poste
, db_column
='poste', related_name
='commentaires'
445 class Employe(AUFMetadata
):
447 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
448 Dossiers qu'il occupe ou a occupé de Postes.
450 Cette classe aurait pu avantageusement s'appeler Personne car la notion
451 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
454 objects
= EmployeManager()
457 nom
= models
.CharField(max_length
=255)
458 prenom
= models
.CharField(u
"prénom", max_length
=255)
459 nom_affichage
= models
.CharField(
460 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
462 nationalite
= models
.ForeignKey(
463 ref
.Pays
, to_field
='code', db_column
='nationalite',
464 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
465 blank
=True, null
=True
467 date_naissance
= models
.DateField(
468 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
469 validators
=[validate_date_passee
], null
=True, blank
=True
471 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
474 situation_famille
= models
.CharField(
475 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
476 null
=True, blank
=True
478 date_entree
= models
.DateField(
479 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
484 tel_domicile
= models
.CharField(
485 u
"tél. domicile", max_length
=255, null
=True, blank
=True
487 tel_cellulaire
= models
.CharField(
488 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
490 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
491 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
492 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
493 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
494 pays
= models
.ForeignKey(
495 ref
.Pays
, to_field
='code', db_column
='pays',
496 related_name
='employes', null
=True, blank
=True
498 courriel_perso
= models
.EmailField(
499 u
'adresse courriel personnelle', blank
=True
502 # meta dématérialisation : pour permettre le filtrage
503 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
506 ordering
= ['nom', 'prenom']
507 verbose_name
= u
"Employé"
508 verbose_name_plural
= u
"Employés"
510 def __unicode__(self
):
511 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
515 if self
.genre
.upper() == u
'M':
517 elif self
.genre
.upper() == u
'F':
523 Retourne l'URL du service retournant la photo de l'Employe.
524 Équivalent reverse url 'rh_photo' avec id en param.
526 from django
.core
.urlresolvers
import reverse
527 return reverse('rh_photo', kwargs
={'id': self
.id})
529 def dossiers_passes(self
):
530 params
= {KEY_STATUT
: STATUT_INACTIF
, }
531 search
= RechercheTemporelle(params
, self
.__class__
)
532 search
.purge_params(params
)
533 q
= search
.get_q_temporel(self
.rh_dossiers
)
534 return self
.rh_dossiers
.filter(q
)
536 def dossiers_futurs(self
):
537 params
= {KEY_STATUT
: STATUT_FUTUR
, }
538 search
= RechercheTemporelle(params
, self
.__class__
)
539 search
.purge_params(params
)
540 q
= search
.get_q_temporel(self
.rh_dossiers
)
541 return self
.rh_dossiers
.filter(q
)
543 def dossiers_encours(self
):
544 params
= {KEY_STATUT
: STATUT_ACTIF
, }
545 search
= RechercheTemporelle(params
, self
.__class__
)
546 search
.purge_params(params
)
547 q
= search
.get_q_temporel(self
.rh_dossiers
)
548 return self
.rh_dossiers
.filter(q
)
550 def postes_encours(self
):
551 postes_encours
= set()
552 for d
in self
.dossiers_encours():
553 postes_encours
.add(d
.poste
)
554 return postes_encours
556 def poste_principal(self
):
558 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
560 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
562 poste
= Poste
.objects
.none()
564 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
569 prefix_implantation
= "rh_dossiers__poste__implantation__region"
571 def get_regions(self
):
573 for d
in self
.dossiers
.all():
574 regions
.append(d
.poste
.implantation
.region
)
578 class EmployePiece(models
.Model
):
580 Documents relatifs à un employé.
583 employe
= models
.ForeignKey(
584 'Employe', db_column
='employe', related_name
="pieces",
585 verbose_name
=u
"employé"
587 nom
= models
.CharField(max_length
=255)
588 fichier
= models
.FileField(
589 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
594 verbose_name
= u
"Employé pièce"
595 verbose_name_plural
= u
"Employé pièces"
597 def __unicode__(self
):
598 return u
'%s' % (self
.nom
)
601 class EmployeCommentaire(Commentaire
):
602 employe
= models
.ForeignKey(
603 'Employe', db_column
='employe', related_name
='+'
607 verbose_name
= u
"Employé commentaire"
608 verbose_name_plural
= u
"Employé commentaires"
611 LIEN_PARENTE_CHOICES
= (
612 ('Conjoint', 'Conjoint'),
613 ('Conjointe', 'Conjointe'),
619 class AyantDroit(AUFMetadata
):
621 Personne en relation avec un Employe.
624 nom
= models
.CharField(max_length
=255)
625 prenom
= models
.CharField(u
"prénom", max_length
=255)
626 nom_affichage
= models
.CharField(
627 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
629 nationalite
= models
.ForeignKey(
630 ref
.Pays
, to_field
='code', db_column
='nationalite',
631 related_name
='ayantdroits_nationalite',
632 verbose_name
=u
"nationalité", null
=True, blank
=True
634 date_naissance
= models
.DateField(
635 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
636 validators
=[validate_date_passee
], null
=True, blank
=True
638 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
641 employe
= models
.ForeignKey(
642 'Employe', db_column
='employe', related_name
='ayantdroits',
643 verbose_name
=u
"Employé"
645 lien_parente
= models
.CharField(
646 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
647 null
=True, blank
=True
652 verbose_name
= u
"Ayant droit"
653 verbose_name_plural
= u
"Ayants droit"
655 def __unicode__(self
):
656 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
658 prefix_implantation
= "employe__dossiers__poste__implantation__region"
660 def get_regions(self
):
662 for d
in self
.employe
.dossiers
.all():
663 regions
.append(d
.poste
.implantation
.region
)
667 class AyantDroitCommentaire(Commentaire
):
668 ayant_droit
= models
.ForeignKey(
669 'AyantDroit', db_column
='ayant_droit', related_name
='+'
675 STATUT_RESIDENCE_CHOICES
= (
677 ('expat', 'Expatrié'),
680 COMPTE_COMPTA_CHOICES
= (
687 class Dossier_(AUFMetadata
, DevisableMixin
):
689 Le Dossier regroupe les informations relatives à l'occupation
690 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
693 Plusieurs Contrats peuvent être associés au Dossier.
694 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
695 lequel aucun Dossier n'existe est un poste vacant.
698 objects
= DossierManager()
701 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
702 organisme_bstg
= models
.ForeignKey(
703 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
704 verbose_name
=u
"organisme",
706 u
"Si détaché (DET) ou mis à disposition (MAD), "
707 u
"préciser l'organisme."
708 ), null
=True, blank
=True
712 remplacement
= models
.BooleanField(default
=False)
713 remplacement_de
= models
.ForeignKey(
714 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
715 null
=True, blank
=True
717 statut_residence
= models
.CharField(
718 u
"statut", max_length
=10, default
='local', null
=True,
719 choices
=STATUT_RESIDENCE_CHOICES
723 classement
= models
.ForeignKey(
724 'Classement', db_column
='classement', related_name
='+', null
=True,
727 regime_travail
= models
.DecimalField(
728 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
729 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
731 regime_travail_nb_heure_semaine
= models
.DecimalField(
732 u
"nb. heures par semaine", max_digits
=12,
733 decimal_places
=2, null
=True,
734 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
735 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
738 # Occupation du Poste par cet Employe (anciennement "mandat")
739 date_debut
= models
.DateField(u
"date de début d'occupation de poste")
740 date_fin
= models
.DateField(
741 u
"Date de fin d'occupation de poste", null
=True, blank
=True
749 ordering
= ['employe__nom', ]
750 verbose_name
= u
"Dossier"
751 verbose_name_plural
= "Dossiers"
753 def salaire_theorique(self
):
754 annee
= date
.today().year
755 coeff
= self
.classement
.coefficient
756 implantation
= self
.poste
.implantation
757 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
759 montant
= coeff
* point
.valeur
760 devise
= point
.devise
761 return {'montant': montant
, 'devise': devise
}
763 def __unicode__(self
):
764 poste
= self
.poste
.nom
765 if self
.employe
.genre
== 'F':
766 poste
= self
.poste
.nom_feminin
767 return u
'%s - %s' % (self
.employe
, poste
)
769 prefix_implantation
= "poste__implantation__region"
771 def get_regions(self
):
772 return [self
.poste
.implantation
.region
]
774 def remunerations(self
):
775 key
= "%s_remunerations" % self
._meta
.app_label
776 remunerations
= getattr(self
, key
)
777 return remunerations
.all().order_by('-date_debut')
779 def remunerations_en_cours(self
):
780 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
781 return self
.remunerations().all().filter(q
).order_by('date_debut')
783 def get_salaire(self
):
785 return [r
for r
in self
.remunerations().order_by('-date_debut')
786 if r
.type_id
== 1][0]
790 def get_salaire_euros(self
):
791 tx
= self
.taux_devise()
792 return (float)(tx
) * (float)(self
.salaire
)
794 def get_remunerations_brutes(self
):
798 4 Indemnité d'expatriation
799 5 Indemnité pour frais
800 6 Indemnité de logement
801 7 Indemnité de fonction
802 8 Indemnité de responsabilité
803 9 Indemnité de transport
804 10 Indemnité compensatrice
805 11 Indemnité de subsistance
806 12 Indemnité différentielle
807 13 Prime d'installation
810 16 Indemnité de départ
811 18 Prime de 13ième mois
814 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
815 return [r
for r
in self
.remunerations_en_cours().all()
818 def get_charges_salariales(self
):
820 20 Charges salariales ?
823 return [r
for r
in self
.remunerations_en_cours().all()
826 def get_charges_patronales(self
):
828 17 Charges patronales
831 return [r
for r
in self
.remunerations_en_cours().all()
834 def get_remunerations_tierces(self
):
838 return [r
for r
in self
.remunerations_en_cours().all()
839 if r
.type_id
in (2,)]
843 def get_total_local_charges_salariales(self
):
844 devise
= self
.poste
.get_devise()
846 for r
in self
.get_charges_salariales():
847 if r
.devise
!= devise
:
849 total
+= float(r
.montant
)
852 def get_total_local_charges_patronales(self
):
853 devise
= self
.poste
.get_devise()
855 for r
in self
.get_charges_patronales():
856 if r
.devise
!= devise
:
858 total
+= float(r
.montant
)
861 def get_local_salaire_brut(self
):
863 somme des rémuérations brutes
865 devise
= self
.poste
.get_devise()
867 for r
in self
.get_remunerations_brutes():
868 if r
.devise
!= devise
:
870 total
+= float(r
.montant
)
873 def get_local_salaire_net(self
):
875 salaire brut - charges salariales
877 devise
= self
.poste
.get_devise()
879 for r
in self
.get_charges_salariales():
880 if r
.devise
!= devise
:
882 total_charges
+= float(r
.montant
)
883 return self
.get_local_salaire_brut() - total_charges
885 def get_local_couts_auf(self
):
887 salaire net + charges patronales
889 devise
= self
.poste
.get_devise()
891 for r
in self
.get_charges_patronales():
892 if r
.devise
!= devise
:
894 total_charges
+= float(r
.montant
)
895 return self
.get_local_salaire_net() + total_charges
897 def get_total_local_remunerations_tierces(self
):
898 devise
= self
.poste
.get_devise()
900 for r
in self
.get_remunerations_tierces():
901 if r
.devise
!= devise
:
903 total
+= float(r
.montant
)
908 def get_total_charges_salariales(self
):
910 for r
in self
.get_charges_salariales():
911 total
+= r
.montant_euros()
914 def get_total_charges_patronales(self
):
916 for r
in self
.get_charges_patronales():
917 total
+= r
.montant_euros()
920 def get_salaire_brut(self
):
922 somme des rémuérations brutes
925 for r
in self
.get_remunerations_brutes():
926 total
+= r
.montant_euros()
929 def get_salaire_net(self
):
931 salaire brut - charges salariales
934 for r
in self
.get_charges_salariales():
935 total_charges
+= r
.montant_euros()
936 return self
.get_salaire_brut() - total_charges
938 def get_couts_auf(self
):
940 salaire net + charges patronales
943 for r
in self
.get_charges_patronales():
944 total_charges
+= r
.montant_euros()
945 return self
.get_salaire_net() + total_charges
947 def get_total_remunerations_tierces(self
):
949 for r
in self
.get_remunerations_tierces():
950 total
+= r
.montant_euros()
955 return (self
.date_debut
is None or self
.date_debut
<= today
) \
956 and (self
.date_fin
is None or self
.date_fin
>= today
) \
957 and not (self
.date_fin
is None and self
.date_debut
is None)
960 class Dossier(Dossier_
):
961 __doc__
= Dossier_
.__doc__
962 poste
= models
.ForeignKey(
963 Poste
, db_column
='poste', related_name
='rh_dossiers',
964 help_text
=u
"Taper le nom du poste ou du type de poste",
966 employe
= models
.ForeignKey(
967 'Employe', db_column
='employe',
968 help_text
=u
"Taper le nom de l'employé",
969 related_name
='rh_dossiers', verbose_name
=u
"employé"
971 principal
= models
.BooleanField(
972 u
"dossier principal", default
=True,
974 u
"Ce dossier est pour le principal poste occupé par l'employé"
979 class DossierPiece_(models
.Model
):
981 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
982 Ex.: Lettre de motivation.
984 nom
= models
.CharField(max_length
=255)
985 fichier
= models
.FileField(
986 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
993 def __unicode__(self
):
994 return u
'%s' % (self
.nom
)
997 class DossierPiece(DossierPiece_
):
998 dossier
= models
.ForeignKey(
999 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
1003 class DossierCommentaire(Commentaire
):
1004 dossier
= models
.ForeignKey(
1005 Dossier
, db_column
='dossier', related_name
='commentaires'
1009 class DossierComparaison_(models
.Model
, DevisableMixin
):
1011 Photo d'une comparaison salariale au moment de l'embauche.
1013 objects
= DossierComparaisonManager()
1015 implantation
= models
.ForeignKey(
1016 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1018 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1019 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1020 montant
= models
.IntegerField(null
=True)
1021 devise
= models
.ForeignKey(
1022 'Devise', related_name
='+', null
=True, blank
=True
1028 def __unicode__(self
):
1029 return "%s (%s)" % (self
.poste
, self
.personne
)
1032 class DossierComparaison(DossierComparaison_
):
1033 dossier
= models
.ForeignKey(
1034 Dossier
, related_name
='rh_comparaisons'
1040 class RemunerationMixin(AUFMetadata
):
1043 type = models
.ForeignKey(
1044 'TypeRemuneration', db_column
='type', related_name
='+',
1045 verbose_name
=u
"type de rémunération"
1047 type_revalorisation
= models
.ForeignKey(
1048 'TypeRevalorisation', db_column
='type_revalorisation',
1049 related_name
='+', verbose_name
=u
"type de revalorisation",
1050 null
=True, blank
=True
1052 montant
= models
.DecimalField(
1053 null
=True, blank
=True,
1054 default
=0, max_digits
=12, decimal_places
=2
1055 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1056 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1058 # commentaire = precision
1059 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1061 # date_debut = anciennement date_effectif
1062 date_debut
= models
.DateField(u
"date de début", null
=True, blank
=True)
1063 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1065 objects
= RemunerationManager()
1069 ordering
= ['type__nom', '-date_fin']
1071 def __unicode__(self
):
1072 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1075 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1077 Structure de rémunération (données budgétaires) en situation normale
1078 pour un Dossier. Si un Evenement existe, utiliser la structure de
1079 rémunération EvenementRemuneration de cet événement.
1082 def montant_mois(self
):
1083 return round(self
.montant
/ 12, 2)
1085 def montant_avec_regime(self
):
1086 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1088 def montant_euro_mois(self
):
1089 return round(self
.montant_euros() / 12, 2)
1091 def __unicode__(self
):
1093 devise
= self
.devise
.code
1096 return "%s %s" % (self
.montant
, devise
)
1100 verbose_name
= u
"Rémunération"
1101 verbose_name_plural
= u
"Rémunérations"
1104 class Remuneration(Remuneration_
):
1105 dossier
= models
.ForeignKey(
1106 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1112 class ContratManager(NoDeleteManager
):
1113 def get_query_set(self
):
1114 return super(ContratManager
, self
).get_query_set() \
1115 .select_related('dossier', 'dossier__poste')
1118 class Contrat_(AUFMetadata
):
1120 Document juridique qui encadre la relation de travail d'un Employe
1121 pour un Poste particulier. Pour un Dossier (qui documente cette
1122 relation de travail) plusieurs contrats peuvent être associés.
1124 objects
= ContratManager()
1125 type_contrat
= models
.ForeignKey(
1126 'TypeContrat', db_column
='type_contrat',
1127 verbose_name
=u
'type de contrat', related_name
='+'
1129 date_debut
= models
.DateField(u
"date de début")
1130 date_fin
= models
.DateField(u
"date de fin", null
=True, blank
=True)
1131 fichier
= models
.FileField(
1132 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1138 ordering
= ['dossier__employe__nom']
1139 verbose_name
= u
"Contrat"
1140 verbose_name_plural
= u
"Contrats"
1142 def __unicode__(self
):
1143 return u
'%s - %s' % (self
.dossier
, self
.id)
1146 class Contrat(Contrat_
):
1147 dossier
= models
.ForeignKey(
1148 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1154 class CategorieEmploi(AUFMetadata
):
1156 Catégorie utilisée dans la gestion des Postes.
1157 Catégorie supérieure à TypePoste.
1159 nom
= models
.CharField(max_length
=255)
1163 verbose_name
= u
"catégorie d'emploi"
1164 verbose_name_plural
= u
"catégories d'emploi"
1166 def __unicode__(self
):
1170 class FamilleProfessionnelle(models
.Model
):
1172 Famille professionnelle d'un poste.
1174 nom
= models
.CharField(max_length
=100)
1178 verbose_name
= u
'famille professionnelle'
1179 verbose_name_plural
= u
'familles professionnelles'
1181 def __unicode__(self
):
1185 class TypePoste(AUFMetadata
):
1189 nom
= models
.CharField(max_length
=255)
1190 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1191 is_responsable
= models
.BooleanField(
1192 u
"poste de responsabilité", default
=False
1194 categorie_emploi
= models
.ForeignKey(
1195 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1196 verbose_name
=u
"catégorie d'emploi"
1198 famille_professionnelle
= models
.ForeignKey(
1199 FamilleProfessionnelle
, related_name
='types_de_poste',
1200 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1205 verbose_name
= u
"Type de poste"
1206 verbose_name_plural
= u
"Types de poste"
1208 def __unicode__(self
):
1209 return u
'%s' % (self
.nom
)
1211 TYPE_PAIEMENT_CHOICES
= (
1212 (u
'Régulier', u
'Régulier'),
1213 (u
'Ponctuel', u
'Ponctuel'),
1216 NATURE_REMUNERATION_CHOICES
= (
1217 (u
'Accessoire', u
'Accessoire'),
1218 (u
'Charges', u
'Charges'),
1219 (u
'Indemnité', u
'Indemnité'),
1220 (u
'RAS', u
'Rémunération autre source'),
1221 (u
'Traitement', u
'Traitement'),
1225 class TypeRemuneration(AUFMetadata
):
1227 Catégorie de Remuneration.
1229 objects
= TypeRemunerationManager()
1231 nom
= models
.CharField(max_length
=255)
1232 type_paiement
= models
.CharField(
1233 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1235 nature_remuneration
= models
.CharField(
1236 u
"nature de la rémunération", max_length
=30,
1237 choices
=NATURE_REMUNERATION_CHOICES
1239 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1243 verbose_name
= u
"Type de rémunération"
1244 verbose_name_plural
= u
"Types de rémunération"
1246 def __unicode__(self
):
1248 archive
= u
"(archivé)"
1251 return u
'%s %s' % (self
.nom
, archive
)
1254 class TypeRevalorisation(AUFMetadata
):
1256 Justification du changement de la Remuneration.
1257 (Actuellement utilisé dans aucun traitement informatique.)
1259 nom
= models
.CharField(max_length
=255)
1263 verbose_name
= u
"Type de revalorisation"
1264 verbose_name_plural
= u
"Types de revalorisation"
1266 def __unicode__(self
):
1267 return u
'%s' % (self
.nom
)
1270 class Service(AUFMetadata
):
1272 Unité administrative où les Postes sont rattachés.
1274 objects
= ServiceManager()
1276 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1277 nom
= models
.CharField(max_length
=255)
1281 verbose_name
= u
"Service"
1282 verbose_name_plural
= u
"Services"
1284 def __unicode__(self
):
1286 archive
= u
"(archivé)"
1289 return u
'%s %s' % (self
.nom
, archive
)
1292 TYPE_ORGANISME_CHOICES
= (
1293 ('MAD', 'Mise à disposition'),
1294 ('DET', 'Détachement'),
1298 class OrganismeBstg(AUFMetadata
):
1300 Organisation d'où provient un Employe mis à disposition (MAD) de
1301 ou détaché (DET) à l'AUF à titre gratuit.
1303 (BSTG = bien et service à titre gratuit.)
1305 nom
= models
.CharField(max_length
=255)
1306 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1307 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1309 related_name
='organismes_bstg',
1310 null
=True, blank
=True)
1313 ordering
= ['type', 'nom']
1314 verbose_name
= u
"Organisme BSTG"
1315 verbose_name_plural
= u
"Organismes BSTG"
1317 def __unicode__(self
):
1318 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1320 prefix_implantation
= "pays__region"
1322 def get_regions(self
):
1323 return [self
.pays
.region
]
1326 class Statut(AUFMetadata
):
1328 Statut de l'Employe dans le cadre d'un Dossier particulier.
1331 code
= models
.CharField(
1332 max_length
=25, unique
=True,
1334 u
"Saisir un code court mais lisible pour ce statut : "
1335 u
"le code est utilisé pour associer les statuts aux autres "
1336 u
"données tout en demeurant plus lisible qu'un identifiant "
1340 nom
= models
.CharField(max_length
=255)
1344 verbose_name
= u
"Statut d'employé"
1345 verbose_name_plural
= u
"Statuts d'employé"
1347 def __unicode__(self
):
1348 return u
'%s : %s' % (self
.code
, self
.nom
)
1351 TYPE_CLASSEMENT_CHOICES
= (
1352 ('S', 'S -Soutien'),
1353 ('T', 'T - Technicien'),
1354 ('P', 'P - Professionel'),
1356 ('D', 'D - Direction'),
1357 ('SO', 'SO - Sans objet [expatriés]'),
1358 ('HG', 'HG - Hors grille [direction]'),
1362 class ClassementManager(models
.Manager
):
1364 Ordonner les spcéfiquement les classements.
1366 def get_query_set(self
):
1367 qs
= super(self
.__class__
, self
).get_query_set()
1368 qs
= qs
.extra(select
={
1369 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1371 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1375 class Classement_(AUFMetadata
):
1377 Éléments de classement de la
1378 "Grille générique de classement hiérarchique".
1380 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1381 classement dans la grille. Le classement donne le coefficient utilisé dans:
1383 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1385 objects
= ClassementManager()
1388 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1389 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1390 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1391 coefficient
= models
.FloatField(u
"coefficient", default
=0, null
=True)
1394 # annee # au lieu de date_debut et date_fin
1395 commentaire
= models
.TextField(null
=True, blank
=True)
1399 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1400 verbose_name
= u
"Classement"
1401 verbose_name_plural
= u
"Classements"
1403 def __unicode__(self
):
1404 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1407 class Classement(Classement_
):
1408 __doc__
= Classement_
.__doc__
1411 class TauxChange_(AUFMetadata
):
1413 Taux de change de la devise vers l'euro (EUR)
1414 pour chaque année budgétaire.
1417 devise
= models
.ForeignKey('Devise', db_column
='devise')
1418 annee
= models
.IntegerField(u
"année")
1419 taux
= models
.FloatField(u
"taux vers l'euro")
1423 ordering
= ['-annee', 'devise__code']
1424 verbose_name
= u
"Taux de change"
1425 verbose_name_plural
= u
"Taux de change"
1427 def __unicode__(self
):
1428 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1431 class TauxChange(TauxChange_
):
1432 __doc__
= TauxChange_
.__doc__
1435 class ValeurPointManager(NoDeleteManager
):
1437 def get_query_set(self
):
1438 return super(ValeurPointManager
, self
).get_query_set() \
1439 .select_related('devise', 'implantation')
1442 class ValeurPoint_(AUFMetadata
):
1444 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1445 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1446 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1448 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1451 actuelles
= ValeurPointManager()
1453 valeur
= models
.FloatField(null
=True)
1454 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1455 implantation
= models
.ForeignKey(ref
.Implantation
,
1456 db_column
='implantation',
1457 related_name
='%(app_label)s_valeur_point')
1459 annee
= models
.IntegerField()
1462 ordering
= ['-annee', 'implantation__nom']
1464 verbose_name
= u
"Valeur du point"
1465 verbose_name_plural
= u
"Valeurs du point"
1467 def __unicode__(self
):
1468 return u
'%s %s %s [%s] %s' % (
1469 self
.devise
.code
, self
.annee
, self
.valeur
,
1470 self
.implantation
.nom_court
, self
.devise
.nom
1474 class ValeurPoint(ValeurPoint_
):
1475 __doc__
= ValeurPoint_
.__doc__
1478 class Devise(AUFMetadata
):
1482 objects
= DeviseManager()
1484 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1485 code
= models
.CharField(max_length
=10, unique
=True)
1486 nom
= models
.CharField(max_length
=255)
1490 verbose_name
= u
"Devise"
1491 verbose_name_plural
= u
"Devises"
1493 def __unicode__(self
):
1494 return u
'%s - %s' % (self
.code
, self
.nom
)
1497 class TypeContrat(AUFMetadata
):
1501 nom
= models
.CharField(max_length
=255)
1502 nom_long
= models
.CharField(max_length
=255)
1506 verbose_name
= u
"Type de contrat"
1507 verbose_name_plural
= u
"Types de contrat"
1509 def __unicode__(self
):
1510 return u
'%s' % (self
.nom
)
1515 class ResponsableImplantationProxy(ref
.Implantation
):
1522 verbose_name
= u
"Responsable d'implantation"
1523 verbose_name_plural
= u
"Responsables d'implantation"
1526 class ResponsableImplantation(models
.Model
):
1528 Le responsable d'une implantation.
1529 Anciennement géré sur le Dossier du responsable.
1531 employe
= models
.ForeignKey(
1532 'Employe', db_column
='employe', related_name
='+', null
=True,
1535 implantation
= models
.OneToOneField(
1536 "ResponsableImplantationProxy", db_column
='implantation',
1537 related_name
='responsable', unique
=True
1540 def __unicode__(self
):
1541 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1544 ordering
= ['implantation__nom']
1545 verbose_name
= "Responsable d'implantation"
1546 verbose_name_plural
= "Responsables d'implantation"