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 from project
.rh
.validators
import validate_date_passee
36 # import pour relocaliser le modèle selon la convention (models.py pour
38 from project
.rh
.historique
import ModificationTraite
41 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
42 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
43 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
44 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
= \
45 "Saisir le nombre d'heure de travail à temps complet (100%), " \
46 "sans tenir compte du régime de travail"
49 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
50 base_url
=settings
.PRIVE_MEDIA_URL
)
53 class RemunIntegrityException(Exception):
56 def poste_piece_dispatch(instance
, filename
):
57 path
= "%s/poste/%s/%s" % (
58 instance
._meta
.app_label
, instance
.poste_id
, filename
63 def dossier_piece_dispatch(instance
, filename
):
64 path
= "%s/dossier/%s/%s" % (
65 instance
._meta
.app_label
, instance
.dossier_id
, filename
70 def employe_piece_dispatch(instance
, filename
):
71 path
= "%s/employe/%s/%s" % (
72 instance
._meta
.app_label
, instance
.employe_id
, filename
77 def contrat_dispatch(instance
, filename
):
78 path
= "%s/contrat/%s/%s" % (
79 instance
._meta
.app_label
, instance
.dossier_id
, filename
84 class DateActiviteMixin(models
.Model
):
86 Mixin pour mettre à jour l'activité d'un modèle
90 date_creation
= models
.DateTimeField(auto_now_add
=True,
91 null
=True, blank
=True,
92 verbose_name
=u
"Date de création",)
93 date_modification
= models
.DateTimeField(auto_now
=True,
94 null
=True, blank
=True,
95 verbose_name
=u
"Date de modification",)
98 class Archivable(models
.Model
):
99 archive
= models
.BooleanField(u
'archivé', default
=False)
101 objects
= ArchivableManager()
102 avec_archives
= models
.Manager()
108 class DevisableMixin(object):
110 def get_annee_pour_taux_devise(self
):
111 return datetime
.datetime
.now().year
113 def taux_devise(self
, devise
=None):
119 if devise
.code
== "EUR":
122 annee
= self
.get_annee_pour_taux_devise()
123 taux
= TauxChange
.objects
.filter(devise
=devise
, annee__lte
=annee
) \
127 def montant_euros(self
):
129 taux
= self
.taux_devise()
134 return int(round(float(self
.montant
) * float(taux
), 2))
137 class Commentaire(models
.Model
):
138 texte
= models
.TextField()
139 owner
= models
.ForeignKey(
140 'auth.User', db_column
='owner', related_name
='+',
141 verbose_name
=u
"Commentaire de"
143 date_creation
= models
.DateTimeField(
144 u
'date', auto_now_add
=True, blank
=True, null
=True
149 ordering
= ['-date_creation']
151 def __unicode__(self
):
152 return u
'%s' % (self
.texte
)
157 POSTE_APPEL_CHOICES
= (
158 ('interne', 'Interne'),
159 ('externe', 'Externe'),
163 class Poste_( DateActiviteMixin
, models
.Model
,):
165 Un Poste est un emploi (job) à combler dans une implantation.
166 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
167 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
170 objects
= PosteManager()
173 nom
= models
.CharField(u
"Titre du poste", max_length
=255)
174 nom_feminin
= models
.CharField(
175 u
"Titre du poste (au féminin)", max_length
=255, null
=True
177 implantation
= models
.ForeignKey(
179 help_text
=u
"Taper le nom de l'implantation ou sa région",
180 db_column
='implantation', related_name
='+'
182 type_poste
= models
.ForeignKey(
183 'TypePoste', db_column
='type_poste',
184 help_text
=u
"Taper le nom du type de poste", related_name
='+',
185 null
=True, verbose_name
=u
"type de poste"
187 service
= models
.ForeignKey(
188 'Service', db_column
='service', related_name
='%(app_label)s_postes',
189 verbose_name
=u
"direction/service/pôle support", null
=True
191 responsable
= models
.ForeignKey(
192 'Poste', db_column
='responsable',
193 related_name
='+', null
=True,
194 help_text
=u
"Taper le nom du poste ou du type de poste",
195 verbose_name
=u
"Poste du responsable"
199 regime_travail
= models
.DecimalField(
200 u
"temps de travail", max_digits
=12, decimal_places
=2,
201 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
202 help_text
="% du temps complet"
204 regime_travail_nb_heure_semaine
= models
.DecimalField(
205 u
"nb. heures par semaine", max_digits
=12, decimal_places
=2,
206 null
=True, default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
207 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
211 local
= models
.NullBooleanField(
212 u
"local", default
=True, null
=True, blank
=True
214 expatrie
= models
.NullBooleanField(
215 u
"expatrié", default
=False, null
=True, blank
=True
217 mise_a_disposition
= models
.NullBooleanField(
218 u
"mise à disposition", null
=True, default
=False
220 appel
= models
.CharField(
221 u
"Appel à candidature", max_length
=10, null
=True,
222 choices
=POSTE_APPEL_CHOICES
, default
='interne'
226 classement_min
= models
.ForeignKey(
227 'Classement', db_column
='classement_min', related_name
='+',
228 null
=True, blank
=True
230 classement_max
= models
.ForeignKey(
231 'Classement', db_column
='classement_max', related_name
='+',
232 null
=True, blank
=True
234 valeur_point_min
= models
.ForeignKey(
236 help_text
=u
"Taper le code ou le nom de l'implantation",
237 db_column
='valeur_point_min', related_name
='+', null
=True,
240 valeur_point_max
= models
.ForeignKey(
242 help_text
=u
"Taper le code ou le nom de l'implantation",
243 db_column
='valeur_point_max', related_name
='+', null
=True,
246 devise_min
= models
.ForeignKey(
247 'Devise', db_column
='devise_min', null
=True, related_name
='+'
249 devise_max
= models
.ForeignKey(
250 'Devise', db_column
='devise_max', null
=True, related_name
='+'
252 salaire_min
= models
.DecimalField(
253 max_digits
=12, decimal_places
=2, default
=0,
255 salaire_max
= models
.DecimalField(
256 max_digits
=12, decimal_places
=2, default
=0,
258 indemn_min
= models
.DecimalField(
259 max_digits
=12, decimal_places
=2, default
=0,
261 indemn_max
= models
.DecimalField(
262 max_digits
=12, decimal_places
=2, default
=0,
264 autre_min
= models
.DecimalField(
265 max_digits
=12, decimal_places
=2, default
=0,
267 autre_max
= models
.DecimalField(
268 max_digits
=12, decimal_places
=2, default
=0,
271 # Comparatifs de rémunération
272 devise_comparaison
= models
.ForeignKey(
273 'Devise', null
=True, blank
=True, db_column
='devise_comparaison',
276 comp_locale_min
= models
.DecimalField(
277 max_digits
=12, decimal_places
=2, null
=True, blank
=True
279 comp_locale_max
= models
.DecimalField(
280 max_digits
=12, decimal_places
=2, null
=True, blank
=True
282 comp_universite_min
= models
.DecimalField(
283 max_digits
=12, decimal_places
=2, null
=True, blank
=True
285 comp_universite_max
= models
.DecimalField(
286 max_digits
=12, decimal_places
=2, null
=True, blank
=True
288 comp_fonctionpub_min
= models
.DecimalField(
289 max_digits
=12, decimal_places
=2, null
=True, blank
=True
291 comp_fonctionpub_max
= models
.DecimalField(
292 max_digits
=12, decimal_places
=2, null
=True, blank
=True
294 comp_ong_min
= models
.DecimalField(
295 max_digits
=12, decimal_places
=2, null
=True, blank
=True
297 comp_ong_max
= models
.DecimalField(
298 max_digits
=12, decimal_places
=2, null
=True, blank
=True
300 comp_autre_min
= models
.DecimalField(
301 max_digits
=12, decimal_places
=2, null
=True, blank
=True
303 comp_autre_max
= models
.DecimalField(
304 max_digits
=12, decimal_places
=2, null
=True, blank
=True
308 justification
= models
.TextField(null
=True, blank
=True)
311 date_debut
= models
.DateField(
312 u
"date de début", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True,
315 date_fin
= models
.DateField(
316 u
"date de fin", help_text
=HELP_TEXT_DATE
, null
=True, blank
=True,
322 ordering
= ['implantation__nom', 'nom']
323 verbose_name
= u
"Poste"
324 verbose_name_plural
= u
"Postes"
327 def __unicode__(self
):
328 representation
= u
'%s - %s [%s]' % (
329 self
.implantation
, self
.nom
, self
.id
331 return representation
333 prefix_implantation
= "implantation__zone_administrative"
335 def get_zones_administratives(self
):
336 return [self
.implantation
.zone_administrative
]
338 def get_devise(self
):
339 vp
= ValeurPoint
.objects
.filter(
340 implantation
=self
.implantation
, devise__archive
=False
345 return Devise
.objects
.get(code
='EUR')
349 __doc__
= Poste_
.__doc__
351 # meta dématérialisation : pour permettre le filtrage
352 vacant
= models
.NullBooleanField(u
"vacant", null
=True, blank
=True)
356 if self
.occupe_par():
360 def occupe_par(self
):
362 Retourne la liste d'employé occupant ce poste.
363 Généralement, retourne une liste d'un élément.
364 Si poste inoccupé, retourne liste vide.
365 UTILISE pour mettre a jour le flag vacant
369 for d
in self
.rh_dossiers
.exclude(date_fin__lt
=date
.today())
372 reversion
.register(Poste
, format
='xml', follow
=[
373 'rh_financements', 'rh_pieces', 'rh_comparaisons_internes',
378 POSTE_FINANCEMENT_CHOICES
= (
379 ('A', 'A - Frais de personnel'),
380 ('B', 'B - Projet(s)-Titre(s)'),
385 class PosteFinancement_(models
.Model
):
387 Pour un Poste, structure d'informations décrivant comment on prévoit
390 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
391 pourcentage
= models
.DecimalField(
392 max_digits
=12, decimal_places
=2,
393 help_text
="ex.: 33.33 % (décimale avec point)"
395 commentaire
= models
.TextField(
396 help_text
="Spécifiez la source de financement."
403 def __unicode__(self
):
404 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
407 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
410 class PosteFinancement(PosteFinancement_
):
411 poste
= models
.ForeignKey(
412 Poste
, db_column
='poste', related_name
='rh_financements'
415 reversion
.register(PosteFinancement
, format
='xml')
418 class PostePiece_(models
.Model
):
420 Documents relatifs au Poste.
421 Ex.: Description de poste
423 nom
= models
.CharField(u
"Nom", max_length
=255)
424 fichier
= models
.FileField(
425 u
"Fichier", upload_to
=poste_piece_dispatch
, storage
=storage_prive
432 def __unicode__(self
):
433 return u
'%s' % (self
.nom
)
436 class PostePiece(PostePiece_
):
437 poste
= models
.ForeignKey(
438 Poste
, db_column
='poste', related_name
='rh_pieces'
441 reversion
.register(PostePiece
, format
='xml')
444 class PosteComparaison_(models
.Model
, DevisableMixin
):
446 De la même manière qu'un dossier, un poste peut-être comparé à un autre
449 objects
= PosteComparaisonManager()
451 implantation
= models
.ForeignKey(
452 ref
.Implantation
, null
=True, blank
=True, related_name
="+"
454 nom
= models
.CharField(u
"Poste", max_length
=255, null
=True, blank
=True)
455 montant
= models
.IntegerField(null
=True)
456 devise
= models
.ForeignKey(
457 "Devise", related_name
='+', null
=True, blank
=True
463 def __unicode__(self
):
467 class PosteComparaison(PosteComparaison_
):
468 poste
= models
.ForeignKey(
469 Poste
, related_name
='rh_comparaisons_internes'
472 reversion
.register(PosteComparaison
, format
='xml')
475 class PosteCommentaire(Commentaire
):
476 poste
= models
.ForeignKey(
477 Poste
, db_column
='poste', related_name
='commentaires'
480 reversion
.register(PosteCommentaire
, format
='xml')
484 class Employe(models
.Model
):
486 Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
487 Dossiers qu'il occupe ou a occupé de Postes.
489 Cette classe aurait pu avantageusement s'appeler Personne car la notion
490 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
493 objects
= EmployeManager()
496 nom
= models
.CharField(max_length
=255)
497 prenom
= models
.CharField(u
"prénom", max_length
=255)
498 nom_affichage
= models
.CharField(
499 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
501 nationalite
= models
.ForeignKey(
502 ref
.Pays
, to_field
='code', db_column
='nationalite',
503 related_name
='employes_nationalite', verbose_name
=u
"nationalité",
504 blank
=True, null
=True
506 date_naissance
= models
.DateField(
507 u
"date de naissance", help_text
=HELP_TEXT_DATE
,
508 validators
=[validate_date_passee
], null
=True, blank
=True
510 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
513 situation_famille
= models
.CharField(
514 u
"situation familiale", max_length
=1, choices
=SITUATION_CHOICES
,
515 null
=True, blank
=True
517 date_entree
= models
.DateField(
518 u
"date d'entrée à l'AUF", help_text
=HELP_TEXT_DATE
, null
=True,
523 tel_domicile
= models
.CharField(
524 u
"tél. domicile", max_length
=255, null
=True, blank
=True
526 tel_cellulaire
= models
.CharField(
527 u
"tél. cellulaire", max_length
=255, null
=True, blank
=True
529 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
530 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
531 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
532 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
533 pays
= models
.ForeignKey(
534 ref
.Pays
, to_field
='code', db_column
='pays',
535 related_name
='employes', null
=True, blank
=True
537 courriel_perso
= models
.EmailField(
538 u
'adresse courriel personnelle', blank
=True
541 # meta dématérialisation : pour permettre le filtrage
542 nb_postes
= models
.IntegerField(u
"nombre de postes", null
=True, blank
=True)
545 ordering
= ['nom', 'prenom']
546 verbose_name
= u
"Employé"
547 verbose_name_plural
= u
"Employés"
549 def __unicode__(self
):
550 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
554 if self
.genre
.upper() == u
'M':
556 elif self
.genre
.upper() == u
'F':
562 Retourne l'URL du service retournant la photo de l'Employe.
563 Équivalent reverse url 'rh_photo' avec id en param.
565 from django
.core
.urlresolvers
import reverse
566 return reverse('rh_photo', kwargs
={'id': self
.id})
568 def dossiers_passes(self
):
569 params
= {KEY_STATUT
: STATUT_INACTIF
, }
570 search
= RechercheTemporelle(params
, Dossier
)
571 search
.purge_params(params
)
572 q
= search
.get_q_temporel(self
.rh_dossiers
)
573 return self
.rh_dossiers
.filter(q
)
575 def dossiers_futurs(self
):
576 params
= {KEY_STATUT
: STATUT_FUTUR
, }
577 search
= RechercheTemporelle(params
, Dossier
)
578 search
.purge_params(params
)
579 q
= search
.get_q_temporel(self
.rh_dossiers
)
580 return self
.rh_dossiers
.filter(q
)
582 def dossiers_encours(self
):
583 params
= {KEY_STATUT
: STATUT_ACTIF
, }
584 search
= RechercheTemporelle(params
, Dossier
)
585 search
.purge_params(params
)
586 q
= search
.get_q_temporel(self
.rh_dossiers
)
587 return self
.rh_dossiers
.filter(q
)
589 def dossier_principal(self
):
591 Retourne le dossier principal (ou le plus ancien si il y en a
595 dossier
= self
.rh_dossiers \
596 .filter(principal
=True).order_by('date_debut')[0]
597 except IndexError, Dossier
.DoesNotExist
:
601 def postes_encours(self
):
602 postes_encours
= set()
603 for d
in self
.dossiers_encours():
604 postes_encours
.add(d
.poste
)
605 return postes_encours
607 def poste_principal(self
):
609 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
611 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
613 # DEPRECATED : on a maintenant Dossier.principal
614 poste
= Poste
.objects
.none()
616 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
621 prefix_implantation
= \
622 "rh_dossiers__poste__implantation__zone_administrative"
624 def get_zones_administratives(self
):
626 d
.poste
.implantation
.zone_administrative
627 for d
in self
.dossiers
.all()
630 reversion
.register(Employe
, format
='xml', follow
=[
631 'pieces', 'commentaires', 'ayantdroits'
635 class EmployePiece(models
.Model
):
637 Documents relatifs à un employé.
640 employe
= models
.ForeignKey(
641 'Employe', db_column
='employe', related_name
="pieces",
642 verbose_name
=u
"employé"
644 nom
= models
.CharField(max_length
=255)
645 fichier
= models
.FileField(
646 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
651 verbose_name
= u
"Employé pièce"
652 verbose_name_plural
= u
"Employé pièces"
654 def __unicode__(self
):
655 return u
'%s' % (self
.nom
)
657 reversion
.register(EmployePiece
, format
='xml')
660 class EmployeCommentaire(Commentaire
):
661 employe
= models
.ForeignKey(
662 'Employe', db_column
='employe', related_name
='commentaires'
666 verbose_name
= u
"Employé commentaire"
667 verbose_name_plural
= u
"Employé commentaires"
669 reversion
.register(EmployeCommentaire
, format
='xml')
672 LIEN_PARENTE_CHOICES
= (
673 ('Conjoint', 'Conjoint'),
674 ('Conjointe', 'Conjointe'),
680 class AyantDroit(models
.Model
):
682 Personne en relation avec un Employe.
685 nom
= models
.CharField(max_length
=255)
686 prenom
= models
.CharField(u
"prénom", max_length
=255)
687 nom_affichage
= models
.CharField(
688 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
690 nationalite
= models
.ForeignKey(
691 ref
.Pays
, to_field
='code', db_column
='nationalite',
692 related_name
='ayantdroits_nationalite',
693 verbose_name
=u
"nationalité", null
=True, blank
=True
695 date_naissance
= models
.DateField(
696 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
697 validators
=[validate_date_passee
], null
=True, blank
=True
699 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
702 employe
= models
.ForeignKey(
703 'Employe', db_column
='employe', related_name
='ayantdroits',
704 verbose_name
=u
"Employé"
706 lien_parente
= models
.CharField(
707 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
708 null
=True, blank
=True
713 verbose_name
= u
"Ayant droit"
714 verbose_name_plural
= u
"Ayants droit"
716 def __unicode__(self
):
717 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
719 prefix_implantation
= \
720 "employe__dossiers__poste__implantation__zone_administrative"
722 def get_zones_administratives(self
):
724 d
.poste
.implantation
.zone_administrative
725 for d
in self
.employe
.dossiers
.all()
728 reversion
.register(AyantDroit
, format
='xml', follow
=['commentaires'])
731 class AyantDroitCommentaire(Commentaire
):
732 ayant_droit
= models
.ForeignKey(
733 'AyantDroit', db_column
='ayant_droit', related_name
='commentaires'
736 reversion
.register(AyantDroitCommentaire
, format
='xml')
741 STATUT_RESIDENCE_CHOICES
= (
743 ('expat', 'Expatrié'),
746 COMPTE_COMPTA_CHOICES
= (
753 class Dossier_(DateActiviteMixin
, models
.Model
, DevisableMixin
,):
755 Le Dossier regroupe les informations relatives à l'occupation
756 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
759 Plusieurs Contrats peuvent être associés au Dossier.
760 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
761 lequel aucun Dossier n'existe est un poste vacant.
764 objects
= DossierManager()
767 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
768 organisme_bstg
= models
.ForeignKey(
769 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
770 verbose_name
=u
"organisme",
772 u
"Si détaché (DET) ou mis à disposition (MAD), "
773 u
"préciser l'organisme."
774 ), null
=True, blank
=True
778 remplacement
= models
.BooleanField(default
=False)
779 remplacement_de
= models
.ForeignKey(
780 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
781 null
=True, blank
=True
783 statut_residence
= models
.CharField(
784 u
"statut", max_length
=10, default
='local', null
=True,
785 choices
=STATUT_RESIDENCE_CHOICES
789 classement
= models
.ForeignKey(
790 'Classement', db_column
='classement', related_name
='+', null
=True,
793 regime_travail
= models
.DecimalField(
794 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
795 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
797 regime_travail_nb_heure_semaine
= models
.DecimalField(
798 u
"nb. heures par semaine", max_digits
=12,
799 decimal_places
=2, null
=True,
800 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
801 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
804 # Occupation du Poste par cet Employe (anciennement "mandat")
805 date_debut
= models
.DateField(
806 u
"date de début d'occupation de poste", db_index
=True
808 date_fin
= models
.DateField(
809 u
"Date de fin d'occupation de poste", null
=True, blank
=True,
814 est_cadre
= models
.BooleanField(
824 ordering
= ['employe__nom', ]
825 verbose_name
= u
"Dossier"
826 verbose_name_plural
= "Dossiers"
828 def salaire_theorique(self
):
829 annee
= date
.today().year
830 coeff
= self
.classement
.coefficient
831 implantation
= self
.poste
.implantation
832 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
834 montant
= coeff
* point
.valeur
835 devise
= point
.devise
836 return {'montant': montant
, 'devise': devise
}
838 def __unicode__(self
):
839 poste
= self
.poste
.nom
840 if self
.employe
.genre
== 'F':
841 poste
= self
.poste
.nom_feminin
842 return u
'%s - %s' % (self
.employe
, poste
)
844 prefix_implantation
= "poste__implantation__zone_administrative"
846 def get_zones_administratives(self
):
847 return [self
.poste
.implantation
.zone_administrative
]
849 def remunerations(self
):
850 key
= "%s_remunerations" % self
._meta
.app_label
851 remunerations
= getattr(self
, key
)
852 return remunerations
.all().order_by('-date_debut')
854 def remunerations_en_cours(self
):
855 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
856 return self
.remunerations().all().filter(q
).order_by('date_debut')
858 def get_salaire(self
):
860 return [r
for r
in self
.remunerations().order_by('-date_debut')
861 if r
.type_id
== 1][0]
865 def get_salaire_euros(self
):
866 tx
= self
.taux_devise()
867 return (float)(tx
) * (float)(self
.salaire
)
869 def get_remunerations_brutes(self
):
873 4 Indemnité d'expatriation
874 5 Indemnité pour frais
875 6 Indemnité de logement
876 7 Indemnité de fonction
877 8 Indemnité de responsabilité
878 9 Indemnité de transport
879 10 Indemnité compensatrice
880 11 Indemnité de subsistance
881 12 Indemnité différentielle
882 13 Prime d'installation
885 16 Indemnité de départ
886 18 Prime de 13ième mois
889 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
890 return [r
for r
in self
.remunerations_en_cours().all()
893 def get_charges_salariales(self
):
895 20 Charges salariales ?
898 return [r
for r
in self
.remunerations_en_cours().all()
901 def get_charges_patronales(self
):
903 17 Charges patronales
906 return [r
for r
in self
.remunerations_en_cours().all()
909 def get_remunerations_tierces(self
):
913 return [r
for r
in self
.remunerations_en_cours().all()
914 if r
.type_id
in (2,)]
918 def get_total_local_charges_salariales(self
):
919 devise
= self
.poste
.get_devise()
921 for r
in self
.get_charges_salariales():
922 if r
.devise
!= devise
:
924 total
+= float(r
.montant
)
927 def get_total_local_charges_patronales(self
):
928 devise
= self
.poste
.get_devise()
930 for r
in self
.get_charges_patronales():
931 if r
.devise
!= devise
:
933 total
+= float(r
.montant
)
936 def get_local_salaire_brut(self
):
938 somme des rémuérations brutes
940 devise
= self
.poste
.get_devise()
942 for r
in self
.get_remunerations_brutes():
943 if r
.devise
!= devise
:
945 total
+= float(r
.montant
)
948 def get_local_salaire_net(self
):
950 salaire brut - charges salariales
952 devise
= self
.poste
.get_devise()
954 for r
in self
.get_charges_salariales():
955 if r
.devise
!= devise
:
957 total_charges
+= float(r
.montant
)
958 return self
.get_local_salaire_brut() - total_charges
960 def get_local_couts_auf(self
):
962 salaire net + charges patronales
964 devise
= self
.poste
.get_devise()
966 for r
in self
.get_charges_patronales():
967 if r
.devise
!= devise
:
969 total_charges
+= float(r
.montant
)
970 return self
.get_local_salaire_net() + total_charges
972 def get_total_local_remunerations_tierces(self
):
973 devise
= self
.poste
.get_devise()
975 for r
in self
.get_remunerations_tierces():
976 if r
.devise
!= devise
:
978 total
+= float(r
.montant
)
983 def get_total_charges_salariales(self
):
985 for r
in self
.get_charges_salariales():
986 total
+= r
.montant_euros()
989 def get_total_charges_patronales(self
):
991 for r
in self
.get_charges_patronales():
992 total
+= r
.montant_euros()
995 def get_salaire_brut(self
):
997 somme des rémuérations brutes
1000 for r
in self
.get_remunerations_brutes():
1001 total
+= r
.montant_euros()
1004 def get_salaire_net(self
):
1006 salaire brut - charges salariales
1009 for r
in self
.get_charges_salariales():
1010 total_charges
+= r
.montant_euros()
1011 return self
.get_salaire_brut() - total_charges
1013 def get_couts_auf(self
):
1015 salaire net + charges patronales
1018 for r
in self
.get_charges_patronales():
1019 total_charges
+= r
.montant_euros()
1020 return self
.get_salaire_net() + total_charges
1022 def get_total_remunerations_tierces(self
):
1024 for r
in self
.get_remunerations_tierces():
1025 total
+= r
.montant_euros()
1028 def premier_contrat(self
):
1029 """contrat avec plus petite date de début"""
1031 contrat
= self
.rh_contrats
.exclude(date_debut
=None) \
1032 .order_by('date_debut')[0]
1033 except IndexError, Contrat
.DoesNotExist
:
1037 def dernier_contrat(self
):
1038 """contrat avec plus grande date de fin"""
1040 contrat
= self
.rh_contrats
.exclude(date_debut
=None) \
1041 .order_by('-date_debut')[0]
1042 except IndexError, Contrat
.DoesNotExist
:
1047 today
= date
.today()
1048 return (self
.date_debut
is None or self
.date_debut
<= today
) \
1049 and (self
.date_fin
is None or self
.date_fin
>= today
) \
1050 and not (self
.date_fin
is None and self
.date_debut
is None)
1053 class Dossier(Dossier_
):
1054 __doc__
= Dossier_
.__doc__
1055 poste
= models
.ForeignKey(
1056 Poste
, db_column
='poste', related_name
='rh_dossiers',
1057 help_text
=u
"Taper le nom du poste ou du type de poste",
1059 employe
= models
.ForeignKey(
1060 'Employe', db_column
='employe',
1061 help_text
=u
"Taper le nom de l'employé",
1062 related_name
='rh_dossiers', verbose_name
=u
"employé"
1064 principal
= models
.BooleanField(
1065 u
"dossier principal", default
=True,
1067 u
"Ce dossier est pour le principal poste occupé par l'employé"
1072 reversion
.register(Dossier
, format
='xml', follow
=[
1073 'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
1074 'rh_contrats', 'commentaires'
1078 class RHDossierClassementRecord(models
.Model
):
1079 classement
= models
.ForeignKey(
1081 related_name
='classement_records',
1083 dossier
= models
.ForeignKey(
1085 related_name
='classement_records',
1087 date_debut
= models
.DateField(
1089 help_text
=HELP_TEXT_DATE
,
1094 date_fin
= models
.DateField(
1096 help_text
=HELP_TEXT_DATE
,
1102 def __unicode__(self
):
1103 return self
.classement
.__unicode__()
1106 verbose_name
= u
"Element d'historique de classement"
1107 verbose_name_plural
= u
"Historique de classement"
1110 def post_save_handler(cls
,
1117 today
= date
.today()
1118 previous_record
= None
1119 previous_classement
= None
1122 # Premièrement, pour les nouvelles instances:
1124 if not self
.classement
:
1128 date_debut
=instance
.date_debut
,
1129 classement
=instance
.classement
,
1134 # Deuxièmement, pour les instances existantes:
1137 # 1. Est-ce que le classement a changé?
1138 # 2. Est-ce qu'une historique de classement existe déjà
1140 previous_record
= cls
.objects
.get(
1142 classement
=instance
.before_save
.classement
,
1145 except cls
.DoesNotExist
:
1146 if instance
.before_save
.classement
:
1147 # Il était censé avoir une historique de classement
1149 previous_record
= cls
.objects
.create(
1150 date_debut
=instance
.before_save
.date_debut
,
1151 classement
=instance
.before_save
.classement
,
1154 previous_classement
= instance
.before_save
.classement
1157 previous_classement
= previous_record
.classement
1160 instance
.classement
!=
1164 # Cas aucun changement:
1169 # Classement a changé
1171 previous_record
.date_fin
= today
1172 previous_record
.save()
1174 if instance
.classement
:
1177 classement
=instance
.classement
,
1182 class DossierPiece_(models
.Model
):
1184 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1185 Ex.: Lettre de motivation.
1187 nom
= models
.CharField(max_length
=255)
1188 fichier
= models
.FileField(
1189 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
1196 def __unicode__(self
):
1197 return u
'%s' % (self
.nom
)
1200 class DossierPiece(DossierPiece_
):
1201 dossier
= models
.ForeignKey(
1202 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
1205 reversion
.register(DossierPiece
, format
='xml')
1207 class DossierCommentaire(Commentaire
):
1208 dossier
= models
.ForeignKey(
1209 Dossier
, db_column
='dossier', related_name
='commentaires'
1212 reversion
.register(DossierCommentaire
, format
='xml')
1215 class DossierComparaison_(models
.Model
, DevisableMixin
):
1217 Photo d'une comparaison salariale au moment de l'embauche.
1219 objects
= DossierComparaisonManager()
1221 implantation
= models
.ForeignKey(
1222 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1224 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1225 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1226 montant
= models
.IntegerField(null
=True)
1227 devise
= models
.ForeignKey(
1228 'Devise', related_name
='+', null
=True, blank
=True
1234 def __unicode__(self
):
1235 return "%s (%s)" % (self
.poste
, self
.personne
)
1238 class DossierComparaison(DossierComparaison_
):
1239 dossier
= models
.ForeignKey(
1240 Dossier
, related_name
='rh_comparaisons'
1243 reversion
.register(DossierComparaison
, format
='xml')
1248 class RemunerationMixin(models
.Model
):
1251 type = models
.ForeignKey(
1252 'TypeRemuneration', db_column
='type', related_name
='+',
1253 verbose_name
=u
"type de rémunération"
1255 type_revalorisation
= models
.ForeignKey(
1256 'TypeRevalorisation', db_column
='type_revalorisation',
1257 related_name
='+', verbose_name
=u
"type de revalorisation",
1258 null
=True, blank
=True
1260 montant
= models
.DecimalField(
1261 null
=True, blank
=True, max_digits
=12, decimal_places
=2
1262 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1263 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1265 # commentaire = precision
1266 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1268 # date_debut = anciennement date_effectif
1269 date_debut
= models
.DateField(
1270 u
"date de début", null
=True, blank
=True, db_index
=True
1272 date_fin
= models
.DateField(
1273 u
"date de fin", null
=True, blank
=True, db_index
=True
1278 ordering
= ['type__nom', '-date_fin']
1280 def __unicode__(self
):
1281 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1284 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1286 Structure de rémunération (données budgétaires) en situation normale
1287 pour un Dossier. Si un Evenement existe, utiliser la structure de
1288 rémunération EvenementRemuneration de cet événement.
1290 objects
= RemunerationManager()
1292 def montant_mois(self
):
1293 return round(self
.montant
/ 12, 2)
1295 def montant_avec_regime(self
):
1296 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1298 def montant_euro_mois(self
):
1299 return round(self
.montant_euros() / 12, 2)
1301 def __unicode__(self
):
1303 devise
= self
.devise
.code
1306 return "%s %s" % (self
.montant
, devise
)
1310 verbose_name
= u
"Rémunération"
1311 verbose_name_plural
= u
"Rémunérations"
1314 class Remuneration(Remuneration_
):
1315 dossier
= models
.ForeignKey(
1316 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1319 reversion
.register(Remuneration
, format
='xml')
1324 class Contrat_(models
.Model
):
1326 Document juridique qui encadre la relation de travail d'un Employe
1327 pour un Poste particulier. Pour un Dossier (qui documente cette
1328 relation de travail) plusieurs contrats peuvent être associés.
1330 objects
= ContratManager()
1331 type_contrat
= models
.ForeignKey(
1332 'TypeContrat', db_column
='type_contrat',
1333 verbose_name
=u
'type de contrat', related_name
='+'
1335 date_debut
= models
.DateField(
1336 u
"date de début", db_index
=True
1338 date_fin
= models
.DateField(
1339 u
"date de fin", null
=True, blank
=True, db_index
=True
1341 fichier
= models
.FileField(
1342 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1348 ordering
= ['dossier__employe__nom']
1349 verbose_name
= u
"Contrat"
1350 verbose_name_plural
= u
"Contrats"
1352 def __unicode__(self
):
1353 return u
'%s - %s' % (self
.dossier
, self
.id)
1356 class Contrat(Contrat_
):
1357 dossier
= models
.ForeignKey(
1358 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1361 reversion
.register(Contrat
, format
='xml')
1366 class CategorieEmploi(models
.Model
):
1368 Catégorie utilisée dans la gestion des Postes.
1369 Catégorie supérieure à TypePoste.
1371 nom
= models
.CharField(max_length
=255)
1375 verbose_name
= u
"catégorie d'emploi"
1376 verbose_name_plural
= u
"catégories d'emploi"
1378 def __unicode__(self
):
1381 reversion
.register(CategorieEmploi
, format
='xml')
1384 class FamilleProfessionnelle(models
.Model
):
1386 Famille professionnelle d'un poste.
1388 nom
= models
.CharField(max_length
=100)
1392 verbose_name
= u
'famille professionnelle'
1393 verbose_name_plural
= u
'familles professionnelles'
1395 def __unicode__(self
):
1398 reversion
.register(FamilleProfessionnelle
, format
='xml')
1401 class TypePoste(Archivable
):
1405 nom
= models
.CharField(max_length
=255)
1406 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1407 is_responsable
= models
.BooleanField(
1408 u
"poste de responsabilité", default
=False
1410 categorie_emploi
= models
.ForeignKey(
1411 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1412 verbose_name
=u
"catégorie d'emploi"
1414 famille_professionnelle
= models
.ForeignKey(
1415 FamilleProfessionnelle
, related_name
='types_de_poste',
1416 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1421 verbose_name
= u
"Type de poste"
1422 verbose_name_plural
= u
"Types de poste"
1424 def __unicode__(self
):
1425 return u
'%s' % (self
.nom
)
1427 reversion
.register(TypePoste
, format
='xml')
1430 TYPE_PAIEMENT_CHOICES
= (
1431 (u
'Régulier', u
'Régulier'),
1432 (u
'Ponctuel', u
'Ponctuel'),
1435 NATURE_REMUNERATION_CHOICES
= (
1436 (u
'Accessoire', u
'Accessoire'),
1437 (u
'Charges', u
'Charges patronales'),
1438 (u
'Indemnité', u
'Indemnité autre'),
1439 (u
'RAS', u
'Rémunération autre source'),
1440 (u
'Traitement', u
'Traitement'),
1444 class TypeRemuneration(Archivable
):
1446 Catégorie de Remuneration.
1449 nom
= models
.CharField(max_length
=255)
1450 type_paiement
= models
.CharField(
1451 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1454 nature_remuneration
= models
.CharField(
1455 u
"nature de la rémunération", max_length
=30,
1456 choices
=NATURE_REMUNERATION_CHOICES
1461 verbose_name
= u
"Type de rémunération"
1462 verbose_name_plural
= u
"Types de rémunération"
1464 def __unicode__(self
):
1467 reversion
.register(TypeRemuneration
, format
='xml')
1470 class TypeRevalorisation(Archivable
):
1472 Justification du changement de la Remuneration.
1473 (Actuellement utilisé dans aucun traitement informatique.)
1475 nom
= models
.CharField(max_length
=255)
1479 verbose_name
= u
"Type de revalorisation"
1480 verbose_name_plural
= u
"Types de revalorisation"
1482 def __unicode__(self
):
1483 return u
'%s' % (self
.nom
)
1485 reversion
.register(TypeRevalorisation
, format
='xml')
1488 class Service(Archivable
):
1490 Unité administrative où les Postes sont rattachés.
1492 nom
= models
.CharField(max_length
=255)
1496 verbose_name
= u
"service"
1497 verbose_name_plural
= u
"services"
1499 def __unicode__(self
):
1502 reversion
.register(Service
, format
='xml')
1505 TYPE_ORGANISME_CHOICES
= (
1506 ('MAD', 'Mise à disposition'),
1507 ('DET', 'Détachement'),
1511 class OrganismeBstg(models
.Model
):
1513 Organisation d'où provient un Employe mis à disposition (MAD) de
1514 ou détaché (DET) à l'AUF à titre gratuit.
1516 (BSTG = bien et service à titre gratuit.)
1518 nom
= models
.CharField(max_length
=255)
1519 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1520 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1522 related_name
='organismes_bstg',
1523 null
=True, blank
=True)
1526 ordering
= ['type', 'nom']
1527 verbose_name
= u
"Organisme BSTG"
1528 verbose_name_plural
= u
"Organismes BSTG"
1530 def __unicode__(self
):
1531 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1533 reversion
.register(OrganismeBstg
, format
='xml')
1536 class Statut(Archivable
):
1538 Statut de l'Employe dans le cadre d'un Dossier particulier.
1541 code
= models
.CharField(
1542 max_length
=25, unique
=True,
1544 u
"Saisir un code court mais lisible pour ce statut : "
1545 u
"le code est utilisé pour associer les statuts aux autres "
1546 u
"données tout en demeurant plus lisible qu'un identifiant "
1550 nom
= models
.CharField(max_length
=255)
1554 verbose_name
= u
"Statut d'employé"
1555 verbose_name_plural
= u
"Statuts d'employé"
1557 def __unicode__(self
):
1558 return u
'%s : %s' % (self
.code
, self
.nom
)
1560 reversion
.register(Statut
, format
='xml')
1563 TYPE_CLASSEMENT_CHOICES
= (
1564 ('S', 'S -Soutien'),
1565 ('T', 'T - Technicien'),
1566 ('P', 'P - Professionel'),
1568 ('D', 'D - Direction'),
1569 ('SO', 'SO - Sans objet [expatriés]'),
1570 ('HG', 'HG - Hors grille [direction]'),
1574 class ClassementManager(ArchivableManager
):
1576 Ordonner les spcéfiquement les classements.
1578 def get_query_set(self
):
1579 qs
= super(self
.__class__
, self
).get_query_set()
1580 qs
= qs
.extra(select
={
1581 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1583 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1587 class Classement_(Archivable
):
1589 Éléments de classement de la
1590 "Grille générique de classement hiérarchique".
1592 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1593 classement dans la grille. Le classement donne le coefficient utilisé dans:
1595 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1597 objects
= ClassementManager()
1600 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1601 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1602 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1603 coefficient
= models
.FloatField(u
"coefficient", blank
=True, null
=True)
1606 # annee # au lieu de date_debut et date_fin
1607 commentaire
= models
.TextField(null
=True, blank
=True)
1611 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1612 verbose_name
= u
"Classement"
1613 verbose_name_plural
= u
"Classements"
1615 def __unicode__(self
):
1616 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1619 class Classement(Classement_
):
1620 __doc__
= Classement_
.__doc__
1622 reversion
.register(Classement
, format
='xml')
1625 class TauxChange_(models
.Model
):
1627 Taux de change de la devise vers l'euro (EUR)
1628 pour chaque année budgétaire.
1631 devise
= models
.ForeignKey('Devise', db_column
='devise')
1632 annee
= models
.IntegerField(u
"année")
1633 taux
= models
.FloatField(u
"taux vers l'euro")
1637 ordering
= ['-annee', 'devise__code']
1638 verbose_name
= u
"Taux de change"
1639 verbose_name_plural
= u
"Taux de change"
1640 unique_together
= ('devise', 'annee')
1642 def __unicode__(self
):
1643 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1646 class TauxChange(TauxChange_
):
1647 __doc__
= TauxChange_
.__doc__
1649 reversion
.register(TauxChange
, format
='xml')
1652 class ValeurPointManager(models
.Manager
):
1654 def get_query_set(self
):
1655 return super(ValeurPointManager
, self
).get_query_set() \
1656 .select_related('devise', 'implantation')
1659 class ValeurPoint_(models
.Model
):
1661 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1662 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1663 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1665 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1668 objects
= models
.Manager()
1669 actuelles
= ValeurPointManager()
1671 valeur
= models
.FloatField(null
=True)
1672 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1673 implantation
= models
.ForeignKey(ref
.Implantation
,
1674 db_column
='implantation',
1675 related_name
='%(app_label)s_valeur_point')
1677 annee
= models
.IntegerField()
1680 ordering
= ['-annee', 'implantation__nom']
1682 verbose_name
= u
"Valeur du point"
1683 verbose_name_plural
= u
"Valeurs du point"
1684 unique_together
= ('implantation', 'annee')
1686 def __unicode__(self
):
1687 return u
'%s %s %s [%s] %s' % (
1688 self
.devise
.code
, self
.annee
, self
.valeur
,
1689 self
.implantation
.nom_court
, self
.devise
.nom
1693 class ValeurPoint(ValeurPoint_
):
1694 __doc__
= ValeurPoint_
.__doc__
1696 reversion
.register(ValeurPoint
, format
='xml')
1699 class Devise(Archivable
):
1703 code
= models
.CharField(max_length
=10, unique
=True)
1704 nom
= models
.CharField(max_length
=255)
1708 verbose_name
= u
"devise"
1709 verbose_name_plural
= u
"devises"
1711 def __unicode__(self
):
1712 return u
'%s - %s' % (self
.code
, self
.nom
)
1714 reversion
.register(Devise
, format
='xml')
1717 class TypeContrat(Archivable
):
1721 nom
= models
.CharField(max_length
=255)
1722 nom_long
= models
.CharField(max_length
=255)
1726 verbose_name
= u
"Type de contrat"
1727 verbose_name_plural
= u
"Types de contrat"
1729 def __unicode__(self
):
1730 return u
'%s' % (self
.nom
)
1732 reversion
.register(TypeContrat
, format
='xml')
1737 class ResponsableImplantationProxy(ref
.Implantation
):
1745 verbose_name
= u
"Responsable d'implantation"
1746 verbose_name_plural
= u
"Responsables d'implantation"
1749 class ResponsableImplantation(models
.Model
):
1751 Le responsable d'une implantation.
1752 Anciennement géré sur le Dossier du responsable.
1754 employe
= models
.ForeignKey(
1755 'Employe', db_column
='employe', related_name
='+', null
=True,
1758 implantation
= models
.OneToOneField(
1759 "ResponsableImplantationProxy", db_column
='implantation',
1760 related_name
='responsable', unique
=True
1763 def __unicode__(self
):
1764 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1767 ordering
= ['implantation__nom']
1768 verbose_name
= "Responsable d'implantation"
1769 verbose_name_plural
= "Responsables d'implantation"
1771 reversion
.register(ResponsableImplantation
, format
='xml')
1774 class UserProfile(models
.Model
):
1775 user
= models
.OneToOneField(User
, related_name
='profile')
1776 zones_administratives
= models
.ManyToManyField(
1777 ref
.ZoneAdministrative
,
1778 related_name
='profiles'
1781 verbose_name
= "Permissions sur zones administratives"
1782 verbose_name_plural
= "Permissions sur zones administratives"
1784 def __unicode__(self
):
1785 return self
.user
.__unicode__()
1787 reversion
.register(UserProfile
, format
='xml')
1791 TYPES_CHANGEMENT
= (
1792 ('NO', 'Nouveau personnel'),
1793 ('MO', 'Mouvement de personnel'),
1794 ('DE', 'Départ de personnel'),
1798 class ChangementPersonnelNotifications(models
.Model
):
1800 verbose_name
= u
"Destinataire pour notices de changement de personnel"
1801 verbose_name_plural
= u
"Destinataires pour notices de changement de personnel"
1803 type = models
.CharField(
1805 choices
= TYPES_CHANGEMENT
,
1809 destinataires
= models
.ManyToManyField(
1811 related_name
='changement_notifications',
1814 def __unicode__(self
):
1816 self
.get_type_display(), ','.join(
1817 self
.destinataires
.all().values_list(
1818 'courriel', flat
=True))
1822 class ChangementPersonnel(models
.Model
):
1824 Une notice qui enregistre un changement de personnel, incluant:
1827 * Mouvement de personnel
1832 verbose_name
= u
"Notification de changement du personnel"
1833 verbose_name_plural
= u
"Notifications de changement du personnel"
1835 def __unicode__(self
):
1836 return '%s: %s' % (self
.dossier
.__unicode__(),
1837 self
.get_type_display())
1840 def create_changement(cls
, dossier
, type):
1841 # If this employe has existing Changement, set them to invalid.
1842 cls
.objects
.filter(dossier__employe
=dossier
.employe
).update(valide
=False)
1854 def post_save_handler(cls
,
1861 # This defines the time limit used when checking in previous
1862 # files to see if an employee if new. Basically, if emloyee
1863 # left his position new_file.date_debut -
1864 # NEW_EMPLOYE_THRESHOLD +1 ago (compared to date_debut), then
1865 # if a new file is created for this employee, he will bec
1866 # onsidered "NEW" and a notice will be created to this effect.
1867 NEW_EMPLOYE_THRESHOLD
= datetime
.timedelta(7) # 7 days.
1869 other_dossier_qs
= instance
.employe
.rh_dossiers
.exclude(
1871 dd
= instance
.date_debut
1872 df
= instance
.date_fin
1874 # Here, verify differences between the instance, before and
1876 df_has_changed
= False
1880 df_has_changed
= True
1882 df_has_changed
= (df
!= instance
.before_save
.date_fin
and
1888 # Date de fin est None et c'est une nouvelle instance de
1890 if not df
and created
:
1891 # QS for finding other dossiers with a date_fin of None OR
1892 # with a date_fin >= to this dossier's date_debut
1893 exists_recent_file_qs
= other_dossier_qs
.filter(
1894 Q(date_fin__isnull
=True) |
1895 Q(date_fin__gte
=dd
- NEW_EMPLOYE_THRESHOLD
)
1898 # 1. If existe un Dossier récent, et c'est une nouvelle
1899 # instance de Dossier:
1900 if exists_recent_file_qs
.count() > 0:
1901 cls
.create_changement(
1905 # 2. Il n'existe un Dossier récent, et c'est une nouvelle
1906 # instance de Dossier:
1908 cls
.create_changement(
1914 # Date de fin a été modifiée:
1916 # QS for other active files (date_fin == None), excludes
1918 exists_active_files_qs
= other_dossier_qs
.filter(
1919 Q(date_fin__isnull
=True))
1921 # 3. Date de fin a été modifiée et il n'existe aucun autre
1922 # dossier actifs: Depart
1923 if exists_active_files_qs
.count() == 0:
1924 cls
.create_changement(
1928 # 4. Dossier a une nouvelle date de fin par contre
1929 # d'autres dossiers actifs existent déjà: Mouvement
1931 cls
.create_changement(
1937 dossier
= models
.ForeignKey(
1939 related_name
='mouvements',
1942 valide
= models
.BooleanField(default
=True)
1943 communique
= models
.BooleanField(default
=False)
1944 date_communication
= models
.DateTimeField(
1949 type = models
.CharField(
1951 choices
= TYPES_CHANGEMENT
,
1954 reversion
.register(ChangementPersonnel
, format
='xml')
1957 def dossier_pre_save_handler(sender
,
1961 # Store a copy of the model before save is called.
1962 if instance
.pk
is not None:
1963 instance
.before_save
= Dossier
.objects
.get(pk
=instance
.pk
)
1965 instance
.before_save
= None
1968 # Connect a pre_save handler that assigns a copy of the model as an
1969 # attribute in order to compare it in post_save.
1970 pre_save
.connect(dossier_pre_save_handler
, sender
=Dossier
)
1972 post_save
.connect(ChangementPersonnel
.post_save_handler
, sender
=Dossier
)
1973 post_save
.connect(RHDossierClassementRecord
.post_save_handler
, sender
=Dossier
)