1 # -=- encoding: utf-8 -=-
4 from datetime
import date
5 from decimal
import Decimal
7 from django
.core
.files
.storage
import FileSystemStorage
8 from django
.db
import models
9 from django
.conf
import settings
11 from auf
.django
.emploi
.models
import GENRE_CHOICES
, SITUATION_CHOICES
# devrait plutot être dans references
12 from auf
.django
.metadata
.models
import AUFMetadata
13 from auf
.django
.metadata
.managers
import NoDeleteManager
14 import auf
.django
.references
.models
as ref
15 from validators
import validate_date_passee
16 from managers
import PosteManager
, DossierManager
, DossierComparaisonManager
, PosteComparaisonManager
20 # Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
21 # Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
24 models_stack
= [s
[1].split('/')[-2] for s
in inspect
.stack() if s
[1].endswith('models.py')]
25 return models_stack
[-1]
29 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
30 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
31 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
34 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
35 base_url
=settings
.PRIVE_MEDIA_URL
)
37 def poste_piece_dispatch(instance
, filename
):
38 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
41 def dossier_piece_dispatch(instance
, filename
):
42 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
45 def employe_piece_dispatch(instance
, filename
):
46 path
= "employe/%s/%s" % (instance
.employe_id
, filename
)
50 class Commentaire(AUFMetadata
):
51 texte
= models
.TextField()
52 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+')
56 ordering
= ['-date_creation']
58 def __unicode__(self
):
59 return u
'%s' % (self
.texte
)
64 POSTE_APPEL_CHOICES
= (
65 ('interne', 'Interne'),
66 ('externe', 'Externe'),
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',
90 verbose_name
= u
"Direction/Service/Pôle support", )
91 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
92 related_name
='+', null
=True,
93 verbose_name
= u
"Poste du responsable", )
96 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
97 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
98 verbose_name
= u
"Temps de travail",
99 help_text
="% du temps complet")
100 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
101 decimal_places
=2, null
=True,
102 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
103 verbose_name
= u
"Nb. heures par semaine")
106 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
107 null
=True, blank
=True)
108 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
109 null
=True, blank
=True)
110 mise_a_disposition
= models
.NullBooleanField(
111 verbose_name
= u
"Mise à disposition",
112 null
=True, default
=False)
113 appel
= models
.CharField(max_length
=10, null
=True,
114 verbose_name
= u
"Appel à candidature",
115 choices
=POSTE_APPEL_CHOICES
,
119 classement_min
= models
.ForeignKey('Classement',
120 db_column
='classement_min', related_name
='+',
121 null
=True, blank
=True)
122 classement_max
= models
.ForeignKey('Classement',
123 db_column
='classement_max', related_name
='+',
124 null
=True, blank
=True)
125 valeur_point_min
= models
.ForeignKey('ValeurPoint',
126 db_column
='valeur_point_min', related_name
='+',
127 null
=True, blank
=True)
128 valeur_point_max
= models
.ForeignKey('ValeurPoint',
129 db_column
='valeur_point_max', related_name
='+',
130 null
=True, blank
=True)
131 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
132 related_name
='+', default
=5)
133 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
134 related_name
='+', default
=5)
135 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
136 null
=True, default
=0)
137 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
138 null
=True, default
=0)
139 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
140 null
=True, default
=0)
141 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
142 null
=True, default
=0)
143 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
144 null
=True, default
=0)
145 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
146 null
=True, default
=0)
148 # Comparatifs de rémunération
149 devise_comparaison
= models
.ForeignKey('Devise', null
=True,
150 db_column
='devise_comparaison',
153 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
154 null
=True, blank
=True)
155 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
156 null
=True, blank
=True)
157 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
158 null
=True, blank
=True)
159 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
160 null
=True, blank
=True)
161 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
162 null
=True, blank
=True)
163 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
164 null
=True, blank
=True)
165 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
166 null
=True, blank
=True)
167 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
168 null
=True, blank
=True)
169 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
170 null
=True, blank
=True)
171 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
172 null
=True, blank
=True)
175 justification
= models
.TextField(null
=True, blank
=True)
178 date_validation
= models
.DateTimeField(null
=True, blank
=True) # de dae
179 date_debut
= models
.DateField(verbose_name
=u
"Date de début",
180 null
=True, blank
=True)
181 date_fin
= models
.DateField(verbose_name
=u
"Date de fin",
182 null
=True, blank
=True)
186 ordering
= ['implantation__nom', 'nom']
187 verbose_name
= u
"Poste"
188 verbose_name_plural
= u
"Postes"
190 def __unicode__(self
):
191 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
194 representation
= representation
+ u
' (VACANT)'
195 return representation
199 if self
.occupe_par():
203 def occupe_par(self
):
204 """Retourne la liste d'employé occupant ce poste.
205 Généralement, retourne une liste d'un élément.
206 Si poste inoccupé, retourne liste vide.
208 return [d
.employe
for d
in self
.rh_dossiers
.filter(actif
=True, supprime
=False) \
209 .exclude(date_fin__lt
=date
.today())]
211 prefix_implantation
= "implantation__region"
212 def get_regions(self
):
213 return [self
.implantation
.region
]
217 __doc__
= Poste_
.__doc__
220 POSTE_FINANCEMENT_CHOICES
= (
221 ('A', 'A - Frais de personnel'),
222 ('B', 'B - Projet(s)-Titre(s)'),
227 class PosteFinancement_(models
.Model
):
228 """Pour un Poste, structure d'informations décrivant comment on prévoit
231 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_financements')
232 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
233 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
234 help_text
="ex.: 33.33 % (décimale avec point)")
235 commentaire
= models
.TextField(
236 help_text
="Spécifiez la source de financement.")
242 def __unicode__(self
):
243 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
246 class PosteFinancement(PosteFinancement_
):
250 class PostePiece_(models
.Model
):
251 """Documents relatifs au Poste.
252 Ex.: Description de poste
254 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_pieces')
255 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
256 fichier
= models
.FileField(verbose_name
= u
"Fichier",
257 upload_to
=poste_piece_dispatch
,
258 storage
=storage_prive
)
264 def __unicode__(self
):
265 return u
'%s' % (self
.nom
)
267 class PostePiece(PostePiece_
):
270 class PosteComparaison_(models
.Model
):
272 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
274 poste
= models
.ForeignKey('%s.Poste' % app_context(), related_name
='%(app_label)s_comparaisons_internes')
275 objects
= PosteComparaisonManager()
277 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
278 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
279 montant
= models
.IntegerField(null
=True)
280 devise
= models
.ForeignKey("Devise", default
=5, related_name
='+', null
=True, blank
=True)
285 def taux_devise(self
):
286 if self
.devise
.code
== "EUR":
288 annee
= self
.poste
.date_debut
.year
289 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
292 raise Exception(u
"Le taux de la devise %s n'a pas n'existe pas pour %s ou il existe plusieurs taux pour la même année" % (self
.devise
.id, annee
))
296 def montant_euros(self
):
297 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
299 class PosteComparaison(PosteComparaison_
):
302 class PosteCommentaire_(Commentaire
):
303 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='+')
308 class PosteCommentaire(PosteCommentaire_
):
313 class Employe(AUFMetadata
):
314 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
315 Dossiers qu'il occupe ou a occupé de Postes.
317 Cette classe aurait pu avantageusement s'appeler Personne car la notion
318 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
321 nom
= models
.CharField(max_length
=255)
322 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
323 nom_affichage
= models
.CharField(max_length
=255,
324 verbose_name
= u
"Nom d'affichage",
325 null
=True, blank
=True)
326 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
327 db_column
='nationalite',
328 related_name
='employes_nationalite',
329 verbose_name
= u
"Nationalité",
330 blank
=True, null
=True)
331 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
332 help_text
=HELP_TEXT_DATE
,
333 validators
=[validate_date_passee
],
334 null
=True, blank
=True)
335 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
338 situation_famille
= models
.CharField(max_length
=1,
339 choices
=SITUATION_CHOICES
,
340 verbose_name
= u
"Situation familiale",
341 null
=True, blank
=True)
342 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
343 null
=True, blank
=True)
346 tel_domicile
= models
.CharField(max_length
=255,
347 verbose_name
= u
"Tél. domicile",
348 null
=True, blank
=True)
349 tel_cellulaire
= models
.CharField(max_length
=255,
350 verbose_name
= u
"Tél. cellulaire",
351 null
=True, blank
=True)
352 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
353 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
354 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
355 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
356 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
357 related_name
='employes',
358 null
=True, blank
=True)
361 ordering
= ['nom_affichage','nom','prenom']
362 verbose_name
= u
"Employé"
363 verbose_name_plural
= u
"Employés"
365 def __unicode__(self
):
366 return u
'%s [%s]' % (self
.get_nom(), self
.id)
369 nom_affichage
= self
.nom_affichage
370 if not nom_affichage
:
371 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
376 if self
.genre
.upper() == u
'M':
378 elif self
.genre
.upper() == u
'F':
383 """Retourne l'URL du service retournant la photo de l'Employe.
384 Équivalent reverse url 'rh_photo' avec id en param.
386 from django
.core
.urlresolvers
import reverse
387 return reverse('rh_photo', kwargs
={'id':self
.id})
389 def dossiers_passes(self
):
391 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
392 for d
in dossiers_passes
:
394 return dossiers_passes
396 def dossiers_futurs(self
):
398 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
400 def dossiers_encours(self
):
401 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
402 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
403 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
405 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
406 for d
in dossiers_encours
:
407 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
408 return dossiers_encours
410 def postes_encours(self
):
411 postes_encours
= set()
412 for d
in self
.dossiers_encours():
413 postes_encours
.add(d
.poste
)
414 return postes_encours
416 def poste_principal(self
):
418 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
420 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
422 poste
= Poste
.objects
.none()
424 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
429 prefix_implantation
= "dossiers__poste__implantation__region"
430 def get_regions(self
):
432 for d
in self
.dossiers
.all():
433 regions
.append(d
.poste
.implantation
.region
)
437 class EmployePiece(models
.Model
):
438 """Documents relatifs à un employé.
441 employe
= models
.ForeignKey('Employe', db_column
='employe')
442 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
443 fichier
= models
.FileField(verbose_name
="Fichier",
444 upload_to
=employe_piece_dispatch
,
445 storage
=storage_prive
)
449 verbose_name
= u
"Employé pièce"
450 verbose_name_plural
= u
"Employé pièces"
452 def __unicode__(self
):
453 return u
'%s' % (self
.nom
)
455 class EmployeCommentaire(Commentaire
):
456 employe
= models
.ForeignKey('Employe', db_column
='employe',
460 verbose_name
= u
"Employé commentaire"
461 verbose_name_plural
= u
"Employé commentaires"
464 LIEN_PARENTE_CHOICES
= (
465 ('Conjoint', 'Conjoint'),
466 ('Conjointe', 'Conjointe'),
471 class AyantDroit(AUFMetadata
):
472 """Personne en relation avec un Employe.
475 nom
= models
.CharField(max_length
=255)
476 prenom
= models
.CharField(max_length
=255,
477 verbose_name
= u
"Prénom",)
478 nom_affichage
= models
.CharField(max_length
=255,
479 verbose_name
= u
"Nom d'affichage",
480 null
=True, blank
=True)
481 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
482 db_column
='nationalite',
483 related_name
='ayantdroits_nationalite',
484 verbose_name
= u
"Nationalité",
485 null
=True, blank
=True)
486 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
487 validators
=[validate_date_passee
],
488 null
=True, blank
=True)
489 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
492 employe
= models
.ForeignKey('Employe', db_column
='employe',
493 related_name
='ayantdroits',
494 verbose_name
= u
"Employé")
495 lien_parente
= models
.CharField(max_length
=10,
496 choices
=LIEN_PARENTE_CHOICES
,
497 verbose_name
= u
"Lien de parenté",
498 null
=True, blank
=True)
501 ordering
= ['nom_affichage']
502 verbose_name
= u
"Ayant droit"
503 verbose_name_plural
= u
"Ayants droit"
505 def __unicode__(self
):
506 return u
'%s' % (self
.get_nom())
509 nom_affichage
= self
.nom_affichage
510 if not nom_affichage
:
511 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
514 prefix_implantation
= "employe__dossiers__poste__implantation__region"
515 def get_regions(self
):
517 for d
in self
.employe
.dossiers
.all():
518 regions
.append(d
.poste
.implantation
.region
)
522 class AyantDroitCommentaire(Commentaire
):
523 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
529 STATUT_RESIDENCE_CHOICES
= (
531 ('expat', 'Expatrié'),
534 COMPTE_COMPTA_CHOICES
= (
540 class Dossier_(AUFMetadata
):
541 """Le Dossier regroupe les informations relatives à l'occupation
542 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
545 Plusieurs Contrats peuvent être associés au Dossier.
546 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
547 lequel aucun Dossier n'existe est un poste vacant.
550 objects
= DossierManager()
553 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
555 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
556 db_column
='organisme_bstg',
558 verbose_name
= u
"Organisme",
559 help_text
="Si détaché (DET) ou \
560 mis à disposition (MAD), \
561 préciser l'organisme.",
562 null
=True, blank
=True)
565 remplacement
= models
.BooleanField(default
=False)
566 remplacement_de
= models
.ForeignKey('self', related_name
='+',
567 null
=True, blank
=True)
568 statut_residence
= models
.CharField(max_length
=10, default
='local',
569 verbose_name
= u
"Statut", null
=True,
570 choices
=STATUT_RESIDENCE_CHOICES
)
573 classement
= models
.ForeignKey('Classement', db_column
='classement',
575 null
=True, blank
=True)
576 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
578 default
=REGIME_TRAVAIL_DEFAULT
,
579 verbose_name
= u
"Régime de travail",
580 help_text
="% du temps complet")
581 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
582 decimal_places
=2, null
=True,
583 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
584 verbose_name
= u
"Nb. heures par semaine")
586 # Occupation du Poste par cet Employe (anciennement "mandat")
587 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
589 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
591 null
=True, blank
=True)
598 ordering
= ['employe__nom', ]
599 verbose_name
= u
"Dossier"
600 verbose_name_plural
= "Dossiers"
602 def salaire_theorique(self
):
603 annee
= date
.today().year
604 coeff
= self
.classement
.coefficient
605 implantation
= self
.poste
.implantation
606 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
608 montant
= coeff
* point
.valeur
609 devise
= point
.devise
610 return {'montant':montant
, 'devise':devise
}
612 def __unicode__(self
):
613 poste
= self
.poste
.nom
614 if self
.employe
.genre
== 'F':
615 poste
= self
.poste
.nom_feminin
616 return u
'%s - %s' % (self
.employe
, poste
)
618 prefix_implantation
= "poste__implantation__region"
619 def get_regions(self
):
620 return [self
.poste
.implantation
.region
]
623 def remunerations(self
):
624 return self
.rh_remunerations
.all().order_by('date_debut')
626 def get_salaire(self
):
628 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
632 class Dossier(Dossier_
):
633 __doc__
= Dossier_
.__doc__
634 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_dossiers')
635 employe
= models
.ForeignKey('Employe', db_column
='employe',
636 related_name
='%(app_label)s_dossiers',
637 verbose_name
=u
"Employé")
640 class DossierPiece_(models
.Model
):
641 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
642 Ex.: Lettre de motivation.
644 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
645 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
646 fichier
= models
.FileField(verbose_name
= u
"Fichier",
647 upload_to
=dossier_piece_dispatch
,
648 storage
=storage_prive
)
654 def __unicode__(self
):
655 return u
'%s' % (self
.nom
)
657 class DossierPiece(DossierPiece_
):
660 class DossierCommentaire_(Commentaire
):
661 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
665 class DossierCommentaire(DossierCommentaire_
):
668 class DossierComparaison_(models
.Model
):
670 Photo d'une comparaison salariale au moment de l'embauche.
672 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), related_name
='%(app_label)s_comparaisons')
673 objects
= DossierComparaisonManager()
675 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
676 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
677 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
678 montant
= models
.IntegerField(null
=True)
679 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
684 def taux_devise(self
):
685 annee
= self
.dossier
.poste
.date_debut
.year
686 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
689 raise Exception(u
"Le taux de la devise %s n'a pas n'existe pas pour %s ou il existe plusieurs taux pour la même année" % (self
.devise
.id, annee
))
693 def montant_euros(self
):
694 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
696 class DossierComparaison(DossierComparaison_
):
701 class RemunerationMixin(AUFMetadata
):
702 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_remunerations')
704 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
706 verbose_name
= u
"Type de rémunération")
707 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
708 db_column
='type_revalorisation',
710 verbose_name
= u
"Type de revalorisation",
711 null
=True, blank
=True)
712 montant
= models
.FloatField(null
=True, blank
=True,
714 # Annuel (12 mois, 52 semaines, 364 jours?)
715 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+', default
=5)
716 # commentaire = precision
717 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
718 # date_debut = anciennement date_effectif
719 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
720 null
=True, blank
=True)
721 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
722 null
=True, blank
=True)
726 ordering
= ['type__nom', '-date_fin']
728 def __unicode__(self
):
729 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
731 class Remuneration_(RemunerationMixin
):
732 """Structure de rémunération (données budgétaires) en situation normale
733 pour un Dossier. Si un Evenement existe, utiliser la structure de
734 rémunération EvenementRemuneration de cet événement.
737 def montant_mois(self
):
738 return round(self
.montant
/ 12, 2)
740 def taux_devise(self
):
741 if self
.devise
.code
== "EUR":
744 annee
= datetime
.datetime
.now().year
745 if self
.date_debut
is not None:
746 annee
= self
.date_debut
.year
747 if self
.dossier
.poste
.date_debut
is not None:
748 annee
= self
.dossier
.poste
.date_debut
.year
750 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise_id
, annee
=annee
)]
753 raise Exception(u
"Le taux de la devise %s n'a pas n'existe pas pour %s ou il existe plusieurs taux pour la même année" % (self
.devise
.id, annee
))
757 def montant_euro(self
):
758 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
760 def montant_euro_mois(self
):
761 return round(self
.montant_euro() / 12, 2)
763 def __unicode__(self
):
765 devise
= self
.devise
.code
768 return "%s %s" % (self
.montant
, devise
)
772 verbose_name
= u
"Rémunération"
773 verbose_name_plural
= u
"Rémunérations"
776 class Remuneration(Remuneration_
):
782 class ContratManager(NoDeleteManager
):
783 def get_query_set(self
):
784 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
787 class Contrat_(AUFMetadata
):
788 """Document juridique qui encadre la relation de travail d'un Employe
789 pour un Poste particulier. Pour un Dossier (qui documente cette
790 relation de travail) plusieurs contrats peuvent être associés.
792 objects
= ContratManager()
793 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_contrats')
794 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
796 verbose_name
= u
"Type de contrat")
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)
803 ordering
= ['dossier__employe__nom_affichage']
804 verbose_name
= u
"Contrat"
805 verbose_name_plural
= u
"Contrats"
807 def __unicode__(self
):
808 return u
'%s - %s' % (self
.dossier
, self
.id)
810 class Contrat(Contrat_
):
816 #class Evenement_(AUFMetadata):
817 # """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
818 # d'un Dossier qui vient altérer des informations normales liées à un Dossier
819 # (ex.: la Remuneration).
821 # Ex.: congé de maternité, maladie...
823 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
824 # différent et une rémunération en conséquence. On souhaite toutefois
825 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
826 # du retour à la normale.
828 # dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
830 # nom = models.CharField(max_length=255)
831 # date_debut = models.DateField(verbose_name = u"Date de début")
832 # date_fin = models.DateField(verbose_name = u"Date de fin",
833 # null=True, blank=True)
838 # verbose_name = u"Évènement"
839 # verbose_name_plural = u"Évènements"
841 # def __unicode__(self):
842 # return u'%s' % (self.nom)
845 #class Evenement(Evenement_):
846 # __doc__ = Evenement_.__doc__
849 #class EvenementRemuneration_(RemunerationMixin):
850 # """Structure de rémunération liée à un Evenement qui remplace
851 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
854 # evenement = models.ForeignKey("Evenement", db_column='evenement',
856 # verbose_name = u"Évènement")
857 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
858 # # de l'Evenement associé
862 # ordering = ['evenement', 'type__nom', '-date_fin']
863 # verbose_name = u"Évènement - rémunération"
864 # verbose_name_plural = u"Évènements - rémunérations"
867 #class EvenementRemuneration(EvenementRemuneration_):
868 # __doc__ = EvenementRemuneration_.__doc__
874 #class EvenementRemuneration(EvenementRemuneration_):
875 # __doc__ = EvenementRemuneration_.__doc__
876 # TODO? class ContratPiece(models.Model):
881 class FamilleEmploi(AUFMetadata
):
882 """Catégorie utilisée dans la gestion des Postes.
883 Catégorie supérieure à TypePoste.
885 nom
= models
.CharField(max_length
=255)
889 verbose_name
= u
"Famille d'emploi"
890 verbose_name_plural
= u
"Familles d'emploi"
892 def __unicode__(self
):
893 return u
'%s' % (self
.nom
)
895 class TypePoste(AUFMetadata
):
896 """Catégorie de Poste.
898 nom
= models
.CharField(max_length
=255)
899 nom_feminin
= models
.CharField(max_length
=255,
900 verbose_name
= u
"Nom féminin")
902 is_responsable
= models
.BooleanField(default
=False,
903 verbose_name
= u
"Poste de responsabilité")
904 famille_emploi
= models
.ForeignKey('FamilleEmploi',
905 db_column
='famille_emploi',
907 verbose_name
= u
"Famille d'emploi")
911 verbose_name
= u
"Type de poste"
912 verbose_name_plural
= u
"Types de poste"
914 def __unicode__(self
):
915 return u
'%s' % (self
.nom
)
918 TYPE_PAIEMENT_CHOICES
= (
919 ('Régulier', 'Régulier'),
920 ('Ponctuel', 'Ponctuel'),
923 NATURE_REMUNERATION_CHOICES
= (
924 ('Accessoire', 'Accessoire'),
925 ('Charges', 'Charges'),
926 ('Indemnité', 'Indemnité'),
927 ('RAS', 'Rémunération autre source'),
928 ('Traitement', 'Traitement'),
931 class TypeRemuneration(AUFMetadata
):
932 """Catégorie de Remuneration.
934 nom
= models
.CharField(max_length
=255)
935 type_paiement
= models
.CharField(max_length
=30,
936 choices
=TYPE_PAIEMENT_CHOICES
,
937 verbose_name
= u
"Type de paiement")
938 nature_remuneration
= models
.CharField(max_length
=30,
939 choices
=NATURE_REMUNERATION_CHOICES
,
940 verbose_name
= u
"Nature de la rémunération")
944 verbose_name
= u
"Type de rémunération"
945 verbose_name_plural
= u
"Types de rémunération"
947 def __unicode__(self
):
948 return u
'%s' % (self
.nom
)
950 class TypeRevalorisation(AUFMetadata
):
951 """Justification du changement de la Remuneration.
952 (Actuellement utilisé dans aucun traitement informatique.)
954 nom
= models
.CharField(max_length
=255)
958 verbose_name
= u
"Type de revalorisation"
959 verbose_name_plural
= u
"Types de revalorisation"
961 def __unicode__(self
):
962 return u
'%s' % (self
.nom
)
964 class Service(AUFMetadata
):
965 """Unité administrative où les Postes sont rattachés.
967 nom
= models
.CharField(max_length
=255)
971 verbose_name
= u
"Service"
972 verbose_name_plural
= u
"Services"
974 def __unicode__(self
):
975 return u
'%s' % (self
.nom
)
978 TYPE_ORGANISME_CHOICES
= (
979 ('MAD', 'Mise à disposition'),
980 ('DET', 'Détachement'),
983 class OrganismeBstg(AUFMetadata
):
984 """Organisation d'où provient un Employe mis à disposition (MAD) de
985 ou détaché (DET) à l'AUF à titre gratuit.
987 (BSTG = bien et service à titre gratuit.)
989 nom
= models
.CharField(max_length
=255)
990 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
991 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
993 related_name
='organismes_bstg',
994 null
=True, blank
=True)
997 ordering
= ['type', 'nom']
998 verbose_name
= u
"Organisme BSTG"
999 verbose_name_plural
= u
"Organismes BSTG"
1001 def __unicode__(self
):
1002 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1004 prefix_implantation
= "pays__region"
1005 def get_regions(self
):
1006 return [self
.pays
.region
]
1009 class Statut(AUFMetadata
):
1010 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1013 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.")
1014 nom
= models
.CharField(max_length
=255)
1018 verbose_name
= u
"Statut d'employé"
1019 verbose_name_plural
= u
"Statuts d'employé"
1021 def __unicode__(self
):
1022 return u
'%s : %s' % (self
.code
, self
.nom
)
1025 TYPE_CLASSEMENT_CHOICES
= (
1026 ('S', 'S -Soutien'),
1027 ('T', 'T - Technicien'),
1028 ('P', 'P - Professionel'),
1030 ('D', 'D - Direction'),
1031 ('SO', 'SO - Sans objet [expatriés]'),
1032 ('HG', 'HG - Hors grille [direction]'),
1036 class Classement_(AUFMetadata
):
1037 """Éléments de classement de la
1038 "Grille générique de classement hiérarchique".
1040 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1041 classement dans la grille. Le classement donne le coefficient utilisé dans:
1043 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1046 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1047 echelon
= models
.IntegerField(verbose_name
=u
"Échelon", blank
=True, default
=0)
1048 degre
= models
.IntegerField(verbose_name
=u
"Degré", blank
=True, default
=0)
1049 coefficient
= models
.FloatField(default
=0, verbose_name
=u
"Coefficient",
1052 # annee # au lieu de date_debut et date_fin
1053 commentaire
= models
.TextField(null
=True, blank
=True)
1057 ordering
= ['type','echelon','degre','coefficient']
1058 verbose_name
= u
"Classement"
1059 verbose_name_plural
= u
"Classements"
1061 def __unicode__(self
):
1062 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1065 class Classement(Classement_
):
1066 __doc__
= Classement_
.__doc__
1069 class TauxChange_(AUFMetadata
):
1070 """Taux de change de la devise vers l'euro (EUR)
1071 pour chaque année budgétaire.
1074 devise
= models
.ForeignKey('Devise', db_column
='devise')
1075 annee
= models
.IntegerField(verbose_name
= u
"Année")
1076 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1080 ordering
= ['-annee', 'devise__code']
1081 verbose_name
= u
"Taux de change"
1082 verbose_name_plural
= u
"Taux de change"
1084 def __unicode__(self
):
1085 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1088 class TauxChange(TauxChange_
):
1089 __doc__
= TauxChange_
.__doc__
1091 class ValeurPointManager(NoDeleteManager
):
1092 def get_query_set(self
):
1093 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1096 class ValeurPoint_(AUFMetadata
):
1097 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1098 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1099 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1101 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1104 actuelles
= ValeurPointManager()
1106 valeur
= models
.FloatField(null
=True)
1107 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1108 related_name
='+', default
=5)
1109 implantation
= models
.ForeignKey(ref
.Implantation
,
1110 db_column
='implantation',
1111 related_name
='%(app_label)s_valeur_point')
1113 annee
= models
.IntegerField()
1116 ordering
= ['-annee', 'implantation__nom']
1118 verbose_name
= u
"Valeur du point"
1119 verbose_name_plural
= u
"Valeurs du point"
1121 def __unicode__(self
):
1122 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1125 class ValeurPoint(ValeurPoint_
):
1126 __doc__
= ValeurPoint_
.__doc__
1129 class Devise(AUFMetadata
):
1130 """Devise monétaire.
1132 code
= models
.CharField(max_length
=10, unique
=True)
1133 nom
= models
.CharField(max_length
=255)
1137 verbose_name
= u
"Devise"
1138 verbose_name_plural
= u
"Devises"
1140 def __unicode__(self
):
1141 return u
'%s - %s' % (self
.code
, self
.nom
)
1143 class TypeContrat(AUFMetadata
):
1146 nom
= models
.CharField(max_length
=255)
1147 nom_long
= models
.CharField(max_length
=255)
1151 verbose_name
= u
"Type de contrat"
1152 verbose_name_plural
= u
"Types de contrat"
1154 def __unicode__(self
):
1155 return u
'%s' % (self
.nom
)
1160 class ResponsableImplantation(AUFMetadata
):
1161 """Le responsable d'une implantation.
1162 Anciennement géré sur le Dossier du responsable.
1164 employe
= models
.ForeignKey('Employe', db_column
='employe',
1166 null
=True, blank
=True)
1167 implantation
= models
.ForeignKey(ref
.Implantation
,
1168 db_column
='implantation', related_name
='+',
1171 def __unicode__(self
):
1172 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1175 ordering
= ['implantation__nom']
1176 verbose_name
= "Responsable d'implantation"
1177 verbose_name_plural
= "Responsables d'implantation"