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
, help_text
=u
"Taper le nom de l'implantation ou sa région",
84 db_column
='implantation', related_name
='+')
85 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste', help_text
=u
"Taper le nom du type de 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,help_text
=u
"Taper le nom du poste ou du type de poste",
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', help_text
=u
"Taper le code ou le nom de l'implantation",
126 db_column
='valeur_point_min', related_name
='+',
127 null
=True, blank
=True)
128 valeur_point_max
= models
.ForeignKey('ValeurPoint', help_text
=u
"Taper le code ou le nom de l'implantation",
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_debut
= models
.DateField(verbose_name
=u
"Date de début",
179 null
=True, blank
=True)
180 date_fin
= models
.DateField(verbose_name
=u
"Date de fin",
181 null
=True, blank
=True)
185 ordering
= ['implantation__nom', 'nom']
186 verbose_name
= u
"Poste"
187 verbose_name_plural
= u
"Postes"
189 def __unicode__(self
):
190 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
192 #if self.is_vacant():
193 # representation = representation + u' (VACANT)'
194 return representation
198 if self
.occupe_par():
202 def occupe_par(self
):
203 """Retourne la liste d'employé occupant ce poste.
204 Généralement, retourne une liste d'un élément.
205 Si poste inoccupé, retourne liste vide.
207 return [d
.employe
for d
in self
.rh_dossiers
.filter(actif
=True, supprime
=False) \
208 .exclude(date_fin__lt
=date
.today())]
210 prefix_implantation
= "implantation__region"
211 def get_regions(self
):
212 return [self
.implantation
.region
]
216 __doc__
= Poste_
.__doc__
219 POSTE_FINANCEMENT_CHOICES
= (
220 ('A', 'A - Frais de personnel'),
221 ('B', 'B - Projet(s)-Titre(s)'),
226 class PosteFinancement_(models
.Model
):
227 """Pour un Poste, structure d'informations décrivant comment on prévoit
230 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_financements')
231 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
232 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
233 help_text
="ex.: 33.33 % (décimale avec point)")
234 commentaire
= models
.TextField(
235 help_text
="Spécifiez la source de financement.")
241 def __unicode__(self
):
242 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
245 class PosteFinancement(PosteFinancement_
):
249 class PostePiece_(models
.Model
):
250 """Documents relatifs au Poste.
251 Ex.: Description de poste
253 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_pieces')
254 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
255 fichier
= models
.FileField(verbose_name
= u
"Fichier",
256 upload_to
=poste_piece_dispatch
,
257 storage
=storage_prive
)
263 def __unicode__(self
):
264 return u
'%s' % (self
.nom
)
266 class PostePiece(PostePiece_
):
269 class PosteComparaison_(models
.Model
):
271 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
273 poste
= models
.ForeignKey('%s.Poste' % app_context(), related_name
='%(app_label)s_comparaisons_internes')
274 objects
= PosteComparaisonManager()
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)
284 def taux_devise(self
):
285 if self
.devise
.code
== "EUR":
287 annee
= self
.poste
.date_debut
.year
288 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
291 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
))
295 def montant_euros(self
):
296 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
298 class PosteComparaison(PosteComparaison_
):
301 class PosteCommentaire_(Commentaire
):
302 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='+')
307 class PosteCommentaire(PosteCommentaire_
):
312 class Employe(AUFMetadata
):
313 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
314 Dossiers qu'il occupe ou a occupé de Postes.
316 Cette classe aurait pu avantageusement s'appeler Personne car la notion
317 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
320 nom
= models
.CharField(max_length
=255)
321 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
322 nom_affichage
= models
.CharField(max_length
=255,
323 verbose_name
= u
"Nom d'affichage",
324 null
=True, blank
=True)
325 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
326 db_column
='nationalite',
327 related_name
='employes_nationalite',
328 verbose_name
= u
"Nationalité",
329 blank
=True, null
=True)
330 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
331 help_text
=HELP_TEXT_DATE
,
332 validators
=[validate_date_passee
],
333 null
=True, blank
=True)
334 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
337 situation_famille
= models
.CharField(max_length
=1,
338 choices
=SITUATION_CHOICES
,
339 verbose_name
= u
"Situation familiale",
340 null
=True, blank
=True)
341 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
342 help_text
=HELP_TEXT_DATE
,
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','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 help_text
=HELP_TEXT_DATE
,
488 validators
=[validate_date_passee
],
489 null
=True, blank
=True)
490 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
493 employe
= models
.ForeignKey('Employe', db_column
='employe',
494 related_name
='ayantdroits',
495 verbose_name
= u
"Employé")
496 lien_parente
= models
.CharField(max_length
=10,
497 choices
=LIEN_PARENTE_CHOICES
,
498 verbose_name
= u
"Lien de parenté",
499 null
=True, blank
=True)
502 ordering
= ['nom_affichage']
503 verbose_name
= u
"Ayant droit"
504 verbose_name_plural
= u
"Ayants droit"
506 def __unicode__(self
):
507 return u
'%s' % (self
.get_nom())
510 nom_affichage
= self
.nom_affichage
511 if not nom_affichage
:
512 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
515 prefix_implantation
= "employe__dossiers__poste__implantation__region"
516 def get_regions(self
):
518 for d
in self
.employe
.dossiers
.all():
519 regions
.append(d
.poste
.implantation
.region
)
523 class AyantDroitCommentaire(Commentaire
):
524 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
530 STATUT_RESIDENCE_CHOICES
= (
532 ('expat', 'Expatrié'),
535 COMPTE_COMPTA_CHOICES
= (
541 class Dossier_(AUFMetadata
):
542 """Le Dossier regroupe les informations relatives à l'occupation
543 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
546 Plusieurs Contrats peuvent être associés au Dossier.
547 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
548 lequel aucun Dossier n'existe est un poste vacant.
551 objects
= DossierManager()
554 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
556 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
557 db_column
='organisme_bstg',
559 verbose_name
= u
"Organisme",
560 help_text
="Si détaché (DET) ou \
561 mis à disposition (MAD), \
562 préciser l'organisme.",
563 null
=True, blank
=True)
566 remplacement
= models
.BooleanField(default
=False)
567 remplacement_de
= models
.ForeignKey('self', related_name
='+',
568 null
=True, blank
=True)
569 statut_residence
= models
.CharField(max_length
=10, default
='local',
570 verbose_name
= u
"Statut", null
=True,
571 choices
=STATUT_RESIDENCE_CHOICES
)
574 classement
= models
.ForeignKey('Classement', db_column
='classement',
576 null
=True, blank
=True)
577 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
579 default
=REGIME_TRAVAIL_DEFAULT
,
580 verbose_name
= u
"Régime de travail",
581 help_text
="% du temps complet")
582 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
583 decimal_places
=2, null
=True,
584 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
585 verbose_name
= u
"Nb. heures par semaine")
587 # Occupation du Poste par cet Employe (anciennement "mandat")
588 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
590 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
592 null
=True, blank
=True)
599 ordering
= ['employe__nom', ]
600 verbose_name
= u
"Dossier"
601 verbose_name_plural
= "Dossiers"
603 def salaire_theorique(self
):
604 annee
= date
.today().year
605 coeff
= self
.classement
.coefficient
606 implantation
= self
.poste
.implantation
607 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
609 montant
= coeff
* point
.valeur
610 devise
= point
.devise
611 return {'montant':montant
, 'devise':devise
}
613 def __unicode__(self
):
614 poste
= self
.poste
.nom
615 if self
.employe
.genre
== 'F':
616 poste
= self
.poste
.nom_feminin
617 return u
'%s - %s' % (self
.employe
, poste
)
619 prefix_implantation
= "poste__implantation__region"
620 def get_regions(self
):
621 return [self
.poste
.implantation
.region
]
624 def remunerations(self
):
625 return self
.rh_remunerations
.all().order_by('date_debut')
627 def get_salaire(self
):
629 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
633 class Dossier(Dossier_
):
634 __doc__
= Dossier_
.__doc__
635 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_dossiers')
636 employe
= models
.ForeignKey('Employe', db_column
='employe',
637 related_name
='%(app_label)s_dossiers',
638 verbose_name
=u
"Employé")
641 class DossierPiece_(models
.Model
):
642 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
643 Ex.: Lettre de motivation.
645 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
646 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
647 fichier
= models
.FileField(verbose_name
= u
"Fichier",
648 upload_to
=dossier_piece_dispatch
,
649 storage
=storage_prive
)
655 def __unicode__(self
):
656 return u
'%s' % (self
.nom
)
658 class DossierPiece(DossierPiece_
):
661 class DossierCommentaire_(Commentaire
):
662 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
666 class DossierCommentaire(DossierCommentaire_
):
669 class DossierComparaison_(models
.Model
):
671 Photo d'une comparaison salariale au moment de l'embauche.
673 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), related_name
='%(app_label)s_comparaisons')
674 objects
= DossierComparaisonManager()
676 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
677 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
678 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
679 montant
= models
.IntegerField(null
=True)
680 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
685 def taux_devise(self
):
686 annee
= self
.dossier
.poste
.date_debut
.year
687 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
690 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
))
694 def montant_euros(self
):
695 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
697 class DossierComparaison(DossierComparaison_
):
702 class RemunerationMixin(AUFMetadata
):
703 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_remunerations')
705 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
707 verbose_name
= u
"Type de rémunération")
708 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
709 db_column
='type_revalorisation',
711 verbose_name
= u
"Type de revalorisation",
712 null
=True, blank
=True)
713 montant
= models
.FloatField(null
=True, blank
=True,
715 # Annuel (12 mois, 52 semaines, 364 jours?)
716 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+', default
=5)
717 # commentaire = precision
718 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
719 # date_debut = anciennement date_effectif
720 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
721 null
=True, blank
=True)
722 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
723 null
=True, blank
=True)
727 ordering
= ['type__nom', '-date_fin']
729 def __unicode__(self
):
730 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
732 class Remuneration_(RemunerationMixin
):
733 """Structure de rémunération (données budgétaires) en situation normale
734 pour un Dossier. Si un Evenement existe, utiliser la structure de
735 rémunération EvenementRemuneration de cet événement.
738 def montant_mois(self
):
739 return round(self
.montant
/ 12, 2)
741 def taux_devise(self
):
742 if self
.devise
.code
== "EUR":
745 annee
= datetime
.datetime
.now().year
746 if self
.date_debut
is not None:
747 annee
= self
.date_debut
.year
748 if self
.dossier
.poste
.date_debut
is not None:
749 annee
= self
.dossier
.poste
.date_debut
.year
751 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise_id
, annee
=annee
)]
754 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
))
758 def montant_euro(self
):
759 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
761 def montant_euro_mois(self
):
762 return round(self
.montant_euro() / 12, 2)
764 def __unicode__(self
):
766 devise
= self
.devise
.code
769 return "%s %s" % (self
.montant
, devise
)
773 verbose_name
= u
"Rémunération"
774 verbose_name_plural
= u
"Rémunérations"
777 class Remuneration(Remuneration_
):
783 class ContratManager(NoDeleteManager
):
784 def get_query_set(self
):
785 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
788 class Contrat_(AUFMetadata
):
789 """Document juridique qui encadre la relation de travail d'un Employe
790 pour un Poste particulier. Pour un Dossier (qui documente cette
791 relation de travail) plusieurs contrats peuvent être associés.
793 objects
= ContratManager()
794 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_contrats')
795 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
797 verbose_name
= u
"Type de contrat")
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)
804 ordering
= ['dossier__employe__nom_affichage']
805 verbose_name
= u
"Contrat"
806 verbose_name_plural
= u
"Contrats"
808 def __unicode__(self
):
809 return u
'%s - %s' % (self
.dossier
, self
.id)
811 class Contrat(Contrat_
):
817 #class Evenement_(AUFMetadata):
818 # """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
819 # d'un Dossier qui vient altérer des informations normales liées à un Dossier
820 # (ex.: la Remuneration).
822 # Ex.: congé de maternité, maladie...
824 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
825 # différent et une rémunération en conséquence. On souhaite toutefois
826 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
827 # du retour à la normale.
829 # dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
831 # nom = models.CharField(max_length=255)
832 # date_debut = models.DateField(verbose_name = u"Date de début")
833 # date_fin = models.DateField(verbose_name = u"Date de fin",
834 # null=True, blank=True)
839 # verbose_name = u"Évènement"
840 # verbose_name_plural = u"Évènements"
842 # def __unicode__(self):
843 # return u'%s' % (self.nom)
846 #class Evenement(Evenement_):
847 # __doc__ = Evenement_.__doc__
850 #class EvenementRemuneration_(RemunerationMixin):
851 # """Structure de rémunération liée à un Evenement qui remplace
852 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
855 # evenement = models.ForeignKey("Evenement", db_column='evenement',
857 # verbose_name = u"Évènement")
858 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
859 # # de l'Evenement associé
863 # ordering = ['evenement', 'type__nom', '-date_fin']
864 # verbose_name = u"Évènement - rémunération"
865 # verbose_name_plural = u"Évènements - rémunérations"
868 #class EvenementRemuneration(EvenementRemuneration_):
869 # __doc__ = EvenementRemuneration_.__doc__
875 #class EvenementRemuneration(EvenementRemuneration_):
876 # __doc__ = EvenementRemuneration_.__doc__
877 # TODO? class ContratPiece(models.Model):
882 class FamilleEmploi(AUFMetadata
):
883 """Catégorie utilisée dans la gestion des Postes.
884 Catégorie supérieure à TypePoste.
886 nom
= models
.CharField(max_length
=255)
890 verbose_name
= u
"Famille d'emploi"
891 verbose_name_plural
= u
"Familles d'emploi"
893 def __unicode__(self
):
894 return u
'%s' % (self
.nom
)
896 class TypePoste(AUFMetadata
):
897 """Catégorie de Poste.
899 nom
= models
.CharField(max_length
=255)
900 nom_feminin
= models
.CharField(max_length
=255,
901 verbose_name
= u
"Nom féminin")
903 is_responsable
= models
.BooleanField(default
=False,
904 verbose_name
= u
"Poste de responsabilité")
905 famille_emploi
= models
.ForeignKey('FamilleEmploi',
906 db_column
='famille_emploi',
908 verbose_name
= u
"Famille d'emploi")
912 verbose_name
= u
"Type de poste"
913 verbose_name_plural
= u
"Types de poste"
915 def __unicode__(self
):
916 return u
'%s' % (self
.nom
)
919 TYPE_PAIEMENT_CHOICES
= (
920 ('Régulier', 'Régulier'),
921 ('Ponctuel', 'Ponctuel'),
924 NATURE_REMUNERATION_CHOICES
= (
925 ('Accessoire', 'Accessoire'),
926 ('Charges', 'Charges'),
927 ('Indemnité', 'Indemnité'),
928 ('RAS', 'Rémunération autre source'),
929 ('Traitement', 'Traitement'),
932 class TypeRemuneration(AUFMetadata
):
933 """Catégorie de Remuneration.
935 nom
= models
.CharField(max_length
=255)
936 type_paiement
= models
.CharField(max_length
=30,
937 choices
=TYPE_PAIEMENT_CHOICES
,
938 verbose_name
= u
"Type de paiement")
939 nature_remuneration
= models
.CharField(max_length
=30,
940 choices
=NATURE_REMUNERATION_CHOICES
,
941 verbose_name
= u
"Nature de la rémunération")
945 verbose_name
= u
"Type de rémunération"
946 verbose_name_plural
= u
"Types de rémunération"
948 def __unicode__(self
):
949 return u
'%s' % (self
.nom
)
951 class TypeRevalorisation(AUFMetadata
):
952 """Justification du changement de la Remuneration.
953 (Actuellement utilisé dans aucun traitement informatique.)
955 nom
= models
.CharField(max_length
=255)
959 verbose_name
= u
"Type de revalorisation"
960 verbose_name_plural
= u
"Types de revalorisation"
962 def __unicode__(self
):
963 return u
'%s' % (self
.nom
)
965 class Service(AUFMetadata
):
966 """Unité administrative où les Postes sont rattachés.
968 nom
= models
.CharField(max_length
=255)
972 verbose_name
= u
"Service"
973 verbose_name_plural
= u
"Services"
975 def __unicode__(self
):
976 return u
'%s' % (self
.nom
)
979 TYPE_ORGANISME_CHOICES
= (
980 ('MAD', 'Mise à disposition'),
981 ('DET', 'Détachement'),
984 class OrganismeBstg(AUFMetadata
):
985 """Organisation d'où provient un Employe mis à disposition (MAD) de
986 ou détaché (DET) à l'AUF à titre gratuit.
988 (BSTG = bien et service à titre gratuit.)
990 nom
= models
.CharField(max_length
=255)
991 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
992 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
994 related_name
='organismes_bstg',
995 null
=True, blank
=True)
998 ordering
= ['type', 'nom']
999 verbose_name
= u
"Organisme BSTG"
1000 verbose_name_plural
= u
"Organismes BSTG"
1002 def __unicode__(self
):
1003 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1005 prefix_implantation
= "pays__region"
1006 def get_regions(self
):
1007 return [self
.pays
.region
]
1010 class Statut(AUFMetadata
):
1011 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1014 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.")
1015 nom
= models
.CharField(max_length
=255)
1019 verbose_name
= u
"Statut d'employé"
1020 verbose_name_plural
= u
"Statuts d'employé"
1022 def __unicode__(self
):
1023 return u
'%s : %s' % (self
.code
, self
.nom
)
1026 TYPE_CLASSEMENT_CHOICES
= (
1027 ('S', 'S -Soutien'),
1028 ('T', 'T - Technicien'),
1029 ('P', 'P - Professionel'),
1031 ('D', 'D - Direction'),
1032 ('SO', 'SO - Sans objet [expatriés]'),
1033 ('HG', 'HG - Hors grille [direction]'),
1037 class Classement_(AUFMetadata
):
1038 """Éléments de classement de la
1039 "Grille générique de classement hiérarchique".
1041 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1042 classement dans la grille. Le classement donne le coefficient utilisé dans:
1044 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1047 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1048 echelon
= models
.IntegerField(verbose_name
=u
"Échelon", blank
=True, default
=0)
1049 degre
= models
.IntegerField(verbose_name
=u
"Degré", blank
=True, default
=0)
1050 coefficient
= models
.FloatField(default
=0, verbose_name
=u
"Coefficient",
1053 # annee # au lieu de date_debut et date_fin
1054 commentaire
= models
.TextField(null
=True, blank
=True)
1058 ordering
= ['type','echelon','degre','coefficient']
1059 verbose_name
= u
"Classement"
1060 verbose_name_plural
= u
"Classements"
1062 def __unicode__(self
):
1063 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1066 class Classement(Classement_
):
1067 __doc__
= Classement_
.__doc__
1070 class TauxChange_(AUFMetadata
):
1071 """Taux de change de la devise vers l'euro (EUR)
1072 pour chaque année budgétaire.
1075 devise
= models
.ForeignKey('Devise', db_column
='devise')
1076 annee
= models
.IntegerField(verbose_name
= u
"Année")
1077 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1081 ordering
= ['-annee', 'devise__code']
1082 verbose_name
= u
"Taux de change"
1083 verbose_name_plural
= u
"Taux de change"
1085 def __unicode__(self
):
1086 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1089 class TauxChange(TauxChange_
):
1090 __doc__
= TauxChange_
.__doc__
1092 class ValeurPointManager(NoDeleteManager
):
1094 def get_query_set(self
):
1095 now
= datetime
.datetime
.now()
1096 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1099 class ValeurPoint_(AUFMetadata
):
1100 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1101 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1102 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1104 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1107 actuelles
= ValeurPointManager()
1109 valeur
= models
.FloatField(null
=True)
1110 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1111 related_name
='+', default
=5)
1112 implantation
= models
.ForeignKey(ref
.Implantation
,
1113 db_column
='implantation',
1114 related_name
='%(app_label)s_valeur_point')
1116 annee
= models
.IntegerField()
1119 ordering
= ['-annee', 'implantation__nom']
1121 verbose_name
= u
"Valeur du point"
1122 verbose_name_plural
= u
"Valeurs du point"
1124 def __unicode__(self
):
1125 return u
'%s %s [%s]' % (self
.devise
, self
.annee
, self
.implantation
.nom_court
)
1128 class ValeurPoint(ValeurPoint_
):
1129 __doc__
= ValeurPoint_
.__doc__
1133 class DeviseManager(NoDeleteManager
):
1135 def get_query_set(self
):
1137 return super(DeviseManager
, self
).get_query_set().exclude(id__in
=(3, 15))
1139 class Devise(AUFMetadata
):
1140 """Devise monétaire.
1142 objects
= DeviseManager()
1144 code
= models
.CharField(max_length
=10, unique
=True)
1145 nom
= models
.CharField(max_length
=255)
1149 verbose_name
= u
"Devise"
1150 verbose_name_plural
= u
"Devises"
1152 def __unicode__(self
):
1153 return u
'%s - %s' % (self
.code
, self
.nom
)
1155 class TypeContrat(AUFMetadata
):
1158 nom
= models
.CharField(max_length
=255)
1159 nom_long
= models
.CharField(max_length
=255)
1163 verbose_name
= u
"Type de contrat"
1164 verbose_name_plural
= u
"Types de contrat"
1166 def __unicode__(self
):
1167 return u
'%s' % (self
.nom
)
1172 class ResponsableImplantation(AUFMetadata
):
1173 """Le responsable d'une implantation.
1174 Anciennement géré sur le Dossier du responsable.
1176 employe
= models
.ForeignKey('Employe', db_column
='employe',
1178 null
=True, blank
=True)
1179 implantation
= models
.ForeignKey(ref
.Implantation
,
1180 db_column
='implantation', related_name
='+',
1183 def __unicode__(self
):
1184 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1187 ordering
= ['implantation__nom']
1188 verbose_name
= "Responsable d'implantation"
1189 verbose_name_plural
= "Responsables d'implantation"