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
):
596 Retourne le dossier principal (ou le plus ancien si il y en a
600 dossier
= self
.rh_dossiers \
601 .filter(principal
=True).order_by('date_debut')[0]
602 except IndexError, Dossier
.DoesNotExist
:
606 def postes_encours(self
):
607 postes_encours
= set()
608 for d
in self
.dossiers_encours():
609 postes_encours
.add(d
.poste
)
610 return postes_encours
612 def poste_principal(self
):
614 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
616 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
618 # DEPRECATED : on a maintenant Dossier.principal
619 poste
= Poste
.objects
.none()
621 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
626 prefix_implantation
= \
627 "rh_dossiers__poste__implantation__zone_administrative"
629 def get_zones_administratives(self
):
631 d
.poste
.implantation
.zone_administrative
632 for d
in self
.dossiers
.all()
635 reversion
.register(Employe
, format
='xml', follow
=[
636 'pieces', 'commentaires', 'ayantdroits'
640 class EmployePiece(models
.Model
):
642 Documents relatifs à un employé.
645 employe
= models
.ForeignKey(
646 'Employe', db_column
='employe', related_name
="pieces",
647 verbose_name
=u
"employé"
649 nom
= models
.CharField(max_length
=255)
650 fichier
= models
.FileField(
651 u
"fichier", upload_to
=employe_piece_dispatch
, storage
=storage_prive
656 verbose_name
= u
"Employé pièce"
657 verbose_name_plural
= u
"Employé pièces"
659 def __unicode__(self
):
660 return u
'%s' % (self
.nom
)
662 reversion
.register(EmployePiece
, format
='xml')
665 class EmployeCommentaire(Commentaire
):
666 employe
= models
.ForeignKey(
667 'Employe', db_column
='employe', related_name
='commentaires'
671 verbose_name
= u
"Employé commentaire"
672 verbose_name_plural
= u
"Employé commentaires"
674 reversion
.register(EmployeCommentaire
, format
='xml')
677 LIEN_PARENTE_CHOICES
= (
678 ('Conjoint', 'Conjoint'),
679 ('Conjointe', 'Conjointe'),
685 class AyantDroit(models
.Model
):
687 Personne en relation avec un Employe.
690 nom
= models
.CharField(max_length
=255)
691 prenom
= models
.CharField(u
"prénom", max_length
=255)
692 nom_affichage
= models
.CharField(
693 u
"nom d'affichage", max_length
=255, null
=True, blank
=True
695 nationalite
= models
.ForeignKey(
696 ref
.Pays
, to_field
='code', db_column
='nationalite',
697 related_name
='ayantdroits_nationalite',
698 verbose_name
=u
"nationalité", null
=True, blank
=True
700 date_naissance
= models
.DateField(
701 u
"Date de naissance", help_text
=HELP_TEXT_DATE
,
702 validators
=[validate_date_passee
], null
=True, blank
=True
704 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
707 employe
= models
.ForeignKey(
708 'Employe', db_column
='employe', related_name
='ayantdroits',
709 verbose_name
=u
"Employé"
711 lien_parente
= models
.CharField(
712 u
"lien de parenté", max_length
=10, choices
=LIEN_PARENTE_CHOICES
,
713 null
=True, blank
=True
718 verbose_name
= u
"Ayant droit"
719 verbose_name_plural
= u
"Ayants droit"
721 def __unicode__(self
):
722 return u
'%s %s' % (self
.nom
.upper(), self
.prenom
, )
724 prefix_implantation
= \
725 "employe__dossiers__poste__implantation__zone_administrative"
727 def get_zones_administratives(self
):
729 d
.poste
.implantation
.zone_administrative
730 for d
in self
.employe
.dossiers
.all()
733 reversion
.register(AyantDroit
, format
='xml', follow
=['commentaires'])
736 class AyantDroitCommentaire(Commentaire
):
737 ayant_droit
= models
.ForeignKey(
738 'AyantDroit', db_column
='ayant_droit', related_name
='commentaires'
741 reversion
.register(AyantDroitCommentaire
, format
='xml')
746 STATUT_RESIDENCE_CHOICES
= (
748 ('expat', 'Expatrié'),
751 COMPTE_COMPTA_CHOICES
= (
758 class Dossier_(DateActiviteMixin
, models
.Model
, DevisableMixin
,):
760 Le Dossier regroupe les informations relatives à l'occupation
761 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
764 Plusieurs Contrats peuvent être associés au Dossier.
765 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
766 lequel aucun Dossier n'existe est un poste vacant.
769 objects
= DossierManager()
772 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
773 organisme_bstg
= models
.ForeignKey(
774 'OrganismeBstg', db_column
='organisme_bstg', related_name
='+',
775 verbose_name
=u
"organisme",
777 u
"Si détaché (DET) ou mis à disposition (MAD), "
778 u
"préciser l'organisme."
779 ), null
=True, blank
=True
783 remplacement
= models
.BooleanField(default
=False)
784 remplacement_de
= models
.ForeignKey(
785 'self', related_name
='+', help_text
=u
"Taper le nom de l'employé",
786 null
=True, blank
=True
788 statut_residence
= models
.CharField(
789 u
"statut", max_length
=10, default
='local', null
=True,
790 choices
=STATUT_RESIDENCE_CHOICES
794 classement
= models
.ForeignKey(
795 'Classement', db_column
='classement', related_name
='+', null
=True,
798 regime_travail
= models
.DecimalField(
799 u
"régime de travail", max_digits
=12, null
=True, decimal_places
=2,
800 default
=REGIME_TRAVAIL_DEFAULT
, help_text
="% du temps complet"
802 regime_travail_nb_heure_semaine
= models
.DecimalField(
803 u
"nb. heures par semaine", max_digits
=12,
804 decimal_places
=2, null
=True,
805 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
806 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
809 # Occupation du Poste par cet Employe (anciennement "mandat")
810 date_debut
= models
.DateField(
811 u
"date de début d'occupation de poste", db_index
=True
813 date_fin
= models
.DateField(
814 u
"Date de fin d'occupation de poste", null
=True, blank
=True,
819 est_cadre
= models
.BooleanField(
829 ordering
= ['employe__nom', ]
830 verbose_name
= u
"Dossier"
831 verbose_name_plural
= "Dossiers"
833 def salaire_theorique(self
):
834 annee
= date
.today().year
835 coeff
= self
.classement
.coefficient
836 implantation
= self
.poste
.implantation
837 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
839 montant
= coeff
* point
.valeur
840 devise
= point
.devise
841 return {'montant': montant
, 'devise': devise
}
843 def __unicode__(self
):
844 poste
= self
.poste
.nom
845 if self
.employe
.genre
== 'F':
846 poste
= self
.poste
.nom_feminin
847 return u
'%s - %s' % (self
.employe
, poste
)
849 prefix_implantation
= "poste__implantation__zone_administrative"
851 def get_zones_administratives(self
):
852 return [self
.poste
.implantation
.zone_administrative
]
854 def remunerations(self
):
855 key
= "%s_remunerations" % self
._meta
.app_label
856 remunerations
= getattr(self
, key
)
857 return remunerations
.all().order_by('-date_debut')
859 def remunerations_en_cours(self
):
860 q
= Q(date_fin__exact
=None) |
Q(date_fin__gt
=datetime
.date
.today())
861 return self
.remunerations().all().filter(q
).order_by('date_debut')
863 def get_salaire(self
):
865 return [r
for r
in self
.remunerations().order_by('-date_debut')
866 if r
.type_id
== 1][0]
870 def get_salaire_euros(self
):
871 tx
= self
.taux_devise()
872 return (float)(tx
) * (float)(self
.salaire
)
874 def get_remunerations_brutes(self
):
878 4 Indemnité d'expatriation
879 5 Indemnité pour frais
880 6 Indemnité de logement
881 7 Indemnité de fonction
882 8 Indemnité de responsabilité
883 9 Indemnité de transport
884 10 Indemnité compensatrice
885 11 Indemnité de subsistance
886 12 Indemnité différentielle
887 13 Prime d'installation
890 16 Indemnité de départ
891 18 Prime de 13ième mois
894 ids
= [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19]
895 return [r
for r
in self
.remunerations_en_cours().all()
898 def get_charges_salariales(self
):
900 20 Charges salariales ?
903 return [r
for r
in self
.remunerations_en_cours().all()
906 def get_charges_patronales(self
):
908 17 Charges patronales
911 return [r
for r
in self
.remunerations_en_cours().all()
914 def get_remunerations_tierces(self
):
918 return [r
for r
in self
.remunerations_en_cours().all()
919 if r
.type_id
in (2,)]
923 def get_total_local_charges_salariales(self
):
924 devise
= self
.poste
.get_devise()
926 for r
in self
.get_charges_salariales():
927 if r
.devise
!= devise
:
929 total
+= float(r
.montant
)
932 def get_total_local_charges_patronales(self
):
933 devise
= self
.poste
.get_devise()
935 for r
in self
.get_charges_patronales():
936 if r
.devise
!= devise
:
938 total
+= float(r
.montant
)
941 def get_local_salaire_brut(self
):
943 somme des rémuérations brutes
945 devise
= self
.poste
.get_devise()
947 for r
in self
.get_remunerations_brutes():
948 if r
.devise
!= devise
:
950 total
+= float(r
.montant
)
953 def get_local_salaire_net(self
):
955 salaire brut - charges salariales
957 devise
= self
.poste
.get_devise()
959 for r
in self
.get_charges_salariales():
960 if r
.devise
!= devise
:
962 total_charges
+= float(r
.montant
)
963 return self
.get_local_salaire_brut() - total_charges
965 def get_local_couts_auf(self
):
967 salaire net + charges patronales
969 devise
= self
.poste
.get_devise()
971 for r
in self
.get_charges_patronales():
972 if r
.devise
!= devise
:
974 total_charges
+= float(r
.montant
)
975 return self
.get_local_salaire_net() + total_charges
977 def get_total_local_remunerations_tierces(self
):
978 devise
= self
.poste
.get_devise()
980 for r
in self
.get_remunerations_tierces():
981 if r
.devise
!= devise
:
983 total
+= float(r
.montant
)
988 def get_total_charges_salariales(self
):
990 for r
in self
.get_charges_salariales():
991 total
+= r
.montant_euros()
994 def get_total_charges_patronales(self
):
996 for r
in self
.get_charges_patronales():
997 total
+= r
.montant_euros()
1000 def get_salaire_brut(self
):
1002 somme des rémuérations brutes
1005 for r
in self
.get_remunerations_brutes():
1006 total
+= r
.montant_euros()
1009 def get_salaire_net(self
):
1011 salaire brut - charges salariales
1014 for r
in self
.get_charges_salariales():
1015 total_charges
+= r
.montant_euros()
1016 return self
.get_salaire_brut() - total_charges
1018 def get_couts_auf(self
):
1020 salaire net + charges patronales
1023 for r
in self
.get_charges_patronales():
1024 total_charges
+= r
.montant_euros()
1025 return self
.get_salaire_net() + total_charges
1027 def get_total_remunerations_tierces(self
):
1029 for r
in self
.get_remunerations_tierces():
1030 total
+= r
.montant_euros()
1033 def premier_contrat(self
):
1034 """contrat avec plus petite date de début"""
1036 contrat
= self
.rh_contrats
.exclude(date_debut
=None) \
1037 .order_by('date_debut')[0]
1038 except IndexError, Contrat
.DoesNotExist
:
1042 def dernier_contrat(self
):
1043 """contrat avec plus grande date de fin"""
1045 contrat
= self
.rh_contrats
.exclude(date_debut
=None) \
1046 .order_by('-date_debut')[0]
1047 except IndexError, Contrat
.DoesNotExist
:
1052 today
= date
.today()
1053 return (self
.date_debut
is None or self
.date_debut
<= today
) \
1054 and (self
.date_fin
is None or self
.date_fin
>= today
) \
1055 and not (self
.date_fin
is None and self
.date_debut
is None)
1058 class Dossier(Dossier_
):
1059 __doc__
= Dossier_
.__doc__
1060 poste
= models
.ForeignKey(
1061 Poste
, db_column
='poste', related_name
='rh_dossiers',
1062 help_text
=u
"Taper le nom du poste ou du type de poste",
1064 employe
= models
.ForeignKey(
1065 'Employe', db_column
='employe',
1066 help_text
=u
"Taper le nom de l'employé",
1067 related_name
='rh_dossiers', verbose_name
=u
"employé"
1069 principal
= models
.BooleanField(
1070 u
"dossier principal", default
=True,
1072 u
"Ce dossier est pour le principal poste occupé par l'employé"
1077 reversion
.register(Dossier
, format
='xml', follow
=[
1078 'rh_dossierpieces', 'rh_comparaisons', 'rh_remunerations',
1079 'rh_contrats', 'commentaires'
1083 class RHDossierClassementRecord(models
.Model
):
1084 classement
= models
.ForeignKey(
1086 related_name
='classement_records',
1088 dossier
= models
.ForeignKey(
1090 related_name
='classement_records',
1092 date_debut
= models
.DateField(
1094 help_text
=HELP_TEXT_DATE
,
1099 date_fin
= models
.DateField(
1101 help_text
=HELP_TEXT_DATE
,
1107 def __unicode__(self
):
1108 return self
.classement
.__unicode__()
1111 verbose_name
= u
"Element d'historique de classement"
1112 verbose_name_plural
= u
"Historique de classement"
1115 def post_save_handler(cls
,
1122 today
= date
.today()
1123 previous_record
= None
1124 previous_classement
= None
1127 # Premièrement, pour les nouvelles instances:
1129 if not instance
.classement
:
1133 date_debut
=instance
.date_debut
,
1134 classement
=instance
.classement
,
1139 # Deuxièmement, pour les instances existantes:
1142 # 1. Est-ce que le classement a changé?
1143 # 2. Est-ce qu'une historique de classement existe déjà
1145 previous_record
= cls
.objects
.get(
1147 classement
=instance
.before_save
.classement
,
1150 except cls
.DoesNotExist
:
1151 if instance
.before_save
.classement
:
1152 # Il était censé avoir une historique de classement
1154 previous_record
= cls
.objects
.create(
1155 date_debut
=instance
.before_save
.date_debut
,
1156 classement
=instance
.before_save
.classement
,
1159 previous_classement
= instance
.before_save
.classement
1162 previous_classement
= previous_record
.classement
1165 instance
.classement
!=
1169 # Cas aucun changement:
1174 # Classement a changé
1176 previous_record
.date_fin
= today
1177 previous_record
.save()
1179 if instance
.classement
:
1182 classement
=instance
.classement
,
1187 class DossierPiece_(models
.Model
):
1189 Documents relatifs au Dossier (à l'occupation de ce poste par employé).
1190 Ex.: Lettre de motivation.
1192 nom
= models
.CharField(max_length
=255)
1193 fichier
= models
.FileField(
1194 upload_to
=dossier_piece_dispatch
, storage
=storage_prive
1201 def __unicode__(self
):
1202 return u
'%s' % (self
.nom
)
1205 class DossierPiece(DossierPiece_
):
1206 dossier
= models
.ForeignKey(
1207 Dossier
, db_column
='dossier', related_name
='rh_dossierpieces'
1210 reversion
.register(DossierPiece
, format
='xml')
1212 class DossierCommentaire(Commentaire
):
1213 dossier
= models
.ForeignKey(
1214 Dossier
, db_column
='dossier', related_name
='commentaires'
1217 reversion
.register(DossierCommentaire
, format
='xml')
1220 class DossierComparaison_(models
.Model
, DevisableMixin
):
1222 Photo d'une comparaison salariale au moment de l'embauche.
1224 objects
= DossierComparaisonManager()
1226 implantation
= models
.ForeignKey(
1227 ref
.Implantation
, related_name
="+", null
=True, blank
=True
1229 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
1230 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
1231 montant
= models
.IntegerField(null
=True)
1232 devise
= models
.ForeignKey(
1233 'Devise', related_name
='+', null
=True, blank
=True
1239 def __unicode__(self
):
1240 return "%s (%s)" % (self
.poste
, self
.personne
)
1243 class DossierComparaison(DossierComparaison_
):
1244 dossier
= models
.ForeignKey(
1245 Dossier
, related_name
='rh_comparaisons'
1248 reversion
.register(DossierComparaison
, format
='xml')
1253 class RemunerationMixin(models
.Model
):
1256 type = models
.ForeignKey(
1257 'TypeRemuneration', db_column
='type', related_name
='+',
1258 verbose_name
=u
"type de rémunération"
1260 type_revalorisation
= models
.ForeignKey(
1261 'TypeRevalorisation', db_column
='type_revalorisation',
1262 related_name
='+', verbose_name
=u
"type de revalorisation",
1263 null
=True, blank
=True
1265 montant
= models
.DecimalField(
1266 null
=True, blank
=True, max_digits
=12, decimal_places
=2
1267 ) # Annuel (12 mois, 52 semaines, 364 jours?)
1268 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+')
1270 # commentaire = precision
1271 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
1273 # date_debut = anciennement date_effectif
1274 date_debut
= models
.DateField(
1275 u
"date de début", null
=True, blank
=True, db_index
=True
1277 date_fin
= models
.DateField(
1278 u
"date de fin", null
=True, blank
=True, db_index
=True
1283 ordering
= ['type__nom', '-date_fin']
1285 def __unicode__(self
):
1286 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
1289 class Remuneration_(RemunerationMixin
, DevisableMixin
):
1291 Structure de rémunération (données budgétaires) en situation normale
1292 pour un Dossier. Si un Evenement existe, utiliser la structure de
1293 rémunération EvenementRemuneration de cet événement.
1295 objects
= RemunerationManager()
1298 def find_yearly_range(from_date
, to_date
, year
):
1299 today
= date
.today()
1300 year
= year
or date
.today().year
1301 year_start
= date(year
, 1, 1)
1302 year_end
= date(year
, 12, 31)
1304 def constrain_to_year(*dates
):
1306 S'assure que les dates soient dans le range year_start a
1309 return [min(max(year_start
, d
), year_end
)
1313 from_date
or year_start
, year_start
)
1315 to_date
or year_end
, year_end
)
1317 start_date
, end_date
= constrain_to_year(start_date
, end_date
)
1319 jours_annee
= (year_end
- year_start
).days
1320 jours_dates
= (end_date
- start_date
).days
1321 factor
= Decimal(str(jours_dates
)) / Decimal(str(jours_annee
))
1323 return start_date
, end_date
, factor
1326 def montant_ajuste_euros(self
, annee
=None):
1328 Le montant ajusté représente le montant annuel, ajusté sur la
1329 période de temps travaillée, multipliée par le ratio de temps
1330 travaillé (en rapport au temps plein).
1332 date_debut
, date_fin
, factor
= self
.find_yearly_range(
1338 montant_euros
= Decimal(str(self
.montant_euros_float()) or '0')
1340 if self
.type.nature_remuneration
!= u
'Accessoire':
1341 dossier
= getattr(self
, 'dossier', None)
1344 Dans le cas d'un DossierComparaisonRemuneration, il
1345 n'y a plus de reference au dossier.
1347 regime_travail
= REGIME_TRAVAIL_DEFAULT
1349 regime_travail
= self
.dossier
.regime_travail
1350 return (montant_euros
* factor
*
1351 regime_travail
/ 100)
1353 return montant_euros
1355 def montant_mois(self
):
1356 return round(self
.montant
/ 12, 2)
1358 def montant_avec_regime(self
):
1359 return round(self
.montant
* (self
.dossier
.regime_travail
/ 100), 2)
1361 def montant_euro_mois(self
):
1362 return round(self
.montant_euros() / 12, 2)
1364 def __unicode__(self
):
1366 devise
= self
.devise
.code
1369 return "%s %s" % (self
.montant
, devise
)
1373 verbose_name
= u
"Rémunération"
1374 verbose_name_plural
= u
"Rémunérations"
1377 class Remuneration(Remuneration_
):
1378 dossier
= models
.ForeignKey(
1379 Dossier
, db_column
='dossier', related_name
='rh_remunerations'
1382 reversion
.register(Remuneration
, format
='xml')
1387 class Contrat_(models
.Model
):
1389 Document juridique qui encadre la relation de travail d'un Employe
1390 pour un Poste particulier. Pour un Dossier (qui documente cette
1391 relation de travail) plusieurs contrats peuvent être associés.
1393 objects
= ContratManager()
1394 type_contrat
= models
.ForeignKey(
1395 'TypeContrat', db_column
='type_contrat',
1396 verbose_name
=u
'type de contrat', related_name
='+'
1398 date_debut
= models
.DateField(
1399 u
"date de début", db_index
=True
1401 date_fin
= models
.DateField(
1402 u
"date de fin", null
=True, blank
=True, db_index
=True
1404 fichier
= models
.FileField(
1405 upload_to
=contrat_dispatch
, storage
=storage_prive
, null
=True,
1411 ordering
= ['dossier__employe__nom']
1412 verbose_name
= u
"Contrat"
1413 verbose_name_plural
= u
"Contrats"
1415 def __unicode__(self
):
1416 return u
'%s - %s' % (self
.dossier
, self
.id)
1419 class Contrat(Contrat_
):
1420 dossier
= models
.ForeignKey(
1421 Dossier
, db_column
='dossier', related_name
='rh_contrats'
1424 reversion
.register(Contrat
, format
='xml')
1429 class CategorieEmploi(models
.Model
):
1431 Catégorie utilisée dans la gestion des Postes.
1432 Catégorie supérieure à TypePoste.
1434 nom
= models
.CharField(max_length
=255)
1438 verbose_name
= u
"catégorie d'emploi"
1439 verbose_name_plural
= u
"catégories d'emploi"
1441 def __unicode__(self
):
1444 reversion
.register(CategorieEmploi
, format
='xml')
1447 class FamilleProfessionnelle(models
.Model
):
1449 Famille professionnelle d'un poste.
1451 nom
= models
.CharField(max_length
=100)
1455 verbose_name
= u
'famille professionnelle'
1456 verbose_name_plural
= u
'familles professionnelles'
1458 def __unicode__(self
):
1461 reversion
.register(FamilleProfessionnelle
, format
='xml')
1464 class TypePoste(Archivable
):
1468 nom
= models
.CharField(max_length
=255)
1469 nom_feminin
= models
.CharField(u
"nom féminin", max_length
=255)
1470 is_responsable
= models
.BooleanField(
1471 u
"poste de responsabilité", default
=False
1473 categorie_emploi
= models
.ForeignKey(
1474 CategorieEmploi
, db_column
='categorie_emploi', related_name
='+',
1475 verbose_name
=u
"catégorie d'emploi"
1477 famille_professionnelle
= models
.ForeignKey(
1478 FamilleProfessionnelle
, related_name
='types_de_poste',
1479 verbose_name
=u
"famille professionnelle", blank
=True, null
=True
1484 verbose_name
= u
"Type de poste"
1485 verbose_name_plural
= u
"Types de poste"
1487 def __unicode__(self
):
1488 return u
'%s' % (self
.nom
)
1490 reversion
.register(TypePoste
, format
='xml')
1493 TYPE_PAIEMENT_CHOICES
= (
1494 (u
'Régulier', u
'Régulier'),
1495 (u
'Ponctuel', u
'Ponctuel'),
1498 NATURE_REMUNERATION_CHOICES
= (
1499 (u
'Traitement', u
'Traitements'),
1500 (u
'Indemnité', u
'Indemnités autres'),
1501 (u
'Charges', u
'Charges patronales'),
1502 (u
'Accessoire', u
'Accessoires'),
1503 (u
'RAS', u
'Rémunération autre source'),
1507 class TypeRemuneration(Archivable
):
1509 Catégorie de Remuneration.
1512 objects
= models
.Manager()
1513 sans_archives
= ArchivableManager()
1515 nom
= models
.CharField(max_length
=255)
1516 type_paiement
= models
.CharField(
1517 u
"type de paiement", max_length
=30, choices
=TYPE_PAIEMENT_CHOICES
1520 nature_remuneration
= models
.CharField(
1521 u
"nature de la rémunération", max_length
=30,
1522 choices
=NATURE_REMUNERATION_CHOICES
1527 verbose_name
= u
"Type de rémunération"
1528 verbose_name_plural
= u
"Types de rémunération"
1530 def __unicode__(self
):
1533 reversion
.register(TypeRemuneration
, format
='xml')
1536 class TypeRevalorisation(Archivable
):
1538 Justification du changement de la Remuneration.
1539 (Actuellement utilisé dans aucun traitement informatique.)
1541 nom
= models
.CharField(max_length
=255)
1545 verbose_name
= u
"Type de revalorisation"
1546 verbose_name_plural
= u
"Types de revalorisation"
1548 def __unicode__(self
):
1549 return u
'%s' % (self
.nom
)
1551 reversion
.register(TypeRevalorisation
, format
='xml')
1554 class Service(Archivable
):
1556 Unité administrative où les Postes sont rattachés.
1558 nom
= models
.CharField(max_length
=255)
1562 verbose_name
= u
"service"
1563 verbose_name_plural
= u
"services"
1565 def __unicode__(self
):
1568 reversion
.register(Service
, format
='xml')
1571 TYPE_ORGANISME_CHOICES
= (
1572 ('MAD', 'Mise à disposition'),
1573 ('DET', 'Détachement'),
1577 class OrganismeBstg(models
.Model
):
1579 Organisation d'où provient un Employe mis à disposition (MAD) de
1580 ou détaché (DET) à l'AUF à titre gratuit.
1582 (BSTG = bien et service à titre gratuit.)
1584 nom
= models
.CharField(max_length
=255)
1585 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1586 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1588 related_name
='organismes_bstg',
1589 null
=True, blank
=True)
1592 ordering
= ['type', 'nom']
1593 verbose_name
= u
"Organisme BSTG"
1594 verbose_name_plural
= u
"Organismes BSTG"
1596 def __unicode__(self
):
1597 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1599 reversion
.register(OrganismeBstg
, format
='xml')
1602 class Statut(Archivable
):
1604 Statut de l'Employe dans le cadre d'un Dossier particulier.
1607 code
= models
.CharField(
1608 max_length
=25, unique
=True,
1610 u
"Saisir un code court mais lisible pour ce statut : "
1611 u
"le code est utilisé pour associer les statuts aux autres "
1612 u
"données tout en demeurant plus lisible qu'un identifiant "
1616 nom
= models
.CharField(max_length
=255)
1620 verbose_name
= u
"Statut d'employé"
1621 verbose_name_plural
= u
"Statuts d'employé"
1623 def __unicode__(self
):
1624 return u
'%s : %s' % (self
.code
, self
.nom
)
1626 reversion
.register(Statut
, format
='xml')
1629 TYPE_CLASSEMENT_CHOICES
= (
1630 ('S', 'S -Soutien'),
1631 ('T', 'T - Technicien'),
1632 ('P', 'P - Professionel'),
1634 ('D', 'D - Direction'),
1635 ('SO', 'SO - Sans objet [expatriés]'),
1636 ('HG', 'HG - Hors grille [direction]'),
1640 class ClassementManager(models
.Manager
):
1642 Ordonner les spcéfiquement les classements.
1644 def get_query_set(self
):
1645 qs
= super(ClassementManager
, self
).get_query_set()
1646 qs
= qs
.extra(select
={
1647 'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'
1649 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1653 class ClassementArchivableManager(ClassementManager
,
1658 class Classement_(Archivable
):
1660 Éléments de classement de la
1661 "Grille générique de classement hiérarchique".
1663 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1664 classement dans la grille. Le classement donne le coefficient utilisé dans:
1666 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1668 objects
= ClassementManager()
1669 sans_archives
= ClassementArchivableManager()
1672 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1673 echelon
= models
.IntegerField(u
"échelon", blank
=True, default
=0)
1674 degre
= models
.IntegerField(u
"degré", blank
=True, default
=0)
1675 coefficient
= models
.FloatField(u
"coefficient", blank
=True, null
=True)
1678 # annee # au lieu de date_debut et date_fin
1679 commentaire
= models
.TextField(null
=True, blank
=True)
1683 ordering
= ['type', 'echelon', 'degre', 'coefficient']
1684 verbose_name
= u
"Classement"
1685 verbose_name_plural
= u
"Classements"
1687 def __unicode__(self
):
1688 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1691 class Classement(Classement_
):
1692 __doc__
= Classement_
.__doc__
1694 reversion
.register(Classement
, format
='xml')
1697 class TauxChange_(models
.Model
):
1699 Taux de change de la devise vers l'euro (EUR)
1700 pour chaque année budgétaire.
1703 devise
= models
.ForeignKey('Devise', db_column
='devise')
1704 annee
= models
.IntegerField(u
"année")
1705 taux
= models
.FloatField(u
"taux vers l'euro")
1709 ordering
= ['-annee', 'devise__code']
1710 verbose_name
= u
"Taux de change"
1711 verbose_name_plural
= u
"Taux de change"
1712 unique_together
= ('devise', 'annee')
1714 def __unicode__(self
):
1715 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1718 class TauxChange(TauxChange_
):
1719 __doc__
= TauxChange_
.__doc__
1721 reversion
.register(TauxChange
, format
='xml')
1724 class ValeurPointManager(models
.Manager
):
1726 def get_query_set(self
):
1727 return super(ValeurPointManager
, self
).get_query_set() \
1728 .select_related('devise', 'implantation')
1731 class ValeurPoint_(models
.Model
):
1733 Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1734 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1735 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1737 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1740 objects
= models
.Manager()
1741 actuelles
= ValeurPointManager()
1743 valeur
= models
.FloatField(null
=True)
1744 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1745 implantation
= models
.ForeignKey(ref
.Implantation
,
1746 db_column
='implantation',
1747 related_name
='%(app_label)s_valeur_point')
1749 annee
= models
.IntegerField()
1752 ordering
= ['-annee', 'implantation__nom']
1754 verbose_name
= u
"Valeur du point"
1755 verbose_name_plural
= u
"Valeurs du point"
1756 unique_together
= ('implantation', 'annee')
1758 def __unicode__(self
):
1759 return u
'%s %s %s [%s] %s' % (
1760 self
.devise
.code
, self
.annee
, self
.valeur
,
1761 self
.implantation
.nom_court
, self
.devise
.nom
1765 class ValeurPoint(ValeurPoint_
):
1766 __doc__
= ValeurPoint_
.__doc__
1768 reversion
.register(ValeurPoint
, format
='xml')
1771 class Devise(Archivable
):
1775 code
= models
.CharField(max_length
=10, unique
=True)
1776 nom
= models
.CharField(max_length
=255)
1780 verbose_name
= u
"devise"
1781 verbose_name_plural
= u
"devises"
1783 def __unicode__(self
):
1784 return u
'%s - %s' % (self
.code
, self
.nom
)
1786 reversion
.register(Devise
, format
='xml')
1789 class TypeContrat(Archivable
):
1793 nom
= models
.CharField(max_length
=255)
1794 nom_long
= models
.CharField(max_length
=255)
1798 verbose_name
= u
"Type de contrat"
1799 verbose_name_plural
= u
"Types de contrat"
1801 def __unicode__(self
):
1802 return u
'%s' % (self
.nom
)
1804 reversion
.register(TypeContrat
, format
='xml')
1809 class ResponsableImplantationProxy(ref
.Implantation
):
1817 verbose_name
= u
"Responsable d'implantation"
1818 verbose_name_plural
= u
"Responsables d'implantation"
1821 class ResponsableImplantation(models
.Model
):
1823 Le responsable d'une implantation.
1824 Anciennement géré sur le Dossier du responsable.
1826 employe
= models
.ForeignKey(
1827 'Employe', db_column
='employe', related_name
='+', null
=True,
1830 implantation
= models
.OneToOneField(
1831 "ResponsableImplantationProxy", db_column
='implantation',
1832 related_name
='responsable', unique
=True
1835 def __unicode__(self
):
1836 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1839 ordering
= ['implantation__nom']
1840 verbose_name
= "Responsable d'implantation"
1841 verbose_name_plural
= "Responsables d'implantation"
1843 reversion
.register(ResponsableImplantation
, format
='xml')
1846 class UserProfile(models
.Model
):
1847 user
= models
.OneToOneField(User
, related_name
='profile')
1848 zones_administratives
= models
.ManyToManyField(
1849 ref
.ZoneAdministrative
,
1850 related_name
='profiles'
1853 verbose_name
= "Permissions sur zones administratives"
1854 verbose_name_plural
= "Permissions sur zones administratives"
1856 def __unicode__(self
):
1857 return self
.user
.__unicode__()
1859 reversion
.register(UserProfile
, format
='xml')
1863 TYPES_CHANGEMENT
= (
1870 class ChangementPersonnelNotifications(models
.Model
):
1872 verbose_name
= u
"Destinataire pour notices de mouvement de personnel"
1873 verbose_name_plural
= u
"Destinataires pour notices de mouvement de personnel"
1875 type = models
.CharField(
1877 choices
= TYPES_CHANGEMENT
,
1881 destinataires
= models
.ManyToManyField(
1883 related_name
='changement_notifications',
1886 def __unicode__(self
):
1888 self
.get_type_display(), ','.join(
1889 self
.destinataires
.all().values_list(
1890 'courriel', flat
=True))
1894 class ChangementPersonnel(models
.Model
):
1896 Une notice qui enregistre un changement de personnel, incluant:
1899 * Mouvement de personnel
1904 verbose_name
= u
"Mouvement de personnel"
1905 verbose_name_plural
= u
"Mouvements de personnel"
1907 def __unicode__(self
):
1908 return '%s: %s' % (self
.dossier
.__unicode__(),
1909 self
.get_type_display())
1912 def create_changement(cls
, dossier
, type):
1913 # If this employe has existing Changement, set them to invalid.
1914 cls
.objects
.filter(dossier__employe
=dossier
.employe
).update(valide
=False)
1926 def post_save_handler(cls
,
1933 # This defines the time limit used when checking in previous
1934 # files to see if an employee if new. Basically, if emloyee
1935 # left his position new_file.date_debut -
1936 # NEW_EMPLOYE_THRESHOLD +1 ago (compared to date_debut), then
1937 # if a new file is created for this employee, he will bec
1938 # onsidered "NEW" and a notice will be created to this effect.
1939 NEW_EMPLOYE_THRESHOLD
= datetime
.timedelta(7) # 7 days.
1941 other_dossier_qs
= instance
.employe
.rh_dossiers
.exclude(
1943 dd
= instance
.date_debut
1944 df
= instance
.date_fin
1946 # Here, verify differences between the instance, before and
1948 df_has_changed
= False
1952 df_has_changed
= True
1954 df_has_changed
= (df
!= instance
.before_save
.date_fin
and
1960 # Date de fin est None et c'est une nouvelle instance de
1962 if not df
and created
:
1963 # QS for finding other dossiers with a date_fin of None OR
1964 # with a date_fin >= to this dossier's date_debut
1965 exists_recent_file_qs
= other_dossier_qs
.filter(
1966 Q(date_fin__isnull
=True) |
1967 Q(date_fin__gte
=dd
- NEW_EMPLOYE_THRESHOLD
)
1970 # 1. If existe un Dossier récent, et c'est une nouvelle
1971 # instance de Dossier:
1972 if exists_recent_file_qs
.count() > 0:
1973 cls
.create_changement(
1977 # 2. Il n'existe un Dossier récent, et c'est une nouvelle
1978 # instance de Dossier:
1980 cls
.create_changement(
1986 # Date de fin a été modifiée:
1988 # QS for other active files (date_fin == None), excludes
1990 exists_active_files_qs
= other_dossier_qs
.filter(
1991 Q(date_fin__isnull
=True))
1993 # 3. Date de fin a été modifiée et il n'existe aucun autre
1994 # dossier actifs: Depart
1995 if exists_active_files_qs
.count() == 0:
1996 cls
.create_changement(
2000 # 4. Dossier a une nouvelle date de fin par contre
2001 # d'autres dossiers actifs existent déjà: Mouvement
2003 cls
.create_changement(
2009 dossier
= models
.ForeignKey(
2011 related_name
='mouvements',
2014 valide
= models
.BooleanField(default
=True)
2015 date_creation
= models
.DateTimeField(
2017 communique
= models
.BooleanField(
2021 date_communication
= models
.DateTimeField(
2026 type = models
.CharField(
2028 choices
= TYPES_CHANGEMENT
,
2031 reversion
.register(ChangementPersonnel
, format
='xml')
2034 def dossier_pre_save_handler(sender
,
2038 # Store a copy of the model before save is called.
2039 if instance
.pk
is not None:
2040 instance
.before_save
= Dossier
.objects
.get(pk
=instance
.pk
)
2042 instance
.before_save
= None
2045 # Connect a pre_save handler that assigns a copy of the model as an
2046 # attribute in order to compare it in post_save.
2047 pre_save
.connect(dossier_pre_save_handler
, sender
=Dossier
)
2049 post_save
.connect(ChangementPersonnel
.post_save_handler
, sender
=Dossier
)
2050 post_save
.connect(RHDossierClassementRecord
.post_save_handler
, sender
=Dossier
)