1 # -=- encoding: utf-8 -=-
3 from datetime
import date
4 from decimal
import Decimal
6 from django
.core
.files
.storage
import FileSystemStorage
7 from django
.db
import models
8 from django
.conf
import settings
10 from auf
.django
.metadata
.models
import AUFMetadata
11 from auf
.django
.metadata
.managers
import NoDeleteManager
12 import auf
.django
.references
.models
as ref
13 from validators
import validate_date_passee
14 from dae
.managers
import SecurityManager
17 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
18 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
22 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
23 base_url
=settings
.PRIVE_MEDIA_URL
)
25 def poste_piece_dispatch(instance
, filename
):
26 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
29 def dossier_piece_dispatch(instance
, filename
):
30 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
33 def employe_piece_dispatch(instance
, filename
):
34 path
= "employe/%s/%s" % (instance
.employe_id
, filename
)
38 class Commentaire(AUFMetadata
):
39 texte
= models
.TextField()
40 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+')
44 ordering
= ['-date_creation']
46 def __unicode__(self
):
47 return u
'%s' % (self
.texte
)
52 POSTE_APPEL_CHOICES
= (
53 ('interne', 'Interne'),
54 ('externe', 'Externe'),
57 class PosteManager(SecurityManager
):
59 Chargement de tous les objets FK existants sur chaque QuerySet.
61 prefixe_implantation
= "implantation__region"
63 def get_query_set(self
):
68 return super(PosteManager
, self
).get_query_set().select_related(*fkeys
).all()
70 class Poste_(AUFMetadata
):
71 """Un Poste est un emploi (job) à combler dans une implantation.
72 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
73 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
76 objects
= PosteManager()
79 nom
= models
.CharField(max_length
=255,
80 verbose_name
= u
"Titre du poste", )
81 nom_feminin
= models
.CharField(max_length
=255,
82 verbose_name
= u
"Titre du poste (au féminin)",
84 implantation
= models
.ForeignKey(ref
.Implantation
,
85 db_column
='implantation', related_name
='+')
86 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste',
89 service
= models
.ForeignKey('Service', db_column
='service',
91 verbose_name
= u
"Direction/Service/Pôle support",
92 default
=1) # default = Rectorat
93 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
95 verbose_name
= u
"Poste du responsable",
96 default
=149) # default = Recteur
99 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
100 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
101 verbose_name
= u
"Temps de travail",
102 help_text
="% du temps complet")
103 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
104 decimal_places
=2, null
=True,
105 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
106 verbose_name
= u
"Nb. heures par semaine")
109 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
110 null
=True, blank
=True)
111 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
112 null
=True, blank
=True)
113 mise_a_disposition
= models
.NullBooleanField(
114 verbose_name
= u
"Mise à disposition",
115 null
=True, default
=False)
116 appel
= models
.CharField(max_length
=10, null
=True,
117 verbose_name
= u
"Appel à candidature",
118 choices
=POSTE_APPEL_CHOICES
,
122 classement_min
= models
.ForeignKey('Classement',
123 db_column
='classement_min', related_name
='+',
124 null
=True, blank
=True)
125 classement_max
= models
.ForeignKey('Classement',
126 db_column
='classement_max', related_name
='+',
127 null
=True, blank
=True)
128 valeur_point_min
= models
.ForeignKey('ValeurPoint',
129 db_column
='valeur_point_min', related_name
='+',
130 null
=True, blank
=True)
131 valeur_point_max
= models
.ForeignKey('ValeurPoint',
132 db_column
='valeur_point_max', related_name
='+',
133 null
=True, blank
=True)
134 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
135 related_name
='+', default
=5)
136 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
137 related_name
='+', default
=5)
138 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
139 null
=True, default
=0)
140 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
141 null
=True, default
=0)
142 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
143 null
=True, default
=0)
144 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
145 null
=True, default
=0)
146 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
147 null
=True, default
=0)
148 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
149 null
=True, default
=0)
151 # Comparatifs de rémunération
152 devise_comparaison
= models
.ForeignKey('Devise', null
=True,
153 db_column
='devise_comparaison',
156 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
157 null
=True, blank
=True)
158 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
159 null
=True, blank
=True)
160 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
161 null
=True, blank
=True)
162 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
163 null
=True, blank
=True)
164 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
165 null
=True, blank
=True)
166 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
167 null
=True, blank
=True)
168 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
169 null
=True, blank
=True)
170 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
171 null
=True, blank
=True)
172 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
173 null
=True, blank
=True)
174 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
175 null
=True, blank
=True)
178 justification
= models
.TextField(null
=True, blank
=True)
181 date_validation
= models
.DateTimeField(null
=True, blank
=True) # de dae
182 date_debut
= models
.DateField(verbose_name
=u
"Date de début",
183 null
=True, blank
=True)
184 date_fin
= models
.DateField(verbose_name
=u
"Date de fin",
185 null
=True, blank
=True)
189 ordering
= ['implantation__nom', 'nom']
190 verbose_name
= u
"Poste"
191 verbose_name_plural
= u
"Postes"
193 def __unicode__(self
):
194 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
197 representation
= representation
+ u
' (VACANT)'
198 return representation
202 if self
.occupe_par():
206 def occupe_par(self
):
207 """Retourne la liste d'employé occupant ce poste.
208 Généralement, retourne une liste d'un élément.
209 Si poste inoccupé, retourne liste vide.
211 return [d
.employe
for d
in self
.dossiers
.filter(actif
=True, supprime
=False) \
212 .exclude(date_fin__lt
=date
.today())]
214 prefix_implantation
= "implantation__region"
215 def get_regions(self
):
216 return [self
.implantation
.region
]
220 __doc__
= Poste_
.__doc__
223 POSTE_FINANCEMENT_CHOICES
= (
224 ('A', 'A - Frais de personnel'),
225 ('B', 'B - Projet(s)-Titre(s)'),
230 class PosteFinancement_(models
.Model
):
231 """Pour un Poste, structure d'informations décrivant comment on prévoit
234 poste
= models
.ForeignKey('Poste', db_column
='poste',
235 related_name
='%(app_label)s_financements')
236 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
237 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
238 help_text
="ex.: 33.33 % (décimale avec point)")
239 commentaire
= models
.TextField(
240 help_text
="Spécifiez la source de financement.")
246 def __unicode__(self
):
247 return u
'%s : %s %' % (self
.type, self
.pourcentage
)
250 class PosteFinancement(PosteFinancement_
):
251 __doc__
= PosteFinancement_
.__doc__
254 class PostePiece(models
.Model
):
255 """Documents relatifs au Poste.
256 Ex.: Description de poste
258 poste
= models
.ForeignKey('Poste', db_column
='poste',
259 related_name
='pieces')
260 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
261 fichier
= models
.FileField(verbose_name
= u
"Fichier",
262 upload_to
=poste_piece_dispatch
,
263 storage
=storage_prive
)
268 def __unicode__(self
):
269 return u
'%s' % (self
.nom
)
271 class PosteComparaison(models
.Model
):
273 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
275 poste
= models
.ForeignKey('Poste', related_name
='comparaisons_internes')
276 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
277 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
278 montant
= models
.IntegerField(null
=True)
279 devise
= models
.ForeignKey("Devise", default
=5, related_name
='+', null
=True, blank
=True)
281 def taux_devise(self
):
282 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.implantation
)
283 if len(liste_taux
) == 0:
284 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.implantation
))
286 return liste_taux
[0].taux
288 def montant_euros(self
):
289 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
292 class PosteCommentaire(Commentaire
):
293 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='+')
302 SITUATION_CHOICES
= (
303 ('C', 'Célibataire'),
308 class Employe(AUFMetadata
):
309 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
310 Dossiers qu'il occupe ou a occupé de Postes.
312 Cette classe aurait pu avantageusement s'appeler Personne car la notion
313 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
316 nom
= models
.CharField(max_length
=255)
317 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
318 nom_affichage
= models
.CharField(max_length
=255,
319 verbose_name
= u
"Nom d'affichage",
320 null
=True, blank
=True)
321 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
322 db_column
='nationalite',
323 related_name
='employes_nationalite',
324 verbose_name
= u
"Nationalité",
325 blank
=True, null
=True)
326 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
327 validators
=[validate_date_passee
],
328 null
=True, blank
=True)
329 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
332 situation_famille
= models
.CharField(max_length
=1,
333 choices
=SITUATION_CHOICES
,
334 verbose_name
= u
"Situation familiale",
335 null
=True, blank
=True)
336 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
337 null
=True, blank
=True)
340 tel_domicile
= models
.CharField(max_length
=255,
341 verbose_name
= u
"Tél. domicile",
342 null
=True, blank
=True)
343 tel_cellulaire
= models
.CharField(max_length
=255,
344 verbose_name
= u
"Tél. cellulaire",
345 null
=True, blank
=True)
346 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
347 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
348 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
349 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
350 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
351 related_name
='employes',
352 null
=True, blank
=True)
355 ordering
= ['nom_affichage','nom','prenom']
356 verbose_name
= u
"Employé"
357 verbose_name_plural
= u
"Employés"
359 def __unicode__(self
):
360 return u
'%s [%s]' % (self
.get_nom(), self
.id)
363 nom_affichage
= self
.nom_affichage
364 if not nom_affichage
:
365 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
370 if self
.genre
.upper() == u
'M':
372 elif self
.genre
.upper() == u
'F':
377 """Retourne l'URL du service retournant la photo de l'Employe.
378 Équivalent reverse url 'rh_photo' avec id en param.
380 from django
.core
.urlresolvers
import reverse
381 return reverse('rh_photo', kwargs
={'id':self
.id})
383 def dossiers_passes(self
):
385 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
386 for d
in dossiers_passes
:
388 return dossiers_passes
390 def dossiers_futurs(self
):
392 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
394 def dossiers_encours(self
):
395 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
396 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
397 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
399 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
400 for d
in dossiers_encours
:
401 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
402 return dossiers_encours
404 def postes_encours(self
):
405 postes_encours
= set()
406 for d
in self
.dossiers_encours():
407 postes_encours
.add(d
.poste
)
408 return postes_encours
410 def poste_principal(self
):
412 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
414 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
416 poste
= Poste
.objects
.none()
418 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
423 prefix_implantation
= "dossiers__poste__implantation__region"
424 def get_regions(self
):
426 for d
in self
.dossiers
.all():
427 regions
.append(d
.poste
.implantation
.region
)
431 class EmployePiece(models
.Model
):
432 """Documents relatifs à un employé.
435 employe
= models
.ForeignKey('Employe', db_column
='employe')
436 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
437 fichier
= models
.FileField(verbose_name
="Fichier",
438 upload_to
=employe_piece_dispatch
,
439 storage
=storage_prive
)
443 verbose_name
= u
"Employé pièce"
444 verbose_name_plural
= u
"Employé pièces"
446 def __unicode__(self
):
447 return u
'%s' % (self
.nom
)
449 class EmployeCommentaire(Commentaire
):
450 employe
= models
.ForeignKey('Employe', db_column
='employe',
454 verbose_name
= u
"Employé commentaire"
455 verbose_name_plural
= u
"Employé commentaires"
458 LIEN_PARENTE_CHOICES
= (
459 ('Conjoint', 'Conjoint'),
460 ('Conjointe', 'Conjointe'),
465 class AyantDroit(AUFMetadata
):
466 """Personne en relation avec un Employe.
469 nom
= models
.CharField(max_length
=255)
470 prenom
= models
.CharField(max_length
=255,
471 verbose_name
= u
"Prénom",)
472 nom_affichage
= models
.CharField(max_length
=255,
473 verbose_name
= u
"Nom d'affichage",
474 null
=True, blank
=True)
475 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
476 db_column
='nationalite',
477 related_name
='ayantdroits_nationalite',
478 verbose_name
= u
"Nationalité",
479 null
=True, blank
=True)
480 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
481 validators
=[validate_date_passee
],
482 null
=True, blank
=True)
483 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
486 employe
= models
.ForeignKey('Employe', db_column
='employe',
487 related_name
='ayantdroits',
488 verbose_name
= u
"Employé")
489 lien_parente
= models
.CharField(max_length
=10,
490 choices
=LIEN_PARENTE_CHOICES
,
491 verbose_name
= u
"Lien de parenté",
492 null
=True, blank
=True)
495 ordering
= ['nom_affichage']
496 verbose_name
= u
"Ayant droit"
497 verbose_name_plural
= u
"Ayants droit"
499 def __unicode__(self
):
500 return u
'%s' % (self
.get_nom())
503 nom_affichage
= self
.nom_affichage
504 if not nom_affichage
:
505 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
508 prefix_implantation
= "employe__dossiers__poste__implantation__region"
509 def get_regions(self
):
511 for d
in self
.employe
.dossiers
.all():
512 regions
.append(d
.poste
.implantation
.region
)
516 class AyantDroitCommentaire(Commentaire
):
517 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
523 STATUT_RESIDENCE_CHOICES
= (
525 ('expat', 'Expatrié'),
528 COMPTE_COMPTA_CHOICES
= (
534 class Dossier_(AUFMetadata
):
535 """Le Dossier regroupe les informations relatives à l'occupation
536 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
539 Plusieurs Contrats peuvent être associés au Dossier.
540 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
541 lequel aucun Dossier n'existe est un poste vacant.
544 employe
= models
.ForeignKey('Employe', db_column
='employe',
545 related_name
='dossiers',
546 verbose_name
=u
"Employé")
548 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='dossiers')
549 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
551 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
552 db_column
='organisme_bstg',
554 verbose_name
= u
"Organisme",
555 help_text
="Si détaché (DET) ou \
556 mis à disposition (MAD), \
557 préciser l'organisme.",
558 null
=True, blank
=True)
561 remplacement
= models
.BooleanField(default
=False)
562 remplacement_de
= models
.ForeignKey('self', related_name
='+',
563 null
=True, blank
=True)
564 statut_residence
= models
.CharField(max_length
=10, default
='local',
565 verbose_name
= u
"Statut", null
=True,
566 choices
=STATUT_RESIDENCE_CHOICES
)
569 classement
= models
.ForeignKey('Classement', db_column
='classement',
571 null
=True, blank
=True)
572 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
574 default
=REGIME_TRAVAIL_DEFAULT
,
575 verbose_name
= u
"Régime de travail",
576 help_text
="% du temps complet")
577 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
578 decimal_places
=2, null
=True,
579 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
580 verbose_name
= u
"Nb. heures par semaine")
582 # Occupation du Poste par cet Employe (anciennement "mandat")
583 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
585 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
587 null
=True, blank
=True)
594 ordering
= ['employe__nom', ]
595 verbose_name
= u
"Dossier"
596 verbose_name_plural
= "Dossiers"
598 def salaire_theorique(self
):
599 annee
= date
.today().year
600 coeff
= self
.classement
.coefficient
601 implantation
= self
.poste
.implantation
602 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
604 montant
= coeff
* point
.valeur
605 devise
= point
.devise
606 return {'montant':montant
, 'devise':devise
}
608 def __unicode__(self
):
609 poste
= self
.poste
.nom
610 if self
.employe
.genre
== 'F':
611 poste
= self
.poste
.nom_feminin
612 return u
'%s - %s' % (self
.employe
, poste
)
614 prefix_implantation
= "poste__implantation__region"
615 def get_regions(self
):
616 return [self
.poste
.implantation
.region
]
619 def remunerations(self
):
620 return self
.rh_remuneration_remunerations
.all().order_by('date_debut')
622 def get_salaire(self
):
624 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
628 class Dossier(Dossier_
):
629 __doc__
= Dossier_
.__doc__
632 class DossierPiece(models
.Model
):
633 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
634 Ex.: Lettre de motivation.
636 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
638 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
639 fichier
= models
.FileField(verbose_name
= u
"Fichier",
640 upload_to
=dossier_piece_dispatch
,
641 storage
=storage_prive
)
646 def __unicode__(self
):
647 return u
'%s' % (self
.nom
)
649 class DossierCommentaire(Commentaire
):
650 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
653 class DossierComparaison(models
.Model
):
655 Photo d'une comparaison salariale au moment de l'embauche.
657 dossier
= models
.ForeignKey('Dossier', related_name
='comparaisons')
658 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
659 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
660 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
661 montant
= models
.IntegerField(null
=True)
662 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
664 def taux_devise(self
):
665 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.dossier
.poste
.implantation
)
666 if len(liste_taux
) == 0:
667 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.dossier
.poste
.implantation
))
669 return liste_taux
[0].taux
671 def montant_euros(self
):
672 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
677 class RemunerationMixin(AUFMetadata
):
679 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
680 related_name
='%(app_label)s_%(class)s_remunerations')
681 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
683 verbose_name
= u
"Type de rémunération")
684 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
685 db_column
='type_revalorisation',
687 verbose_name
= u
"Type de revalorisation",
688 null
=True, blank
=True)
689 montant
= models
.FloatField(null
=True, blank
=True,
691 # Annuel (12 mois, 52 semaines, 364 jours?)
692 devise
= models
.ForeignKey('Devise', to_field
='id',
693 db_column
='devise', related_name
='+',
695 # commentaire = precision
696 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
697 # date_debut = anciennement date_effectif
698 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
699 null
=True, blank
=True)
700 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
701 null
=True, blank
=True)
705 ordering
= ['type__nom', '-date_fin']
707 def __unicode__(self
):
708 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
710 class Remuneration_(RemunerationMixin
):
711 """Structure de rémunération (données budgétaires) en situation normale
712 pour un Dossier. Si un Evenement existe, utiliser la structure de
713 rémunération EvenementRemuneration de cet événement.
716 def montant_mois(self
):
717 return round(self
.montant
/ 12, 2)
719 def taux_devise(self
):
720 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
722 def montant_euro(self
):
723 return round(float(self
.montant
) / float(self
.taux_devise()), 2)
725 def montant_euro_mois(self
):
726 return round(self
.montant_euro() / 12, 2)
728 def __unicode__(self
):
730 devise
= self
.devise
.code
733 return "%s %s" % (self
.montant
, devise
)
737 verbose_name
= u
"Rémunération"
738 verbose_name_plural
= u
"Rémunérations"
741 class Remuneration(Remuneration_
):
742 __doc__
= Remuneration_
.__doc__
747 class ContratManager(NoDeleteManager
):
748 def get_query_set(self
):
749 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
752 class Contrat(AUFMetadata
):
753 """Document juridique qui encadre la relation de travail d'un Employe
754 pour un Poste particulier. Pour un Dossier (qui documente cette
755 relation de travail) plusieurs contrats peuvent être associés.
758 objects
= ContratManager()
760 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
761 related_name
='contrats')
762 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
764 verbose_name
= u
"Type de contrat")
765 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
766 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
767 null
=True, blank
=True)
770 ordering
= ['dossier__employe__nom_affichage']
771 verbose_name
= u
"Contrat"
772 verbose_name_plural
= u
"Contrats"
774 def __unicode__(self
):
775 return u
'%s - %s' % (self
.dossier
, self
.id)
777 # TODO? class ContratPiece(models.Model):
782 class Evenement_(AUFMetadata
):
783 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
784 d'un Dossier qui vient altérer des informations normales liées à un Dossier
785 (ex.: la Remuneration).
787 Ex.: congé de maternité, maladie...
789 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
790 différent et une rémunération en conséquence. On souhaite toutefois
791 conserver le Dossier intact afin d'éviter une re-saisie des données lors
792 du retour à la normale.
794 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
796 nom
= models
.CharField(max_length
=255)
797 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
798 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
799 null
=True, blank
=True)
804 verbose_name
= u
"Évènement"
805 verbose_name_plural
= u
"Évènements"
807 def __unicode__(self
):
808 return u
'%s' % (self
.nom
)
811 class Evenement(Evenement_
):
812 __doc__
= Evenement_
.__doc__
815 class EvenementRemuneration_(RemunerationMixin
):
816 """Structure de rémunération liée à un Evenement qui remplace
817 temporairement la Remuneration normale d'un Dossier, pour toute la durée
820 evenement
= models
.ForeignKey("Evenement", db_column
='evenement',
822 verbose_name
= u
"Évènement")
823 # TODO : le champ dossier hérité de Remuneration doit être dérivé
824 # de l'Evenement associé
828 ordering
= ['evenement', 'type__nom', '-date_fin']
829 verbose_name
= u
"Évènement - rémunération"
830 verbose_name_plural
= u
"Évènements - rémunérations"
833 class EvenementRemuneration(EvenementRemuneration_
):
834 __doc__
= EvenementRemuneration_
.__doc__
840 class EvenementRemuneration(EvenementRemuneration_
):
841 __doc__
= EvenementRemuneration_
.__doc__
846 class FamilleEmploi(AUFMetadata
):
847 """Catégorie utilisée dans la gestion des Postes.
848 Catégorie supérieure à TypePoste.
850 nom
= models
.CharField(max_length
=255)
854 verbose_name
= u
"Famille d'emploi"
855 verbose_name_plural
= u
"Familles d'emploi"
857 def __unicode__(self
):
858 return u
'%s' % (self
.nom
)
860 class TypePoste(AUFMetadata
):
861 """Catégorie de Poste.
863 nom
= models
.CharField(max_length
=255)
864 nom_feminin
= models
.CharField(max_length
=255,
865 verbose_name
= u
"Nom féminin")
867 is_responsable
= models
.BooleanField(default
=False,
868 verbose_name
= u
"Poste de responsabilité")
869 famille_emploi
= models
.ForeignKey('FamilleEmploi',
870 db_column
='famille_emploi',
872 verbose_name
= u
"Famille d'emploi")
876 verbose_name
= u
"Type de poste"
877 verbose_name_plural
= u
"Types de poste"
879 def __unicode__(self
):
880 return u
'%s' % (self
.nom
)
883 TYPE_PAIEMENT_CHOICES
= (
884 ('Régulier', 'Régulier'),
885 ('Ponctuel', 'Ponctuel'),
888 NATURE_REMUNERATION_CHOICES
= (
889 ('Accessoire', 'Accessoire'),
890 ('Charges', 'Charges'),
891 ('Indemnité', 'Indemnité'),
892 ('RAS', 'Rémunération autre source'),
893 ('Traitement', 'Traitement'),
896 class TypeRemuneration(AUFMetadata
):
897 """Catégorie de Remuneration.
899 nom
= models
.CharField(max_length
=255)
900 type_paiement
= models
.CharField(max_length
=30,
901 choices
=TYPE_PAIEMENT_CHOICES
,
902 verbose_name
= u
"Type de paiement")
903 nature_remuneration
= models
.CharField(max_length
=30,
904 choices
=NATURE_REMUNERATION_CHOICES
,
905 verbose_name
= u
"Nature de la rémunération")
909 verbose_name
= u
"Type de rémunération"
910 verbose_name_plural
= u
"Types de rémunération"
912 def __unicode__(self
):
913 return u
'%s' % (self
.nom
)
915 class TypeRevalorisation(AUFMetadata
):
916 """Justification du changement de la Remuneration.
917 (Actuellement utilisé dans aucun traitement informatique.)
919 nom
= models
.CharField(max_length
=255)
923 verbose_name
= u
"Type de revalorisation"
924 verbose_name_plural
= u
"Types de revalorisation"
926 def __unicode__(self
):
927 return u
'%s' % (self
.nom
)
929 class Service(AUFMetadata
):
930 """Unité administrative où les Postes sont rattachés.
932 nom
= models
.CharField(max_length
=255)
936 verbose_name
= u
"Service"
937 verbose_name_plural
= u
"Services"
939 def __unicode__(self
):
940 return u
'%s' % (self
.nom
)
943 TYPE_ORGANISME_CHOICES
= (
944 ('MAD', 'Mise à disposition'),
945 ('DET', 'Détachement'),
948 class OrganismeBstg(AUFMetadata
):
949 """Organisation d'où provient un Employe mis à disposition (MAD) de
950 ou détaché (DET) à l'AUF à titre gratuit.
952 (BSTG = bien et service à titre gratuit.)
954 nom
= models
.CharField(max_length
=255)
955 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
956 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
958 related_name
='organismes_bstg',
959 null
=True, blank
=True)
962 ordering
= ['type', 'nom']
963 verbose_name
= u
"Organisme BSTG"
964 verbose_name_plural
= u
"Organismes BSTG"
966 def __unicode__(self
):
967 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
969 prefix_implantation
= "pays__region"
970 def get_regions(self
):
971 return [self
.pays
.region
]
974 class Statut(AUFMetadata
):
975 """Statut de l'Employe dans le cadre d'un Dossier particulier.
978 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.")
979 nom
= models
.CharField(max_length
=255)
983 verbose_name
= u
"Statut d'employé"
984 verbose_name_plural
= u
"Statuts d'employé"
986 def __unicode__(self
):
987 return u
'%s : %s' % (self
.code
, self
.nom
)
990 TYPE_CLASSEMENT_CHOICES
= (
992 ('T', 'T - Technicien'),
993 ('P', 'P - Professionel'),
995 ('D', 'D - Direction'),
996 ('SO', 'SO - Sans objet [expatriés]'),
997 ('HG', 'HG - Hors grille [direction]'),
1001 class Classement_(AUFMetadata
):
1002 """Éléments de classement de la
1003 "Grille générique de classement hiérarchique".
1005 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1006 classement dans la grille. Le classement donne le coefficient utilisé dans:
1008 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1011 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1012 echelon
= models
.IntegerField(verbose_name
=u
"Échelon", blank
=True, default
=0)
1013 degre
= models
.IntegerField(verbose_name
=u
"Degré", blank
=True, default
=0)
1014 coefficient
= models
.FloatField(default
=0, verbose_name
=u
"Coefficient",
1017 # annee # au lieu de date_debut et date_fin
1018 commentaire
= models
.TextField(null
=True, blank
=True)
1022 ordering
= ['type','echelon','degre','coefficient']
1023 verbose_name
= u
"Classement"
1024 verbose_name_plural
= u
"Classements"
1026 def __unicode__(self
):
1027 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1030 class Classement(Classement_
):
1031 __doc__
= Classement_
.__doc__
1034 class TauxChange_(AUFMetadata
):
1035 """Taux de change de la devise vers l'euro (EUR)
1036 pour chaque année budgétaire.
1039 devise
= models
.ForeignKey('Devise', db_column
='devise')
1040 annee
= models
.IntegerField(verbose_name
= u
"Année")
1041 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1045 ordering
= ['-annee', 'devise__code']
1046 verbose_name
= u
"Taux de change"
1047 verbose_name_plural
= u
"Taux de change"
1049 def __unicode__(self
):
1050 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1053 class TauxChange(TauxChange_
):
1054 __doc__
= TauxChange_
.__doc__
1056 class ValeurPointManager(NoDeleteManager
):
1057 def get_query_set(self
):
1058 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1061 class ValeurPoint_(AUFMetadata
):
1062 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1063 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1064 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1066 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1069 actuelles
= ValeurPointManager()
1071 valeur
= models
.FloatField(null
=True)
1072 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1073 related_name
='+', default
=5)
1074 implantation
= models
.ForeignKey(ref
.Implantation
,
1075 db_column
='implantation',
1076 related_name
='%(app_label)s_valeur_point')
1078 annee
= models
.IntegerField()
1081 ordering
= ['-annee', 'implantation__nom']
1083 verbose_name
= u
"Valeur du point"
1084 verbose_name_plural
= u
"Valeurs du point"
1086 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
1087 def get_tauxchange_courant(self
):
1089 Recherche le taux courant associé à la valeur d'un point.
1090 Tous les taux de l'année courante sont chargés, pour optimiser un
1091 affichage en liste. (On pourrait probablement améliorer le manager pour
1092 lui greffer le taux courant sous forme de JOIN)
1094 for tauxchange
in self
.tauxchange
:
1095 if tauxchange
.implantation_id
== self
.implantation_id
:
1099 def __unicode__(self
):
1100 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1103 class ValeurPoint(ValeurPoint_
):
1104 __doc__
= ValeurPoint_
.__doc__
1107 class Devise(AUFMetadata
):
1108 """Devise monétaire.
1110 code
= models
.CharField(max_length
=10, unique
=True)
1111 nom
= models
.CharField(max_length
=255)
1115 verbose_name
= u
"Devise"
1116 verbose_name_plural
= u
"Devises"
1118 def __unicode__(self
):
1119 return u
'%s - %s' % (self
.code
, self
.nom
)
1121 class TypeContrat(AUFMetadata
):
1124 nom
= models
.CharField(max_length
=255)
1125 nom_long
= models
.CharField(max_length
=255)
1129 verbose_name
= u
"Type de contrat"
1130 verbose_name_plural
= u
"Types de contrat"
1132 def __unicode__(self
):
1133 return u
'%s' % (self
.nom
)
1138 class ResponsableImplantation(AUFMetadata
):
1139 """Le responsable d'une implantation.
1140 Anciennement géré sur le Dossier du responsable.
1142 employe
= models
.ForeignKey('Employe', db_column
='employe',
1144 null
=True, blank
=True)
1145 implantation
= models
.ForeignKey(ref
.Implantation
,
1146 db_column
='implantation', related_name
='+',
1149 def __unicode__(self
):
1150 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1153 ordering
= ['implantation__nom']
1154 verbose_name
= "Responsable d'implantation"
1155 verbose_name_plural
= "Responsables d'implantation"