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
.contrib
.auth
.models
import User
12 from django
.core
.files
.storage
import FileSystemStorage
13 from django
.db
import models
14 from django
.db
.models
import Q
15 from django
.db
.models
.signals
import post_save
, pre_save
16 from django
.conf
import settings
18 from project
.rh
.change_list
import \
19 RechercheTemporelle
, KEY_STATUT
, STATUT_ACTIF
, STATUT_INACTIF
, \
21 from project
import groups
22 from project
.rh
.managers
import (
26 DossierComparaisonManager
,
27 PosteComparaisonManager
,
34 TWOPLACES
= Decimal('0.01')
36 from project
.rh
.validators
import validate_date_passee
38 # import pour relocaliser le modèle selon la convention (models.py pour
40 from project
.rh
.historique
import ModificationTraite
43 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
44 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
45 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
46 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
= \
47 "Saisir le nombre d'heure de travail à temps complet (100%), " \
48 "sans tenir compte du régime de travail"
51 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
52 base_url
=settings
.PRIVE_MEDIA_URL
)
55 class RemunIntegrityException(Exception):
58 def poste_piece_dispatch(instance
, filename
):
59 path
= "%s/poste/%s/%s" % (
60 instance
._meta
.app_label
, instance
.poste_id
, filename
65 def dossier_piece_dispatch(instance
, filename
):
66 path
= "%s/dossier/%s/%s" % (
67 instance
._meta
.app_label
, instance
.dossier_id
, filename
72 def employe_piece_dispatch(instance
, filename
):
73 path
= "%s/employe/%s/%s" % (
74 instance
._meta
.app_label
, instance
.employe_id
, filename
79 def contrat_dispatch(instance
, filename
):
80 path
= "%s/contrat/%s/%s" % (
81 instance
._meta
.app_label
, instance
.dossier_id
, filename
86 class DateActiviteMixin(models
.Model
):
88 Mixin pour mettre à jour l'activité d'un modèle
92 date_creation
= models
.DateTimeField(auto_now_add
=True,
93 null
=True, blank
=True,
94 verbose_name
=u
"Date de création",)
95 date_modification
= models
.DateTimeField(auto_now
=True,
96 null
=True, blank
=True,
97 verbose_name
=u
"Date de modification",)
100 class Archivable(models
.Model
):
101 archive
= models
.BooleanField(u
'archivé', default
=False)
103 objects
= ArchivableManager()
104 avec_archives
= models
.Manager()
110 class DevisableMixin(object):
112 def get_annee_pour_taux_devise(self
):
113 return datetime
.datetime
.now().year
115 def taux_devise(self
, devise
=None):
121 if devise
.code
== "EUR":
124 annee
= self
.get_annee_pour_taux_devise()
125 taux
= TauxChange
.objects
.filter(devise
=devise
, annee__lte
=annee
) \
129 def montant_euros_float(self
):
131 taux
= self
.taux_devise()
136 return float(self
.montant
) * float(taux
)
138 def montant_euros(self
):
139 return int(round(self
.montant_euros_float(), 2))
142 class Commentaire(models
.Model
):
143 texte
= models
.TextField()
144 owner
= models
.ForeignKey(
145 'auth.User', db_column
='owner', related_name
='+',
146 verbose_name
=u
"Commentaire de"
148 date_creation
= models
.DateTimeField(
149 u
'date', auto_now_add
=True, blank
=True, null
=True
154 ordering
= ['-date_creation']
156 def __unicode__(self
):
157 return u
'%s' % (self
.texte
)
162 POSTE_APPEL_CHOICES
= (
163 ('interne', 'Interne'),
164 ('externe', 'Externe'),
168 class Poste_( DateActiviteMixin
, models
.Model
,):
170 Un Poste est un emploi (job) à combler dans une implantation.
171 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
172 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
175 objects
= PosteManager()
178 nom
= models
.CharField(u
"Titre du poste", max_length
=255)
179 nom_feminin
= models
.CharField(
180 u
"Titre du poste (au féminin)", max_length
=255, null
=True
182 implantation
= models
.ForeignKey(
184 help_text
=u
"Taper le nom de l'implantation ou sa région",
185 db_column
='implantation', related_name
='+'
187 type_poste
= models
.ForeignKey(
188 'TypePoste', db_column
='type_poste',
189 help_text
=u
"Taper le nom du type de poste", related_name
='+',
190 null
=True, verbose_name
=u
"type de poste"
192 service
= models
.ForeignKey(
193 'Service', db_column
='service', related_name
='%(app_label)s_postes',
194 verbose_name
=u
"direction/service/pôle support", null
=True
196 responsable
= models
.ForeignKey(
197 'Poste', db_column
='responsable',
198 related_name
='+', null
=True,
199 help_text
=u
"Taper le nom du poste ou du type de poste",
200 verbose_name
=u
"Poste du responsable"
204 regime_travail
= models
.DecimalField(
205 u
"temps de travail", max_digits
=12, decimal_places
=2,
206 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
207 help_text
="% du temps complet"
209 regime_travail_nb_heure_semaine
= models
.DecimalField(
210 u
"nb. heures par semaine", max_digits
=12, decimal_places
=2,
211 null
=True, default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
212 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
216 local
= models
.NullBooleanField(
217 u
"local", default
=True, null
=True, blank
=True
219 expatrie
= models
.NullBooleanField(
220 u
"expatrié", default
=False, null
=True, blank
=True
222 mise_a_disposition
= models
.NullBooleanField(
223 u
"mise à disposition", null
=True, default
=False
225 appel
= models
.CharField(
226 u
"Appel à candidature", max_length
=10, null
=True,
227 choices
=POSTE_APPEL_CHOICES
, default
='interne'
231 classement_min
= models
.ForeignKey(
232 'Classement', db_column
='classement_min', related_name
='+',
233 null
=True, blank
=True
235 classement_max
= models
.ForeignKey(
236 'Classement', db_column
='classement_max', related_name
='+',
237 null
=True, blank
=True
239 valeur_point_min
= models
.ForeignKey(
241 help_text
=u
"Taper le code ou le nom de l'implantation",
242 db_column
='valeur_point_min', related_name
='+', null
=True,
245 valeur_point_max
= models
.ForeignKey(
247 help_text
=u
"Taper le code ou le nom de l'implantation",
248 db_column
='valeur_point_max', related_name
='+', null
=True,
251 devise_min
= models
.ForeignKey(
252 'Devise', db_column
='devise_min', null
=True, related_name
='+'
254 devise_max
= models
.ForeignKey(
255 'Devise', db_column
='devise_max', null
=True, related_name
='+'
257 salaire_min
= models
.DecimalField(
258 max_digits
=12, decimal_places
=2, default
=0,
260 salaire_max
= models
.DecimalField(
261 max_digits
=12, decimal_places
=2, default
=0,
263 indemn_min
= models
.DecimalField(
264 max_digits
=12, decimal_places
=2, default
=0,
266 indemn_max
= models
.DecimalField(
267 max_digits
=12, decimal_places
=2, default
=0,
269 autre_min
= models
.DecimalField(
270 max_digits
=12, decimal_places
=2, default
=0,
272 autre_max
= models
.DecimalField(
273 max_digits
=12, decimal_places
=2, default
=0,
276 # Comparatifs de rémunération
277 devise_comparaison
= models
.ForeignKey(
278 'Devise', null
=True, blank
=True, db_column
='devise_comparaison',
281 comp_locale_min
= models
.DecimalField(
282 max_digits
=12, decimal_places
=2, null
=True, blank
=True
284 comp_locale_max
= models
.DecimalField(
285 max_digits
=12, decimal_places
=2, null
=True, blank
=True
287 comp_universite_min
= models
.DecimalField(
288 max_digits
=12, decimal_places
=2, null
=True, blank
=True
290 comp_universite_max
= models
.DecimalField(
291 max_digits
=12, decimal_places
=2, null
=True, blank
=True
293 comp_fonctionpub_min
= models
.DecimalField(
294 max_digits
=12, decimal_places
=2, null
=True, blank
=True
296 comp_fonctionpub_max
= models
.DecimalField(
297 max_digits
=12, decimal_places
=2, null
=True, blank
=True
299 comp_ong_min
= models
.DecimalField(
300 max_digits
=12, decimal_places
=2, null
=True, blank
=True
302 comp_ong_max
= models
.DecimalField(
303 max_digits
=12, decimal_places
=2, null
=True, blank
=True
305 comp_autre_min
= models
.DecimalField(
306 max_digits
=12, decimal_places
=2, null
=True, blank
=True
308 comp_autre_max
= models
.DecimalField(
309 max_digits
=12, decimal_places
=2, null
=True, blank
=True
313 justification
= models
.TextField(null
=True, blank
=True)
316 date_debut
= models
.DateField(
317 u
"date de début", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True,
320 date_fin
= models
.DateField(
321 u
"date de fin", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True,
327 ordering
= ['implantation__nom', 'nom']
328 verbose_name
= u
"Poste"
329 verbose_name_plural
= u
"Postes"
332 def __unicode__(self
):
333 representation
= u
'%s - %s [%s]' % (
334 self
.implantation
, self
.nom
, self
.id
336 return representation
338 prefix_implantation
= "implantation__zone_administrative"
340 def get_zones_administratives(self
):
341 return [self
.implantation
.zone_administrative
]
343 def get_devise(self
):
344 vp
= ValeurPoint
.objects
.filter(
345 implantation
=self
.implantation
, devise__archive
=False
350 return Devise
.objects
.get(code
='EUR')
354 __doc__
= Poste_
.__doc__
356 # meta dématérialisation : pour permettre le filtrage
357 vacant
= models
.NullBooleanField(u
"vacant", null
=True, blank
=True)
361 if self
.occupe_par():
365 def occupe_par(self
):
367 Retourne la liste d'employé occupant ce poste.
368 Généralement, retourne une liste d'un élément.
369 Si poste inoccupé, retourne liste vide.
370 UTILISE pour mettre a jour le flag vacant
374 for d
in self
.rh_dossiers
.exclude(date_fin__lt
=date
.today())
377 reversion
.register(Poste
, format
='xml', follow
=[
378 'rh_financements', 'rh_pieces', 'rh_comparaisons_internes',
383 POSTE_FINANCEMENT_CHOICES
= (
384 ('A', 'A - Frais de personnel'),
385 ('B', 'B - Projet(s)-Titre(s)'),
390 class PosteFinancement_(models
.Model
):
392 Pour un Poste, structure d'informations décrivant comment on prévoit
395 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
396 pourcentage
= models
.DecimalField(
397 max_digits
=12, decimal_places
=2,
398 help_text
="ex.: 33.33 % (décimale avec point)"
400 commentaire
= models
.TextField(
401 help_text
="Spécifiez la source de financement."
408 def __unicode__(self
):
409 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
412 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
415 class PosteFinancement(PosteFinancement_
):
416 poste
= models
.ForeignKey(
417 Poste
, db_column
='poste', related_name
='rh_financements'
420 reversion
.register(PosteFinancement
, format
='xml')
423 class PostePiece_(models
.Model
):
425 Documents relatifs au Poste.
426 Ex.: Description de poste
428 nom
= models
.CharField(u
"Nom", max_length
=255)
429 fichier
= models
.FileField(
430 u
"Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
437 def __unicode__(self
):
438 return u
'%s' % (self
.nom
)
441 class PostePiece(PostePiece_
):
442 poste
= models
.ForeignKey(
443 Poste
, db_column
='poste', related_name
='rh_pieces'
446 reversion
.register(PostePiece
, format
='xml')
449 class PosteComparaison_(models
.Model
, DevisableMixin
):
451 De la même manière qu'un dossier, un poste peut-être comparé à un autre
454 objects
= PosteComparaisonManager()
456 implantation
= models
.ForeignKey(
457 ref
.Implantation
, null
=True, blank
=True, related_name
="+"
459 nom
= models
.CharField(u
"Poste", max_length
=255, null
=True, blank
=True)
460 montant
= models
.IntegerField(null
=True)
461 devise
= models
.ForeignKey(
462 "Devise", related_name
='+', null
=True, blank
=True
468 def __unicode__(self
):
472 class PosteComparaison(PosteComparaison_
):
473 poste
= models
.ForeignKey(
474 Poste
, related_name
='rh_comparaisons_internes'
477 reversion
.register(PosteComparaison
, format
='xml')
480 class PosteCommentaire(Commentaire
):
481 poste
= models
.ForeignKey(
482 Poste
, db_column
='poste', related_name
='commentaires'
485 reversion
.register(PosteCommentaire
, format
='xml')
489 class Employe(models
.Model
):
491 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
492 Dossiers qu'il occupe ou a occupé de Postes.
494 Cette classe aurait pu avantageusement s'appeler Personne car la notion
495 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
498 objects
= EmployeManager()
501 nom
= models
.CharField(max_length
=255)
502 prenom
= models
.CharField(u
"prénom", max_length
=255)
503 nom_affichage
= models
.CharField(
504 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
506 nationalite
= models
.ForeignKey(
507 ref
.Pays
, to_field
='code', db_column
='nationalite',
508 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
509 blank
=True, null
=True
511 date_naissance
= models
.DateField(
512 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
513 validators
=[validate_date_passee
], null
=True, blank
=True
515 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
518 situation_famille
= models
.CharField(
519 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
520 null
=True, blank
=True
522 date_entree
= models
.DateField(
523 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
528 tel_domicile
= models
.CharField(
529 u
"tél. domicile", max_length
=255, null
=True, blank
=True
531 tel_cellulaire
= models
.CharField(
532 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
534 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
535 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
536 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
537 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
538 pays
= models
.ForeignKey(
539 ref
.Pays
, to_field
='code', db_column
='pays',
540 related_name
='employes', null
=True, blank
=True
542 courriel_perso
= models
.EmailField(
543 u
'adresse courriel personnelle', blank
=True
546 # meta dématérialisation : pour permettre le filtrage
547 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
550 ordering
= ['nom', 'prenom']
551 verbose_name
= u
"Employé"
552 verbose_name_plural
= u
"Employés"
554 def __unicode__(self
):
555 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
559 if self
.genre
.upper() == u
'M':
561 elif self
.genre
.upper() == u
'F':
567 Retourne l'URL du service retournant la photo de l'Employe.
568 Équivalent reverse url 'rh_photo' avec id en param.
570 from django
.core
.urlresolvers
import reverse
571 return reverse('rh_photo', kwargs
={'id': self
.id})
573 def dossiers_passes(self
):
574 params
= {KEY_STATUT
: STATUT_INACTIF
, }
575 search
= RechercheTemporelle(params
, Dossier
)
576 search
.purge_params(params
)
577 q
= search
.get_q_temporel(self
.rh_dossiers
)
578 return self
.rh_dossiers
.filter(q
)
580 def dossiers_futurs(self
):
581 params
= {KEY_STATUT
: STATUT_FUTUR
, }
582 search
= RechercheTemporelle(params
, Dossier
)
583 search
.purge_params(params
)
584 q
= search
.get_q_temporel(self
.rh_dossiers
)
585 return self
.rh_dossiers
.filter(q
)
587 def dossiers_encours(self
):
588 params
= {KEY_STATUT
: STATUT_ACTIF
, }
589 search
= RechercheTemporelle(params
, Dossier
)
590 search
.purge_params(params
)
591 q
= search
.get_q_temporel(self
.rh_dossiers
)
592 return self
.rh_dossiers
.filter(q
)
594 def dossier_principal(self
, year
=None):
596 Retourne le dossier principal (ou le plus ancien si il y en a
600 year
= date
.today().year
601 year_start
= date(year
, 1, 1)
602 year_end
= date(year
, 12, 31)
605 dossier
= self
.rh_dossiers
.filter(
606 (Q(date_debut__lte
=year_end
, date_fin
=None) |
607 Q(date_debut
=None, date_fin__gte
=year_start
) |
608 Q(date_debut
=None, date_fin__gte
=year_start
) |
609 Q(date_debut__lte
=year_end
, date_fin__gte
=year_start
) |
610 Q(date_debut
=None, date_fin
=None)) &
611 Q(principal
=True)).order_by('date_debut')[0]
612 except IndexError, Dossier
.DoesNotExist
:
616 def postes_encours(self
):
617 postes_encours
= set()
618 for d
in self
.dossiers_encours():
619 postes_encours
.add(d
.poste
)
620 return postes_encours
622 def poste_principal(self
):
624 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
626 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
628 # DEPRECATED : on a maintenant Dossier.principal
629 poste
= Poste
.objects
.none()
631 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
636 prefix_implantation
= \
637 "rh_dossiers__poste__implantation__zone_administrative"
639 def get_zones_administratives(self
):
641 d
.poste
.implantation
.zone_administrative
642 for d
in self
.dossiers
.all()
645 reversion
.register(Employe
, format
='xml', follow
=[
646 'pieces', 'commentaires', 'ayantdroits'
650 class EmployePiece(models
.Model
):
652 Documents relatifs à un employé.
655 employe
= models
.ForeignKey(
656 'Employe', db_column
='employe', related_name
="pieces",
657 verbose_name
=u
"employé"
659 nom
= models
.CharField(max_length
=255)
660 fichier
= models
.FileField(
661 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
666 verbose_name
= u
"Employé pièce"
667 verbose_name_plural
= u
"Employé pièces"
669 def __unicode__(self
):
670 return u
'%s' % (self
.nom
)
672 reversion
.register(EmployePiece
, format
='xml')
675 class EmployeCommentaire(Commentaire
):
676 employe
= models
.ForeignKey(
677 'Employe', db_column
='employe', related_name
='commentaires'
681 verbose_name
= u
"Employé commentaire"
682 verbose_name_plural
= u
"Employé commentaires"
684 reversion
.register(EmployeCommentaire
, format
='xml')
687 LIEN_PARENTE_CHOICES
= (
688 ('Conjoint', 'Conjoint'),
689 ('Conjointe', 'Conjointe'),
695 class AyantDroit(models
.Model
):
697 Personne en relation avec un Employe.
700 nom
= models
.CharField(max_length
=255)
701 prenom
= models
.CharField(u
"prénom", max_length
=255)
702 nom_affichage
= models
.CharField(
703 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
705 nationalite
= models
.ForeignKey(
706 ref
.Pays
, to_field
='code', db_column
='nationalite',
707 related_name
='ayantdroits_nationalite',
708 verbose_name
=u
"nationalité", null
=True, blank
=True
710 date_naissance
= models
.DateField(
711 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
712 validators
=[validate_date_passee
], null
=True, blank
=True
714 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
717 employe
= models
.ForeignKey(
718 'Employe', db_column
='employe', related_name
='ayantdroits',
719 verbose_name
=u
"Employé"
721 lien_parente
= models
.CharField(
722 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
723 null
=True, blank
=True
728 verbose_name
= u
"Ayant droit"
729 verbose_name_plural
= u
"Ayants droit"
731 def __unicode__(self
):
732 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
734 prefix_implantation
= \
735 "employe__dossiers__poste__implantation__zone_administrative"
737 def get_zones_administratives(self
):
739 d
.poste
.implantation
.zone_administrative
740 for d
in self
.employe
.dossiers
.all()
743 reversion
.register(AyantDroit
, format
='xml', follow
=['commentaires'])
746 class AyantDroitCommentaire(Commentaire
):
747 ayant_droit
= models
.ForeignKey(
748 'AyantDroit', db_column
='ayant_droit', related_name
='commentaires'
751 reversion
.register(AyantDroitCommentaire
, format
='xml')
756 STATUT_RESIDENCE_CHOICES
= (
758 ('expat', 'Expatrié'),
761 COMPTE_COMPTA_CHOICES
= (
768 class Dossier_(DateActiviteMixin
, models
.Model
, DevisableMixin
,):
770 Le Dossier regroupe les informations relatives à l'occupation
771 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
774 Plusieurs Contrats peuvent être associés au Dossier.
775 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
776 lequel aucun Dossier n'existe est un poste vacant.
779 objects
= DossierManager()
782 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
783 organisme_bstg
= models
.ForeignKey(
784 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
785 verbose_name
=u
"organisme",
787 u
"Si détaché (DET) ou mis à disposition (MAD), "
788 u
"préciser l'organisme."
789 ), null
=True, blank
=True
793 remplacement
= models
.BooleanField(default
=False)
794 remplacement_de
= models
.ForeignKey(
795 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
796 null
=True, blank
=True
798 statut_residence
= models
.CharField(
799 u
"statut", max_length
=10, default
='local', null
=True,
800 choices
=STATUT_RESIDENCE_CHOICES
804 classement
= models
.ForeignKey(
805 'Classement', db_column
='classement', related_name
='+', null
=True,
808 regime_travail
= models
.DecimalField(
809 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
810 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
812 regime_travail_nb_heure_semaine
= models
.DecimalField(
813 u
"nb. heures par semaine", max_digits
=12,
814 decimal_places
=2, null
=True,
815 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
816 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
819 # Occupation du Poste par cet Employe (anciennement "mandat")
820 date_debut
= models
.DateField(
821 u
"date de début d'occupation de poste", db_index
=True
823 date_fin
= models
.DateField(
824 u
"Date de fin d'occupation de poste", null
=True, blank
=True,
829 est_cadre
= models
.BooleanField(
839 ordering
= ['employe__nom', ]
840 verbose_name
= u
"Dossier"
841 verbose_name_plural
= "Dossiers"
843 def salaire_theorique(self
):
844 annee
= date
.today().year
845 coeff
= self
.classement
.coefficient
846 implantation
= self
.poste
.implantation
847 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
849 montant
= coeff
* point
.valeur
850 devise
= point
.devise
851 return {'montant': montant
, 'devise': devise
}
853 def __unicode__(self
):
854 poste
= self
.poste
.nom
855 if self
.employe
.genre
== 'F':
856 poste
= self
.poste
.nom_feminin
857 return u
'%s - %s' % (self
.employe
, poste
)
859 prefix_implantation
= "poste__implantation__zone_administrative"
861 def get_zones_administratives(self
):
862 return [self
.poste
.implantation
.zone_administrative
]
864 def remunerations(self
):
865 key
= "%s_remunerations" % self
._meta
.app_label
866 remunerations
= getattr(self
, key
)
867 return remunerations
.all().order_by('-date_debut')
869 def remunerations_en_cours(self
):
870 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
871 return self
.remunerations().all().filter(q
).order_by('date_debut')
873 def get_salaire(self
):
875 return [r
for r
in self
.remunerations().order_by('-date_debut')
876 if r
.type_id
== 1][0]
880 def get_salaire_euros(self
):
881 tx
= self
.taux_devise()
882 return (float)(tx
) * (float)(self
.salaire
)
884 def get_remunerations_brutes(self
):
888 4 Indemnité d'expatriation
889 5 Indemnité pour frais
890 6 Indemnité de logement
891 7 Indemnité de fonction
892 8 Indemnité de responsabilité
893 9 Indemnité de transport
894 10 Indemnité compensatrice
895 11 Indemnité de subsistance
896 12 Indemnité différentielle
897 13 Prime d'installation
900 16 Indemnité de départ
901 18 Prime de 13ième mois
904 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
905 return [r
for r
in self
.remunerations_en_cours().all()
908 def get_charges_salariales(self
):
910 20 Charges salariales ?
913 return [r
for r
in self
.remunerations_en_cours().all()
916 def get_charges_patronales(self
):
918 17 Charges patronales
921 return [r
for r
in self
.remunerations_en_cours().all()
924 def get_remunerations_tierces(self
):
928 return [r
for r
in self
.remunerations_en_cours().all()
929 if r
.type_id
in (2,)]
933 def get_total_local_charges_salariales(self
):
934 devise
= self
.poste
.get_devise()
936 for r
in self
.get_charges_salariales():
937 if r
.devise
!= devise
:
939 total
+= float(r
.montant
)
942 def get_total_local_charges_patronales(self
):
943 devise
= self
.poste
.get_devise()
945 for r
in self
.get_charges_patronales():
946 if r
.devise
!= devise
:
948 total
+= float(r
.montant
)
951 def get_local_salaire_brut(self
):
953 somme des rémuérations brutes
955 devise
= self
.poste
.get_devise()
957 for r
in self
.get_remunerations_brutes():
958 if r
.devise
!= devise
:
960 total
+= float(r
.montant
)
963 def get_local_salaire_net(self
):
965 salaire brut - charges salariales
967 devise
= self
.poste
.get_devise()
969 for r
in self
.get_charges_salariales():
970 if r
.devise
!= devise
:
972 total_charges
+= float(r
.montant
)
973 return self
.get_local_salaire_brut() - total_charges
975 def get_local_couts_auf(self
):
977 salaire net + charges patronales
979 devise
= self
.poste
.get_devise()
981 for r
in self
.get_charges_patronales():
982 if r
.devise
!= devise
:
984 total_charges
+= float(r
.montant
)
985 return self
.get_local_salaire_net() + total_charges
987 def get_total_local_remunerations_tierces(self
):
988 devise
= self
.poste
.get_devise()
990 for r
in self
.get_remunerations_tierces():
991 if r
.devise
!= devise
:
993 total
+= float(r
.montant
)
998 def get_total_charges_salariales(self
):
1000 for r
in self
.get_charges_salariales():
1001 total
+= r
.montant_euros()
1004 def get_total_charges_patronales(self
):
1006 for r
in self
.get_charges_patronales():
1007 total
+= r
.montant_euros()
1010 def get_salaire_brut(self
):
1012 somme des rémuérations brutes
1015 for r
in self
.get_remunerations_brutes():
1016 total
+= r
.montant_euros()
1019 def get_salaire_net(self
):
1021 salaire brut - charges salariales
1024 for r
in self
.get_charges_salariales():
1025 total_charges
+= r
.montant_euros()
1026 return self
.get_salaire_brut() - total_charges
1028 def get_couts_auf(self
):
1030 salaire net + charges patronales
1033 for r
in self
.get_charges_patronales():
1034 total_charges
+= r
.montant_euros()
1035 return self
.get_salaire_net() + total_charges
1037 def get_total_remunerations_tierces(self
):
1039 for r
in self
.get_remunerations_tierces():
1040 total
+= r
.montant_euros()
1043 def premier_contrat(self
):
1044 """contrat avec plus petite date de début"""
1046 contrat
= self
.rh_contrats
.exclude(date_debut
=None) \
1047 .order_by('date_debut')[0]
1048 except IndexError, Contrat
.DoesNotExist
:
1052 def dernier_contrat(self
):
1053 """contrat avec plus grande date de fin"""
1055 contrat
= self
.rh_contrats
.exclude(date_debut
=None) \
1056 .order_by('-date_debut')[0]
1057 except IndexError, Contrat
.DoesNotExist
:
1062 today
= date
.today()
1063 return (self
.date_debut
is None or self
.date_debut
<= today
) \
1064 and (self
.date_fin
is None or self
.date_fin
>= today
) \
1065 and not (self
.date_fin
is None and self
.date_debut
is None)
1068 class Dossier(Dossier_
):
1069 __doc__
= Dossier_
.__doc__
1070 poste
= models
.ForeignKey(
1071 Poste
, db_column
='poste', related_name
='rh_dossiers',
1072 help_text
=u
"Taper le nom du poste ou du type de poste",
1074 employe
= models
.ForeignKey(
1075 'Employe', db_column
='employe',
1076 help_text
=u
"Taper le nom de l'employé",
1077 related_name
='rh_dossiers', verbose_name
=u
"employé"
1079 principal
= models
.BooleanField(
1080 u
"dossier principal", default
=True,
1082 u
"Ce dossier est pour le principal poste occupé par l'employé"
1087 reversion
.register(Dossier
, format
='xml', follow
=[
1088 'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
1089 'rh_contrats', 'commentaires'
1093 class RHDossierClassementRecord(models
.Model
):
1094 classement
= models
.ForeignKey(
1096 related_name
='classement_records',
1098 dossier
= models
.ForeignKey(
1100 related_name
='classement_records',
1102 date_debut
= models
.DateField(
1104 help_text
=HELP_TEXT_DATE
,
1109 date_fin
= models
.DateField(
1111 help_text
=HELP_TEXT_DATE
,
1117 def __unicode__(self
):
1118 return self
.classement
.__unicode__()
1121 verbose_name
= u
"Element d'historique de classement"
1122 verbose_name_plural
= u
"Historique de classement"
1125 def post_save_handler(cls
,
1132 today
= date
.today()
1133 previous_record
= None
1134 previous_classement
= None
1137 # Premièrement, pour les nouvelles instances:
1139 if not instance
.classement
:
1143 date_debut
=instance
.date_debut
,
1144 classement
=instance
.classement
,
1149 # Deuxièmement, pour les instances existantes:
1152 # 1. Est-ce que le classement a changé?
1153 # 2. Est-ce qu'une historique de classement existe déjà
1155 previous_record
= cls
.objects
.get(
1157 classement
=instance
.before_save
.classement
,
1160 except cls
.DoesNotExist
:
1161 if instance
.before_save
.classement
:
1162 # Il était censé avoir une historique de classement
1164 previous_record
= cls
.objects
.create(
1165 date_debut
=instance
.before_save
.date_debut
,
1166 classement
=instance
.before_save
.classement
,
1169 previous_classement
= instance
.before_save
.classement
1172 previous_classement
= previous_record
.classement
1175 instance
.classement
!=
1179 # Cas aucun changement:
1184 # Classement a changé
1186 previous_record
.date_fin
= today
1187 previous_record
.save()
1189 if instance
.classement
:
1192 classement
=instance
.classement
,
1197 class DossierPiece_(models
.Model
):
1199 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1200 Ex.: Lettre de motivation.
1202 nom
= models
.CharField(max_length
=255)
1203 fichier
= models
.FileField(
1204 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
1211 def __unicode__(self
):
1212 return u
'%s' % (self
.nom
)
1215 class DossierPiece(DossierPiece_
):
1216 dossier
= models
.ForeignKey(
1217 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
1220 reversion
.register(DossierPiece
, format
='xml')
1222 class DossierCommentaire(Commentaire
):
1223 dossier
= models
.ForeignKey(
1224 Dossier
, db_column
='dossier', related_name
='commentaires'
1227 reversion
.register(DossierCommentaire
, format
='xml')
1230 class DossierComparaison_(models
.Model
, DevisableMixin
):
1232 Photo d'une comparaison salariale au moment de l'embauche.
1234 objects
= DossierComparaisonManager()
1236 implantation
= models
.ForeignKey(
1237 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1239 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1240 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1241 montant
= models
.IntegerField(null
=True)
1242 devise
= models
.ForeignKey(
1243 'Devise', related_name
='+', null
=True, blank
=True
1249 def __unicode__(self
):
1250 return "%s (%s)" % (self
.poste
, self
.personne
)
1253 class DossierComparaison(DossierComparaison_
):
1254 dossier
= models
.ForeignKey(
1255 Dossier
, related_name
='rh_comparaisons'
1258 reversion
.register(DossierComparaison
, format
='xml')
1263 class RemunerationMixin(models
.Model
):
1266 type = models
.ForeignKey(
1267 'TypeRemuneration', db_column
='type', related_name
='+',
1268 verbose_name
=u
"type de rémunération"
1270 type_revalorisation
= models
.ForeignKey(
1271 'TypeRevalorisation', db_column
='type_revalorisation',
1272 related_name
='+', verbose_name
=u
"type de revalorisation",
1273 null
=True, blank
=True
1275 montant
= models
.DecimalField(
1276 null
=True, blank
=True, max_digits
=12, decimal_places
=2
1277 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1278 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1280 # commentaire = precision
1281 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1283 # date_debut = anciennement date_effectif
1284 date_debut
= models
.DateField(
1285 u
"date de début", null
=True, blank
=True, db_index
=True
1287 date_fin
= models
.DateField(
1288 u
"date de fin", null
=True, blank
=True, db_index
=True
1293 ordering
= ['type__nom', '-date_fin']
1295 def __unicode__(self
):
1296 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1299 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1301 Structure de rémunération (données budgétaires) en situation normale
1302 pour un Dossier. Si un Evenement existe, utiliser la structure de
1303 rémunération EvenementRemuneration de cet événement.
1305 objects
= RemunerationManager()
1308 def find_yearly_range(from_date
, to_date
, year
):
1309 today
= date
.today()
1310 year
= year
or date
.today().year
1311 year_start
= date(year
, 1, 1)
1312 year_end
= date(year
, 12, 31)
1314 def constrain_to_year(*dates
):
1316 S'assure que les dates soient dans le range year_start a
1319 return [min(max(year_start
, d
), year_end
)
1323 from_date
or year_start
, year_start
)
1325 to_date
or year_end
, year_end
)
1327 start_date
, end_date
= constrain_to_year(start_date
, end_date
)
1329 jours_annee
= (year_end
- year_start
).days
1330 jours_dates
= (end_date
- start_date
).days
1331 factor
= Decimal(str(jours_dates
)) / Decimal(str(jours_annee
))
1333 return start_date
, end_date
, factor
1336 def montant_ajuste_euros(self
, annee
=None):
1338 Le montant ajusté représente le montant annuel, ajusté sur la
1339 période de temps travaillée, multipliée par le ratio de temps
1340 travaillé (en rapport au temps plein).
1342 date_debut
, date_fin
, factor
= self
.find_yearly_range(
1348 montant_euros
= Decimal(str(self
.montant_euros_float()) or '0')
1350 if self
.type.nature_remuneration
!= u
'Accessoire':
1351 dossier
= getattr(self
, 'dossier', None)
1354 Dans le cas d'un DossierComparaisonRemuneration, il
1355 n'y a plus de reference au dossier.
1357 regime_travail
= REGIME_TRAVAIL_DEFAULT
1359 regime_travail
= self
.dossier
.regime_travail
1360 return (montant_euros
* factor
*
1361 regime_travail
/ 100)
1363 return montant_euros
1365 def montant_mois(self
):
1366 return round(self
.montant
/ 12, 2)
1368 def montant_avec_regime(self
):
1369 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1371 def montant_euro_mois(self
):
1372 return round(self
.montant_euros() / 12, 2)
1374 def __unicode__(self
):
1376 devise
= self
.devise
.code
1379 return "%s %s" % (self
.montant
, devise
)
1383 verbose_name
= u
"Rémunération"
1384 verbose_name_plural
= u
"Rémunérations"
1387 class Remuneration(Remuneration_
):
1388 dossier
= models
.ForeignKey(
1389 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1392 reversion
.register(Remuneration
, format
='xml')
1397 class Contrat_(models
.Model
):
1399 Document juridique qui encadre la relation de travail d'un Employe
1400 pour un Poste particulier. Pour un Dossier (qui documente cette
1401 relation de travail) plusieurs contrats peuvent être associés.
1403 objects
= ContratManager()
1404 type_contrat
= models
.ForeignKey(
1405 'TypeContrat', db_column
='type_contrat',
1406 verbose_name
=u
'type de contrat', related_name
='+'
1408 date_debut
= models
.DateField(
1409 u
"date de début", db_index
=True
1411 date_fin
= models
.DateField(
1412 u
"date de fin", null
=True, blank
=True, db_index
=True
1414 fichier
= models
.FileField(
1415 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1421 ordering
= ['dossier__employe__nom']
1422 verbose_name
= u
"Contrat"
1423 verbose_name_plural
= u
"Contrats"
1425 def __unicode__(self
):
1426 return u
'%s - %s' % (self
.dossier
, self
.id)
1429 class Contrat(Contrat_
):
1430 dossier
= models
.ForeignKey(
1431 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1434 reversion
.register(Contrat
, format
='xml')
1439 class CategorieEmploi(models
.Model
):
1441 Catégorie utilisée dans la gestion des Postes.
1442 Catégorie supérieure à TypePoste.
1444 nom
= models
.CharField(max_length
=255)
1448 verbose_name
= u
"catégorie d'emploi"
1449 verbose_name_plural
= u
"catégories d'emploi"
1451 def __unicode__(self
):
1454 reversion
.register(CategorieEmploi
, format
='xml')
1457 class FamilleProfessionnelle(models
.Model
):
1459 Famille professionnelle d'un poste.
1461 nom
= models
.CharField(max_length
=100)
1465 verbose_name
= u
'famille professionnelle'
1466 verbose_name_plural
= u
'familles professionnelles'
1468 def __unicode__(self
):
1471 reversion
.register(FamilleProfessionnelle
, format
='xml')
1474 class TypePoste(Archivable
):
1478 nom
= models
.CharField(max_length
=255)
1479 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1480 is_responsable
= models
.BooleanField(
1481 u
"poste de responsabilité", default
=False
1483 categorie_emploi
= models
.ForeignKey(
1484 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1485 verbose_name
=u
"catégorie d'emploi"
1487 famille_professionnelle
= models
.ForeignKey(
1488 FamilleProfessionnelle
, related_name
='types_de_poste',
1489 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1494 verbose_name
= u
"Type de poste"
1495 verbose_name_plural
= u
"Types de poste"
1497 def __unicode__(self
):
1498 return u
'%s' % (self
.nom
)
1500 reversion
.register(TypePoste
, format
='xml')
1503 TYPE_PAIEMENT_CHOICES
= (
1504 (u
'Régulier', u
'Régulier'),
1505 (u
'Ponctuel', u
'Ponctuel'),
1508 NATURE_REMUNERATION_CHOICES
= (
1509 (u
'Traitement', u
'Traitements'),
1510 (u
'Indemnité', u
'Indemnités autres'),
1511 (u
'Charges', u
'Charges patronales'),
1512 (u
'Accessoire', u
'Accessoires'),
1513 (u
'RAS', u
'Rémunération autre source'),
1517 class TypeRemuneration(Archivable
):
1519 Catégorie de Remuneration.
1522 objects
= models
.Manager()
1523 sans_archives
= ArchivableManager()
1525 nom
= models
.CharField(max_length
=255)
1526 type_paiement
= models
.CharField(
1527 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1530 nature_remuneration
= models
.CharField(
1531 u
"nature de la rémunération", max_length
=30,
1532 choices
=NATURE_REMUNERATION_CHOICES
1537 verbose_name
= u
"Type de rémunération"
1538 verbose_name_plural
= u
"Types de rémunération"
1540 def __unicode__(self
):
1543 reversion
.register(TypeRemuneration
, format
='xml')
1546 class TypeRevalorisation(Archivable
):
1548 Justification du changement de la Remuneration.
1549 (Actuellement utilisé dans aucun traitement informatique.)
1551 nom
= models
.CharField(max_length
=255)
1555 verbose_name
= u
"Type de revalorisation"
1556 verbose_name_plural
= u
"Types de revalorisation"
1558 def __unicode__(self
):
1559 return u
'%s' % (self
.nom
)
1561 reversion
.register(TypeRevalorisation
, format
='xml')
1564 class Service(Archivable
):
1566 Unité administrative où les Postes sont rattachés.
1568 nom
= models
.CharField(max_length
=255)
1572 verbose_name
= u
"service"
1573 verbose_name_plural
= u
"services"
1575 def __unicode__(self
):
1578 reversion
.register(Service
, format
='xml')
1581 TYPE_ORGANISME_CHOICES
= (
1582 ('MAD', 'Mise à disposition'),
1583 ('DET', 'Détachement'),
1587 class OrganismeBstg(models
.Model
):
1589 Organisation d'où provient un Employe mis à disposition (MAD) de
1590 ou détaché (DET) à l'AUF à titre gratuit.
1592 (BSTG = bien et service à titre gratuit.)
1594 nom
= models
.CharField(max_length
=255)
1595 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1596 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1598 related_name
='organismes_bstg',
1599 null
=True, blank
=True)
1602 ordering
= ['type', 'nom']
1603 verbose_name
= u
"Organisme BSTG"
1604 verbose_name_plural
= u
"Organismes BSTG"
1606 def __unicode__(self
):
1607 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1609 reversion
.register(OrganismeBstg
, format
='xml')
1612 class Statut(Archivable
):
1614 Statut de l'Employe dans le cadre d'un Dossier particulier.
1617 code
= models
.CharField(
1618 max_length
=25, unique
=True,
1620 u
"Saisir un code court mais lisible pour ce statut : "
1621 u
"le code est utilisé pour associer les statuts aux autres "
1622 u
"données tout en demeurant plus lisible qu'un identifiant "
1626 nom
= models
.CharField(max_length
=255)
1630 verbose_name
= u
"Statut d'employé"
1631 verbose_name_plural
= u
"Statuts d'employé"
1633 def __unicode__(self
):
1634 return u
'%s : %s' % (self
.code
, self
.nom
)
1636 reversion
.register(Statut
, format
='xml')
1639 TYPE_CLASSEMENT_CHOICES
= (
1640 ('S', 'S -Soutien'),
1641 ('T', 'T - Technicien'),
1642 ('P', 'P - Professionel'),
1644 ('D', 'D - Direction'),
1645 ('SO', 'SO - Sans objet [expatriés]'),
1646 ('HG', 'HG - Hors grille [direction]'),
1650 class ClassementManager(models
.Manager
):
1652 Ordonner les spcéfiquement les classements.
1654 def get_query_set(self
):
1655 qs
= super(ClassementManager
, self
).get_query_set()
1656 qs
= qs
.extra(select
={
1657 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1659 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1663 class ClassementArchivableManager(ClassementManager
,
1668 class Classement_(Archivable
):
1670 Éléments de classement de la
1671 "Grille générique de classement hiérarchique".
1673 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1674 classement dans la grille. Le classement donne le coefficient utilisé dans:
1676 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1678 objects
= ClassementManager()
1679 sans_archives
= ClassementArchivableManager()
1682 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1683 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1684 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1685 coefficient
= models
.FloatField(u
"coefficient", blank
=True, null
=True)
1688 # annee # au lieu de date_debut et date_fin
1689 commentaire
= models
.TextField(null
=True, blank
=True)
1693 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1694 verbose_name
= u
"Classement"
1695 verbose_name_plural
= u
"Classements"
1697 def __unicode__(self
):
1698 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1701 class Classement(Classement_
):
1702 __doc__
= Classement_
.__doc__
1704 reversion
.register(Classement
, format
='xml')
1707 class TauxChange_(models
.Model
):
1709 Taux de change de la devise vers l'euro (EUR)
1710 pour chaque année budgétaire.
1713 devise
= models
.ForeignKey('Devise', db_column
='devise')
1714 annee
= models
.IntegerField(u
"année")
1715 taux
= models
.FloatField(u
"taux vers l'euro")
1719 ordering
= ['-annee', 'devise__code']
1720 verbose_name
= u
"Taux de change"
1721 verbose_name_plural
= u
"Taux de change"
1722 unique_together
= ('devise', 'annee')
1724 def __unicode__(self
):
1725 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1728 class TauxChange(TauxChange_
):
1729 __doc__
= TauxChange_
.__doc__
1731 reversion
.register(TauxChange
, format
='xml')
1734 class ValeurPointManager(models
.Manager
):
1736 def get_query_set(self
):
1737 return super(ValeurPointManager
, self
).get_query_set() \
1738 .select_related('devise', 'implantation')
1741 class ValeurPoint_(models
.Model
):
1743 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1744 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1745 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1747 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1750 objects
= models
.Manager()
1751 actuelles
= ValeurPointManager()
1753 valeur
= models
.FloatField(null
=True)
1754 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1755 implantation
= models
.ForeignKey(ref
.Implantation
,
1756 db_column
='implantation',
1757 related_name
='%(app_label)s_valeur_point')
1759 annee
= models
.IntegerField()
1762 ordering
= ['-annee', 'implantation__nom']
1764 verbose_name
= u
"Valeur du point"
1765 verbose_name_plural
= u
"Valeurs du point"
1766 unique_together
= ('implantation', 'annee')
1768 def __unicode__(self
):
1769 return u
'%s %s %s [%s] %s' % (
1770 self
.devise
.code
, self
.annee
, self
.valeur
,
1771 self
.implantation
.nom_court
, self
.devise
.nom
1775 class ValeurPoint(ValeurPoint_
):
1776 __doc__
= ValeurPoint_
.__doc__
1778 reversion
.register(ValeurPoint
, format
='xml')
1781 class Devise(Archivable
):
1785 code
= models
.CharField(max_length
=10, unique
=True)
1786 nom
= models
.CharField(max_length
=255)
1790 verbose_name
= u
"devise"
1791 verbose_name_plural
= u
"devises"
1793 def __unicode__(self
):
1794 return u
'%s - %s' % (self
.code
, self
.nom
)
1796 reversion
.register(Devise
, format
='xml')
1799 class TypeContrat(Archivable
):
1803 nom
= models
.CharField(max_length
=255)
1804 nom_long
= models
.CharField(max_length
=255)
1808 verbose_name
= u
"Type de contrat"
1809 verbose_name_plural
= u
"Types de contrat"
1811 def __unicode__(self
):
1812 return u
'%s' % (self
.nom
)
1814 reversion
.register(TypeContrat
, format
='xml')
1819 class ResponsableImplantationProxy(ref
.Implantation
):
1827 verbose_name
= u
"Responsable d'implantation"
1828 verbose_name_plural
= u
"Responsables d'implantation"
1831 class ResponsableImplantation(models
.Model
):
1833 Le responsable d'une implantation.
1834 Anciennement géré sur le Dossier du responsable.
1836 employe
= models
.ForeignKey(
1837 'Employe', db_column
='employe', related_name
='+', null
=True,
1840 implantation
= models
.OneToOneField(
1841 "ResponsableImplantationProxy", db_column
='implantation',
1842 related_name
='responsable', unique
=True
1845 def __unicode__(self
):
1846 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1849 ordering
= ['implantation__nom']
1850 verbose_name
= "Responsable d'implantation"
1851 verbose_name_plural
= "Responsables d'implantation"
1853 reversion
.register(ResponsableImplantation
, format
='xml')
1856 class UserProfile(models
.Model
):
1857 user
= models
.OneToOneField(User
, related_name
='profile')
1858 zones_administratives
= models
.ManyToManyField(
1859 ref
.ZoneAdministrative
,
1860 related_name
='profiles'
1863 verbose_name
= "Permissions sur zones administratives"
1864 verbose_name_plural
= "Permissions sur zones administratives"
1866 def __unicode__(self
):
1867 return self
.user
.__unicode__()
1869 reversion
.register(UserProfile
, format
='xml')
1873 TYPES_CHANGEMENT
= (
1880 class ChangementPersonnelNotifications(models
.Model
):
1882 verbose_name
= u
"Destinataire pour notices de mouvement de personnel"
1883 verbose_name_plural
= u
"Destinataires pour notices de mouvement de personnel"
1885 type = models
.CharField(
1887 choices
= TYPES_CHANGEMENT
,
1891 destinataires
= models
.ManyToManyField(
1893 related_name
='changement_notifications',
1896 def __unicode__(self
):
1898 self
.get_type_display(), ','.join(
1899 self
.destinataires
.all().values_list(
1900 'courriel', flat
=True))
1904 class ChangementPersonnel(models
.Model
):
1906 Une notice qui enregistre un changement de personnel, incluant:
1909 * Mouvement de personnel
1914 verbose_name
= u
"Mouvement de personnel"
1915 verbose_name_plural
= u
"Mouvements de personnel"
1917 def __unicode__(self
):
1918 return '%s: %s' % (self
.dossier
.__unicode__(),
1919 self
.get_type_display())
1922 def create_changement(cls
, dossier
, type):
1923 # If this employe has existing Changement, set them to invalid.
1924 cls
.objects
.filter(dossier__employe
=dossier
.employe
).update(valide
=False)
1936 def post_save_handler(cls
,
1943 # This defines the time limit used when checking in previous
1944 # files to see if an employee if new. Basically, if emloyee
1945 # left his position new_file.date_debut -
1946 # NEW_EMPLOYE_THRESHOLD +1 ago (compared to date_debut), then
1947 # if a new file is created for this employee, he will bec
1948 # onsidered "NEW" and a notice will be created to this effect.
1949 NEW_EMPLOYE_THRESHOLD
= datetime
.timedelta(7) # 7 days.
1951 other_dossier_qs
= instance
.employe
.rh_dossiers
.exclude(
1953 dd
= instance
.date_debut
1954 df
= instance
.date_fin
1956 # Here, verify differences between the instance, before and
1958 df_has_changed
= False
1962 df_has_changed
= True
1964 df_has_changed
= (df
!= instance
.before_save
.date_fin
and
1970 # Date de fin est None et c'est une nouvelle instance de
1972 if not df
and created
:
1973 # QS for finding other dossiers with a date_fin of None OR
1974 # with a date_fin >= to this dossier's date_debut
1975 exists_recent_file_qs
= other_dossier_qs
.filter(
1976 Q(date_fin__isnull
=True) |
1977 Q(date_fin__gte
=dd
- NEW_EMPLOYE_THRESHOLD
)
1980 # 1. If existe un Dossier récent, et c'est une nouvelle
1981 # instance de Dossier:
1982 if exists_recent_file_qs
.count() > 0:
1983 cls
.create_changement(
1987 # 2. Il n'existe un Dossier récent, et c'est une nouvelle
1988 # instance de Dossier:
1990 cls
.create_changement(
1996 # Date de fin a été modifiée:
1998 # QS for other active files (date_fin == None), excludes
2000 exists_active_files_qs
= other_dossier_qs
.filter(
2001 Q(date_fin__isnull
=True))
2003 # 3. Date de fin a été modifiée et il n'existe aucun autre
2004 # dossier actifs: Depart
2005 if exists_active_files_qs
.count() == 0:
2006 cls
.create_changement(
2010 # 4. Dossier a une nouvelle date de fin par contre
2011 # d'autres dossiers actifs existent déjà: Mouvement
2013 cls
.create_changement(
2019 dossier
= models
.ForeignKey(
2021 related_name
='mouvements',
2024 valide
= models
.BooleanField(default
=True)
2025 date_creation
= models
.DateTimeField(
2027 communique
= models
.BooleanField(
2031 date_communication
= models
.DateTimeField(
2036 type = models
.CharField(
2038 choices
= TYPES_CHANGEMENT
,
2041 reversion
.register(ChangementPersonnel
, format
='xml')
2044 def dossier_pre_save_handler(sender
,
2048 # Store a copy of the model before save is called.
2049 if instance
.pk
is not None:
2050 instance
.before_save
= Dossier
.objects
.get(pk
=instance
.pk
)
2052 instance
.before_save
= None
2055 # Connect a pre_save handler that assigns a copy of the model as an
2056 # attribute in order to compare it in post_save.
2057 pre_save
.connect(dossier_pre_save_handler
, sender
=Dossier
)
2059 post_save
.connect(ChangementPersonnel
.post_save_handler
, sender
=Dossier
)
2060 post_save
.connect(RHDossierClassementRecord
.post_save_handler
, sender
=Dossier
)