1 # -=- encoding: utf-8 -=-
3 from datetime
import date
5 from django
.core
.files
.storage
import FileSystemStorage
6 from django
.db
import models
7 from django
.conf
import settings
9 from auf
.django
.metadata
.models
import AUFMetadata
10 from auf
.django
.metadata
.managers
import NoDeleteManager
11 import auf
.django
.references
.models
as ref
12 from validators
import validate_date_passee
13 from dae
.managers
import SecurityManager
16 REGIME_TRAVAIL_DEFAULT
= 100.00
17 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= 35.00
21 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
22 base_url
=settings
.PRIVE_MEDIA_URL
)
24 def poste_piece_dispatch(instance
, filename
):
25 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
28 def dossier_piece_dispatch(instance
, filename
):
29 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
32 def employe_piece_dispatch(instance
, filename
):
33 path
= "employe/%s/%s" % (instance
.employe_id
, filename
)
37 class Commentaire(AUFMetadata
):
38 texte
= models
.TextField()
39 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+')
43 ordering
= ['-date_creation']
45 def __unicode__(self
):
46 return u
'%s' % (self
.texte
)
51 POSTE_APPEL_CHOICES
= (
52 ('interne', 'Interne'),
53 ('externe', 'Externe'),
56 class PosteManager(SecurityManager
):
58 Chargement de tous les objets FK existants sur chaque QuerySet.
60 prefixe_implantation
= "implantation__region"
62 def get_query_set(self
):
67 return super(PosteManager
, self
).get_query_set().select_related(*fkeys
).all()
69 class Poste_(AUFMetadata
):
70 """Un Poste est un emploi (job) à combler dans une implantation.
71 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
72 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
75 objects
= PosteManager()
78 nom
= models
.CharField(max_length
=255,
79 verbose_name
= u
"Titre du poste", )
80 nom_feminin
= models
.CharField(max_length
=255,
81 verbose_name
= u
"Titre du poste (au féminin)",
83 implantation
= models
.ForeignKey(ref
.Implantation
,
84 db_column
='implantation', related_name
='+')
85 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste',
88 service
= models
.ForeignKey('Service', db_column
='service', null
=True,
90 verbose_name
= u
"Direction/Service/Pôle support",
91 default
=1) # default = Rectorat
92 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
93 related_name
='+', null
=True,
94 verbose_name
= u
"Poste du responsable",
95 default
=149) # default = Recteur
98 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
99 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
100 verbose_name
= u
"Temps de travail",
101 help_text
="% du temps complet")
102 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
103 decimal_places
=2, null
=True,
104 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
105 verbose_name
= u
"Nb. heures par semaine")
108 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
109 null
=True, blank
=True)
110 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
111 null
=True, blank
=True)
112 mise_a_disposition
= models
.NullBooleanField(
113 verbose_name
= u
"Mise à disposition",
114 null
=True, default
=False)
115 appel
= models
.CharField(max_length
=10, null
=True,
116 verbose_name
= u
"Appel à candidature",
117 choices
=POSTE_APPEL_CHOICES
,
121 classement_min
= models
.ForeignKey('Classement',
122 db_column
='classement_min', related_name
='+',
123 null
=True, blank
=True)
124 classement_max
= models
.ForeignKey('Classement',
125 db_column
='classement_max', related_name
='+',
126 null
=True, blank
=True)
127 valeur_point_min
= models
.ForeignKey('ValeurPoint',
128 db_column
='valeur_point_min', related_name
='+',
129 null
=True, blank
=True)
130 valeur_point_max
= models
.ForeignKey('ValeurPoint',
131 db_column
='valeur_point_max', related_name
='+',
132 null
=True, blank
=True)
133 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
134 related_name
='+', default
=5)
135 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
136 related_name
='+', default
=5)
137 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
138 null
=True, default
=0)
139 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
140 null
=True, default
=0)
141 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
142 null
=True, default
=0)
143 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
144 null
=True, default
=0)
145 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
146 null
=True, default
=0)
147 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
148 null
=True, default
=0)
150 # Comparatifs de rémunération
151 devise_comparaison
= models
.ForeignKey('Devise', null
=True,
152 db_column
='devise_comparaison',
155 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
156 null
=True, blank
=True)
157 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
158 null
=True, blank
=True)
159 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
160 null
=True, blank
=True)
161 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
162 null
=True, blank
=True)
163 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
164 null
=True, blank
=True)
165 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
166 null
=True, blank
=True)
167 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
168 null
=True, blank
=True)
169 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
170 null
=True, blank
=True)
171 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
172 null
=True, blank
=True)
173 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
174 null
=True, blank
=True)
177 justification
= models
.TextField(null
=True, blank
=True)
180 date_validation
= models
.DateTimeField(null
=True, blank
=True) # de dae
181 date_debut
= models
.DateField(verbose_name
=u
"Date de début",
182 null
=True, blank
=True)
183 date_fin
= models
.DateField(verbose_name
=u
"Date de fin",
184 null
=True, blank
=True)
188 ordering
= ['implantation__nom', 'nom']
189 verbose_name
= u
"Poste"
190 verbose_name_plural
= u
"Postes"
192 def __unicode__(self
):
193 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
196 representation
= representation
+ u
' (VACANT)'
197 return representation
201 if self
.occupe_par():
205 def occupe_par(self
):
206 """Retourne la liste d'employé occupant ce poste.
207 Généralement, retourne une liste d'un élément.
208 Si poste inoccupé, retourne liste vide.
210 return [d
.employe
for d
in self
.dossiers
.filter(actif
=True, supprime
=False) \
211 .exclude(date_fin__lt
=date
.today())]
213 prefix_implantation
= "implantation__region"
214 def get_regions(self
):
215 return [self
.implantation
.region
]
219 __doc__
= Poste_
.__doc__
223 __doc__
= Poste_
.__doc__
226 POSTE_FINANCEMENT_CHOICES
= (
227 ('A', 'A - Frais de personnel'),
228 ('B', 'B - Projet(s)-Titre(s)'),
233 class PosteFinancement_(models
.Model
):
234 """Pour un Poste, structure d'informations décrivant comment on prévoit
237 poste
= models
.ForeignKey('Poste', db_column
='poste',
238 related_name
='%(app_label)s_financements')
239 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
240 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
241 help_text
="ex.: 33.33 % (décimale avec point)")
242 commentaire
= models
.TextField(
243 help_text
="Spécifiez la source de financement.")
249 def __unicode__(self
):
250 return u
'%s : %s %' % (self
.type, self
.pourcentage
)
253 class PosteFinancement(PosteFinancement_
):
254 __doc__
= PosteFinancement_
.__doc__
257 class PostePiece(models
.Model
):
258 """Documents relatifs au Poste.
259 Ex.: Description de poste
261 poste
= models
.ForeignKey('Poste', db_column
='poste',
262 related_name
='pieces')
263 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
264 fichier
= models
.FileField(verbose_name
= u
"Fichier",
265 upload_to
=poste_piece_dispatch
,
266 storage
=storage_prive
)
271 def __unicode__(self
):
272 return u
'%s' % (self
.nom
)
274 class PosteComparaison(models
.Model
):
276 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
278 poste
= models
.ForeignKey('Poste', related_name
='comparaisons_internes')
279 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
280 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
281 montant
= models
.IntegerField(null
=True)
282 devise
= models
.ForeignKey("Devise", default
=5, related_name
='+', null
=True, blank
=True)
284 def taux_devise(self
):
285 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.implantation
)
286 if len(liste_taux
) == 0:
287 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.implantation
))
289 return liste_taux
[0].taux
291 def montant_euros(self
):
292 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
295 class PosteCommentaire(Commentaire
):
296 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='+')
305 SITUATION_CHOICES
= (
306 ('C', 'Célibataire'),
311 class Employe(AUFMetadata
):
312 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
313 Dossiers qu'il occupe ou a occupé de Postes.
315 Cette classe aurait pu avantageusement s'appeler Personne car la notion
316 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
319 nom
= models
.CharField(max_length
=255)
320 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
321 nom_affichage
= models
.CharField(max_length
=255,
322 verbose_name
= u
"Nom d'affichage",
323 null
=True, blank
=True)
324 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
325 db_column
='nationalite',
326 related_name
='employes_nationalite',
327 verbose_name
= u
"Nationalité")
328 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
329 validators
=[validate_date_passee
],
330 null
=True, blank
=True)
331 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
334 situation_famille
= models
.CharField(max_length
=1,
335 choices
=SITUATION_CHOICES
,
336 verbose_name
= u
"Situation familiale",
337 null
=True, blank
=True)
338 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
339 null
=True, blank
=True)
342 tel_domicile
= models
.CharField(max_length
=255,
343 verbose_name
= u
"Tél. domicile",
344 null
=True, blank
=True)
345 tel_cellulaire
= models
.CharField(max_length
=255,
346 verbose_name
= u
"Tél. cellulaire",
347 null
=True, blank
=True)
348 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
349 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
350 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
351 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
352 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
353 related_name
='employes',
354 null
=True, blank
=True)
357 ordering
= ['nom_affichage','nom','prenom']
358 verbose_name
= u
"Employé"
359 verbose_name_plural
= u
"Employés"
361 def __unicode__(self
):
362 return u
'%s [%s]' % (self
.get_nom(), self
.id)
365 nom_affichage
= self
.nom_affichage
366 if not nom_affichage
:
367 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
372 if self
.genre
.upper() == u
'M':
374 elif self
.genre
.upper() == u
'F':
379 """Retourne l'URL du service retournant la photo de l'Employe.
380 Équivalent reverse url 'rh_photo' avec id en param.
382 from django
.core
.urlresolvers
import reverse
383 return reverse('rh_photo', kwargs
={'id':self
.id})
385 def dossiers_passes(self
):
387 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
388 for d
in dossiers_passes
:
390 return dossiers_passes
392 def dossiers_futurs(self
):
394 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
396 def dossiers_encours(self
):
397 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
398 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
399 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
401 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
402 for d
in dossiers_encours
:
403 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
404 return dossiers_encours
406 def postes_encours(self
):
407 postes_encours
= set()
408 for d
in self
.dossiers_encours():
409 postes_encours
.add(d
.poste
)
410 return postes_encours
412 def poste_principal(self
):
414 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
416 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
418 poste
= Poste
.objects
.none()
420 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
425 prefix_implantation
= "dossiers__poste__implantation__region"
426 def get_regions(self
):
428 for d
in self
.dossiers
.all():
429 regions
.append(d
.poste
.implantation
.region
)
433 class EmployePiece(models
.Model
):
434 """Documents relatifs à un employé.
437 employe
= models
.ForeignKey('Employe', db_column
='employe')
438 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
439 fichier
= models
.FileField(verbose_name
="Fichier",
440 upload_to
=employe_piece_dispatch
,
441 storage
=storage_prive
)
445 verbose_name
= u
"Employé pièce"
446 verbose_name_plural
= u
"Employé pièces"
448 def __unicode__(self
):
449 return u
'%s' % (self
.nom
)
451 class EmployeCommentaire(Commentaire
):
452 employe
= models
.ForeignKey('Employe', db_column
='employe',
456 verbose_name
= u
"Employé commentaire"
457 verbose_name_plural
= u
"Employé commentaires"
460 LIEN_PARENTE_CHOICES
= (
461 ('Conjoint', 'Conjoint'),
462 ('Conjointe', 'Conjointe'),
467 class AyantDroit(AUFMetadata
):
468 """Personne en relation avec un Employe.
471 nom
= models
.CharField(max_length
=255)
472 prenom
= models
.CharField(max_length
=255,
473 verbose_name
= u
"Prénom",)
474 nom_affichage
= models
.CharField(max_length
=255,
475 verbose_name
= u
"Nom d'affichage",
476 null
=True, blank
=True)
477 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
478 db_column
='nationalite',
479 related_name
='ayantdroits_nationalite',
480 verbose_name
= u
"Nationalité")
481 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
482 validators
=[validate_date_passee
],
483 null
=True, blank
=True)
484 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
487 employe
= models
.ForeignKey('Employe', db_column
='employe',
488 related_name
='ayantdroits',
489 verbose_name
= u
"Employé")
490 lien_parente
= models
.CharField(max_length
=10,
491 choices
=LIEN_PARENTE_CHOICES
,
492 verbose_name
= u
"Lien de parenté",
493 null
=True, blank
=True)
496 ordering
= ['nom_affichage']
497 verbose_name
= u
"Ayant droit"
498 verbose_name_plural
= u
"Ayants droit"
500 def __unicode__(self
):
501 return u
'%s' % (self
.get_nom())
504 nom_affichage
= self
.nom_affichage
505 if not nom_affichage
:
506 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
509 prefix_implantation
= "employe__dossiers__poste__implantation__region"
510 def get_regions(self
):
512 for d
in self
.employe
.dossiers
.all():
513 regions
.append(d
.poste
.implantation
.region
)
517 class AyantDroitCommentaire(Commentaire
):
518 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
524 STATUT_RESIDENCE_CHOICES
= (
526 ('expat', 'Expatrié'),
529 COMPTE_COMPTA_CHOICES
= (
535 class Dossier_(AUFMetadata
):
536 """Le Dossier regroupe les informations relatives à l'occupation
537 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
540 Plusieurs Contrats peuvent être associés au Dossier.
541 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
542 lequel aucun Dossier n'existe est un poste vacant.
545 employe
= models
.ForeignKey('Employe', db_column
='employe',
546 related_name
='dossiers',
547 verbose_name
=u
"Employé")
549 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='dossiers')
550 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
552 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
553 db_column
='organisme_bstg',
555 verbose_name
= u
"Organisme",
556 help_text
="Si détaché (DET) ou \
557 mis à disposition (MAD), \
558 préciser l'organisme.",
559 null
=True, blank
=True)
562 remplacement
= models
.BooleanField(default
=False)
563 remplacement_de
= models
.ForeignKey('self', related_name
='+',
564 null
=True, blank
=True)
565 statut_residence
= models
.CharField(max_length
=10, default
='local',
566 verbose_name
= u
"Statut", null
=True,
567 choices
=STATUT_RESIDENCE_CHOICES
)
570 classement
= models
.ForeignKey('Classement', db_column
='classement',
572 null
=True, blank
=True)
573 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
575 default
=REGIME_TRAVAIL_DEFAULT
,
576 verbose_name
= u
"Régime de travail",
577 help_text
="% du temps complet")
578 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
579 decimal_places
=2, null
=True,
580 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
581 verbose_name
= u
"Nb. heures par semaine")
583 # Occupation du Poste par cet Employe (anciennement "mandat")
584 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
586 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
588 null
=True, blank
=True)
595 ordering
= ['employe__nom', ]
596 verbose_name
= u
"Dossier"
597 verbose_name_plural
= "Dossiers"
599 def salaire_theorique(self
):
600 annee
= date
.today().year
601 coeff
= self
.classement
.coefficient
602 implantation
= self
.poste
.implantation
603 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
605 montant
= coeff
* point
.valeur
606 devise
= point
.devise
607 return {'montant':montant
, 'devise':devise
}
609 def __unicode__(self
):
610 poste
= self
.poste
.nom
611 if self
.employe
.genre
== 'F':
612 poste
= self
.poste
.nom_feminin
613 return u
'%s - %s' % (self
.employe
, poste
)
615 prefix_implantation
= "poste__implantation__region"
616 def get_regions(self
):
617 return [self
.poste
.implantation
.region
]
620 def remunerations(self
):
621 return self
.rh_remuneration_remunerations
.all().order_by('date_debut')
623 def get_salaire(self
):
625 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
629 class Dossier(Dossier_
):
630 __doc__
= Dossier_
.__doc__
633 class DossierPiece(models
.Model
):
634 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
635 Ex.: Lettre de motivation.
637 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
639 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
640 fichier
= models
.FileField(verbose_name
= u
"Fichier",
641 upload_to
=dossier_piece_dispatch
,
642 storage
=storage_prive
)
647 def __unicode__(self
):
648 return u
'%s' % (self
.nom
)
650 class DossierCommentaire(Commentaire
):
651 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
654 class DossierComparaison(models
.Model
):
656 Photo d'une comparaison salariale au moment de l'embauche.
658 dossier
= models
.ForeignKey('Dossier', related_name
='comparaisons')
659 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
660 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
661 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
662 montant
= models
.IntegerField(null
=True)
663 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
665 def taux_devise(self
):
666 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.dossier
.poste
.implantation
)
667 if len(liste_taux
) == 0:
668 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.dossier
.poste
.implantation
))
670 return liste_taux
[0].taux
672 def montant_euros(self
):
673 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
678 class RemunerationMixin(AUFMetadata
):
680 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
681 related_name
='%(app_label)s_%(class)s_remunerations')
682 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
684 verbose_name
= u
"Type de rémunération")
685 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
686 db_column
='type_revalorisation',
688 verbose_name
= u
"Type de revalorisation",
689 null
=True, blank
=True)
690 montant
= models
.FloatField(null
=True, blank
=True,
692 # Annuel (12 mois, 52 semaines, 364 jours?)
693 devise
= models
.ForeignKey('Devise', to_field
='id',
694 db_column
='devise', related_name
='+',
696 # commentaire = precision
697 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
698 # date_debut = anciennement date_effectif
699 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
700 null
=True, blank
=True)
701 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
702 null
=True, blank
=True)
706 ordering
= ['type__nom', '-date_fin']
708 def __unicode__(self
):
709 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
711 class Remuneration_(RemunerationMixin
):
712 """Structure de rémunération (données budgétaires) en situation normale
713 pour un Dossier. Si un Evenement existe, utiliser la structure de
714 rémunération EvenementRemuneration de cet événement.
717 def montant_mois(self
):
718 return round(self
.montant
/ 12, 2)
720 def taux_devise(self
):
721 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
723 def montant_euro(self
):
724 return round(float(self
.montant
) / float(self
.taux_devise()), 2)
726 def montant_euro_mois(self
):
727 return round(self
.montant_euro() / 12, 2)
729 def __unicode__(self
):
731 devise
= self
.devise
.code
734 return "%s %s" % (self
.montant
, devise
)
738 verbose_name
= u
"Rémunération"
739 verbose_name_plural
= u
"Rémunérations"
742 class Remuneration(Remuneration_
):
743 __doc__
= Remuneration_
.__doc__
748 class ContratManager(NoDeleteManager
):
749 def get_query_set(self
):
750 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
753 class Contrat(AUFMetadata
):
754 """Document juridique qui encadre la relation de travail d'un Employe
755 pour un Poste particulier. Pour un Dossier (qui documente cette
756 relation de travail) plusieurs contrats peuvent être associés.
759 objects
= ContratManager()
761 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
762 related_name
='contrats')
763 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
765 verbose_name
= u
"Type de contrat")
766 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
767 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
768 null
=True, blank
=True)
771 ordering
= ['dossier__employe__nom_affichage']
772 verbose_name
= u
"Contrat"
773 verbose_name_plural
= u
"Contrats"
775 def __unicode__(self
):
776 return u
'%s - %s' % (self
.dossier
, self
.id)
778 # TODO? class ContratPiece(models.Model):
783 class Evenement_(AUFMetadata
):
784 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
785 d'un Dossier qui vient altérer des informations normales liées à un Dossier
786 (ex.: la Remuneration).
788 Ex.: congé de maternité, maladie...
790 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
791 différent et une rémunération en conséquence. On souhaite toutefois
792 conserver le Dossier intact afin d'éviter une re-saisie des données lors
793 du retour à la normale.
795 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
797 nom
= models
.CharField(max_length
=255)
798 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
799 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
800 null
=True, blank
=True)
805 verbose_name
= u
"Évènement"
806 verbose_name_plural
= u
"Évènements"
808 def __unicode__(self
):
809 return u
'%s' % (self
.nom
)
812 class Evenement(Evenement_
):
813 __doc__
= Evenement_
.__doc__
816 class EvenementRemuneration_(RemunerationMixin
):
817 """Structure de rémunération liée à un Evenement qui remplace
818 temporairement la Remuneration normale d'un Dossier, pour toute la durée
821 evenement
= models
.ForeignKey("Evenement", db_column
='evenement',
823 verbose_name
= u
"Évènement")
824 # TODO : le champ dossier hérité de Remuneration doit être dérivé
825 # de l'Evenement associé
829 ordering
= ['evenement', 'type__nom', '-date_fin']
830 verbose_name
= u
"Évènement - rémunération"
831 verbose_name_plural
= u
"Évènements - rémunérations"
834 class EvenementRemuneration(EvenementRemuneration_
):
835 __doc__
= EvenementRemuneration_
.__doc__
841 class EvenementRemuneration(EvenementRemuneration_
):
842 __doc__
= EvenementRemuneration_
.__doc__
847 class FamilleEmploi(AUFMetadata
):
848 """Catégorie utilisée dans la gestion des Postes.
849 Catégorie supérieure à TypePoste.
851 nom
= models
.CharField(max_length
=255)
855 verbose_name
= u
"Famille d'emploi"
856 verbose_name_plural
= u
"Familles d'emploi"
858 def __unicode__(self
):
859 return u
'%s' % (self
.nom
)
861 class TypePoste(AUFMetadata
):
862 """Catégorie de Poste.
864 nom
= models
.CharField(max_length
=255)
865 nom_feminin
= models
.CharField(max_length
=255,
866 verbose_name
= u
"Nom féminin")
868 is_responsable
= models
.BooleanField(default
=False,
869 verbose_name
= u
"Poste de responsabilité")
870 famille_emploi
= models
.ForeignKey('FamilleEmploi',
871 db_column
='famille_emploi',
873 verbose_name
= u
"Famille d'emploi")
877 verbose_name
= u
"Type de poste"
878 verbose_name_plural
= u
"Types de poste"
880 def __unicode__(self
):
881 return u
'%s' % (self
.nom
)
884 TYPE_PAIEMENT_CHOICES
= (
885 ('Régulier', 'Régulier'),
886 ('Ponctuel', 'Ponctuel'),
889 NATURE_REMUNERATION_CHOICES
= (
890 ('Accessoire', 'Accessoire'),
891 ('Charges', 'Charges'),
892 ('Indemnité', 'Indemnité'),
893 ('RAS', 'Rémunération autre source'),
894 ('Traitement', 'Traitement'),
897 class TypeRemuneration(AUFMetadata
):
898 """Catégorie de Remuneration.
900 nom
= models
.CharField(max_length
=255)
901 type_paiement
= models
.CharField(max_length
=30,
902 choices
=TYPE_PAIEMENT_CHOICES
,
903 verbose_name
= u
"Type de paiement")
904 nature_remuneration
= models
.CharField(max_length
=30,
905 choices
=NATURE_REMUNERATION_CHOICES
,
906 verbose_name
= u
"Nature de la rémunération")
910 verbose_name
= u
"Type de rémunération"
911 verbose_name_plural
= u
"Types de rémunération"
913 def __unicode__(self
):
914 return u
'%s' % (self
.nom
)
916 class TypeRevalorisation(AUFMetadata
):
917 """Justification du changement de la Remuneration.
918 (Actuellement utilisé dans aucun traitement informatique.)
920 nom
= models
.CharField(max_length
=255)
924 verbose_name
= u
"Type de revalorisation"
925 verbose_name_plural
= u
"Types de revalorisation"
927 def __unicode__(self
):
928 return u
'%s' % (self
.nom
)
930 class Service(AUFMetadata
):
931 """Unité administrative où les Postes sont rattachés.
933 nom
= models
.CharField(max_length
=255)
937 verbose_name
= u
"Service"
938 verbose_name_plural
= u
"Services"
940 def __unicode__(self
):
941 return u
'%s' % (self
.nom
)
944 TYPE_ORGANISME_CHOICES
= (
945 ('MAD', 'Mise à disposition'),
946 ('DET', 'Détachement'),
949 class OrganismeBstg(AUFMetadata
):
950 """Organisation d'où provient un Employe mis à disposition (MAD) de
951 ou détaché (DET) à l'AUF à titre gratuit.
953 (BSTG = bien et service à titre gratuit.)
955 nom
= models
.CharField(max_length
=255)
956 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
957 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
959 related_name
='organismes_bstg',
960 null
=True, blank
=True)
963 ordering
= ['type', 'nom']
964 verbose_name
= u
"Organisme BSTG"
965 verbose_name_plural
= u
"Organismes BSTG"
967 def __unicode__(self
):
968 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
970 prefix_implantation
= "pays__region"
971 def get_regions(self
):
972 return [self
.pays
.region
]
975 class Statut(AUFMetadata
):
976 """Statut de l'Employe dans le cadre d'un Dossier particulier.
979 code
= models
.CharField(max_length
=25, unique
=True, help_text
="Saisir un code court mais lisible pour ce statut : le code est utilisé pour associer les statuts aux autres données tout en demeurant plus lisible qu'un identifiant numérique.")
980 nom
= models
.CharField(max_length
=255)
984 verbose_name
= u
"Statut d'employé"
985 verbose_name_plural
= u
"Statuts d'employé"
987 def __unicode__(self
):
988 return u
'%s : %s' % (self
.code
, self
.nom
)
991 TYPE_CLASSEMENT_CHOICES
= (
993 ('T', 'T - Technicien'),
994 ('P', 'P - Professionel'),
996 ('D', 'D - Direction'),
997 ('SO', 'SO - Sans objet [expatriés]'),
998 ('HG', 'HG - Hors grille [direction]'),
1002 class Classement_(AUFMetadata
):
1003 """Éléments de classement de la
1004 "Grille générique de classement hiérarchique".
1006 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1007 classement dans la grille. Le classement donne le coefficient utilisé dans:
1009 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1012 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1013 echelon
= models
.IntegerField(verbose_name
= u
"Échelon")
1014 degre
= models
.IntegerField(verbose_name
= u
"Degré")
1015 coefficient
= models
.FloatField(default
=0, verbose_name
= u
"Coéfficient",
1018 # annee # au lieu de date_debut et date_fin
1019 commentaire
= models
.TextField(null
=True, blank
=True)
1023 ordering
= ['type','echelon','degre','coefficient']
1024 verbose_name
= u
"Classement"
1025 verbose_name_plural
= u
"Classements"
1027 def __unicode__(self
):
1028 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1031 class Classement(Classement_
):
1032 __doc__
= Classement_
.__doc__
1035 class TauxChange_(AUFMetadata
):
1036 """Taux de change de la devise vers l'euro (EUR)
1037 pour chaque année budgétaire.
1040 devise
= models
.ForeignKey('Devise', db_column
='devise')
1041 annee
= models
.IntegerField(verbose_name
= u
"Année")
1042 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1046 ordering
= ['-annee', 'devise__code']
1047 verbose_name
= u
"Taux de change"
1048 verbose_name_plural
= u
"Taux de change"
1050 def __unicode__(self
):
1051 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1054 class TauxChange(TauxChange_
):
1055 __doc__
= TauxChange_
.__doc__
1057 class ValeurPointManager(NoDeleteManager
):
1058 def get_query_set(self
):
1059 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1062 class ValeurPoint_(AUFMetadata
):
1063 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1064 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1065 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1067 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1070 actuelles
= ValeurPointManager()
1072 valeur
= models
.FloatField(null
=True)
1073 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1074 related_name
='+', default
=5)
1075 implantation
= models
.ForeignKey(ref
.Implantation
,
1076 db_column
='implantation',
1077 related_name
='%(app_label)s_valeur_point')
1079 annee
= models
.IntegerField()
1082 ordering
= ['-annee', 'implantation__nom']
1084 verbose_name
= u
"Valeur du point"
1085 verbose_name_plural
= u
"Valeurs du point"
1087 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
1088 def get_tauxchange_courant(self
):
1090 Recherche le taux courant associé à la valeur d'un point.
1091 Tous les taux de l'année courante sont chargés, pour optimiser un
1092 affichage en liste. (On pourrait probablement améliorer le manager pour
1093 lui greffer le taux courant sous forme de JOIN)
1095 for tauxchange
in self
.tauxchange
:
1096 if tauxchange
.implantation_id
== self
.implantation_id
:
1100 def __unicode__(self
):
1101 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1104 class ValeurPoint(ValeurPoint_
):
1105 __doc__
= ValeurPoint_
.__doc__
1108 class Devise(AUFMetadata
):
1109 """Devise monétaire.
1111 code
= models
.CharField(max_length
=10, unique
=True)
1112 nom
= models
.CharField(max_length
=255)
1116 verbose_name
= u
"Devise"
1117 verbose_name_plural
= u
"Devises"
1119 def __unicode__(self
):
1120 return u
'%s - %s' % (self
.code
, self
.nom
)
1122 class TypeContrat(AUFMetadata
):
1125 nom
= models
.CharField(max_length
=255)
1126 nom_long
= models
.CharField(max_length
=255)
1130 verbose_name
= u
"Type de contrat"
1131 verbose_name_plural
= u
"Types de contrat"
1133 def __unicode__(self
):
1134 return u
'%s' % (self
.nom
)
1139 class ResponsableImplantation(AUFMetadata
):
1140 """Le responsable d'une implantation.
1141 Anciennement géré sur le Dossier du responsable.
1143 employe
= models
.ForeignKey('Employe', db_column
='employe',
1145 null
=True, blank
=True)
1146 implantation
= models
.ForeignKey(ref
.Implantation
,
1147 db_column
='implantation', related_name
='+',
1150 def __unicode__(self
):
1151 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1154 ordering
= ['implantation__nom']
1155 verbose_name
= "Responsable d'implantation"
1156 verbose_name_plural
= "Responsables d'implantation"