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
.metadata
.models
import AUFMetadata
12 from auf
.django
.metadata
.managers
import NoDeleteManager
13 import auf
.django
.references
.models
as ref
14 from validators
import validate_date_passee
15 from managers
import PosteManager
, DossierManager
, DossierComparaisonManager
, PosteComparaisonManager
19 # Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
20 # Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
23 models_stack
= [s
[1].split('/')[-2] for s
in inspect
.stack() if s
[1].endswith('models.py')]
24 return models_stack
[-1]
28 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
29 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
32 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
33 base_url
=settings
.PRIVE_MEDIA_URL
)
35 def poste_piece_dispatch(instance
, filename
):
36 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
39 def dossier_piece_dispatch(instance
, filename
):
40 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
43 def employe_piece_dispatch(instance
, filename
):
44 path
= "employe/%s/%s" % (instance
.employe_id
, filename
)
48 class Commentaire(AUFMetadata
):
49 texte
= models
.TextField()
50 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+')
54 ordering
= ['-date_creation']
56 def __unicode__(self
):
57 return u
'%s' % (self
.texte
)
62 POSTE_APPEL_CHOICES
= (
63 ('interne', 'Interne'),
64 ('externe', 'Externe'),
67 class Poste_(AUFMetadata
):
68 """Un Poste est un emploi (job) à combler dans une implantation.
69 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
70 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
73 objects
= PosteManager()
76 nom
= models
.CharField(max_length
=255,
77 verbose_name
= u
"Titre du poste", )
78 nom_feminin
= models
.CharField(max_length
=255,
79 verbose_name
= u
"Titre du poste (au féminin)",
81 implantation
= models
.ForeignKey(ref
.Implantation
,
82 db_column
='implantation', related_name
='+')
83 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste',
86 service
= models
.ForeignKey('Service', db_column
='service',
88 verbose_name
= u
"Direction/Service/Pôle support", )
89 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
90 related_name
='+', null
=True,
91 verbose_name
= u
"Poste du responsable", )
94 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
95 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
96 verbose_name
= u
"Temps de travail",
97 help_text
="% du temps complet")
98 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
99 decimal_places
=2, null
=True,
100 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
101 verbose_name
= u
"Nb. heures par semaine")
104 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
105 null
=True, blank
=True)
106 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
107 null
=True, blank
=True)
108 mise_a_disposition
= models
.NullBooleanField(
109 verbose_name
= u
"Mise à disposition",
110 null
=True, default
=False)
111 appel
= models
.CharField(max_length
=10, null
=True,
112 verbose_name
= u
"Appel à candidature",
113 choices
=POSTE_APPEL_CHOICES
,
117 classement_min
= models
.ForeignKey('Classement',
118 db_column
='classement_min', related_name
='+',
119 null
=True, blank
=True)
120 classement_max
= models
.ForeignKey('Classement',
121 db_column
='classement_max', related_name
='+',
122 null
=True, blank
=True)
123 valeur_point_min
= models
.ForeignKey('ValeurPoint',
124 db_column
='valeur_point_min', related_name
='+',
125 null
=True, blank
=True)
126 valeur_point_max
= models
.ForeignKey('ValeurPoint',
127 db_column
='valeur_point_max', related_name
='+',
128 null
=True, blank
=True)
129 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
130 related_name
='+', default
=5)
131 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
132 related_name
='+', default
=5)
133 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
134 null
=True, default
=0)
135 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
136 null
=True, default
=0)
137 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
138 null
=True, default
=0)
139 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
140 null
=True, default
=0)
141 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
142 null
=True, default
=0)
143 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
144 null
=True, default
=0)
146 # Comparatifs de rémunération
147 devise_comparaison
= models
.ForeignKey('Devise', null
=True,
148 db_column
='devise_comparaison',
151 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
152 null
=True, blank
=True)
153 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
154 null
=True, blank
=True)
155 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
156 null
=True, blank
=True)
157 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
158 null
=True, blank
=True)
159 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
160 null
=True, blank
=True)
161 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
162 null
=True, blank
=True)
163 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
164 null
=True, blank
=True)
165 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
166 null
=True, blank
=True)
167 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
168 null
=True, blank
=True)
169 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
170 null
=True, blank
=True)
173 justification
= models
.TextField(null
=True, blank
=True)
176 date_validation
= models
.DateTimeField(null
=True, blank
=True) # de dae
177 date_debut
= models
.DateField(verbose_name
=u
"Date de début",
178 null
=True, blank
=True)
179 date_fin
= models
.DateField(verbose_name
=u
"Date de fin",
180 null
=True, blank
=True)
184 ordering
= ['implantation__nom', 'nom']
185 verbose_name
= u
"Poste"
186 verbose_name_plural
= u
"Postes"
188 def __unicode__(self
):
189 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
192 representation
= representation
+ u
' (VACANT)'
193 return representation
197 if self
.occupe_par():
201 def occupe_par(self
):
202 """Retourne la liste d'employé occupant ce poste.
203 Généralement, retourne une liste d'un élément.
204 Si poste inoccupé, retourne liste vide.
206 return [d
.employe
for d
in self
.rh_dossiers
.filter(actif
=True, supprime
=False) \
207 .exclude(date_fin__lt
=date
.today())]
209 prefix_implantation
= "implantation__region"
210 def get_regions(self
):
211 return [self
.implantation
.region
]
215 __doc__
= Poste_
.__doc__
218 POSTE_FINANCEMENT_CHOICES
= (
219 ('A', 'A - Frais de personnel'),
220 ('B', 'B - Projet(s)-Titre(s)'),
225 class PosteFinancement_(models
.Model
):
226 """Pour un Poste, structure d'informations décrivant comment on prévoit
229 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_financements')
230 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
231 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
232 help_text
="ex.: 33.33 % (décimale avec point)")
233 commentaire
= models
.TextField(
234 help_text
="Spécifiez la source de financement.")
240 def __unicode__(self
):
241 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
244 class PosteFinancement(PosteFinancement_
):
248 class PostePiece_(models
.Model
):
249 """Documents relatifs au Poste.
250 Ex.: Description de poste
252 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_pieces')
253 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
254 fichier
= models
.FileField(verbose_name
= u
"Fichier",
255 upload_to
=poste_piece_dispatch
,
256 storage
=storage_prive
)
262 def __unicode__(self
):
263 return u
'%s' % (self
.nom
)
265 class PostePiece(PostePiece_
):
268 class PosteComparaison_(models
.Model
):
270 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
272 poste
= models
.ForeignKey('%s.Poste' % app_context(), related_name
='%(app_label)s_comparaisons_internes')
273 objects
= PosteComparaisonManager()
275 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
276 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
277 montant
= models
.IntegerField(null
=True)
278 devise
= models
.ForeignKey("Devise", default
=5, related_name
='+', null
=True, blank
=True)
283 def taux_devise(self
):
284 if self
.devise
.code
== "EUR":
286 annee
= self
.poste
.date_debut
.year
287 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
290 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
))
294 def montant_euros(self
):
295 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
297 class PosteComparaison(PosteComparaison_
):
300 class PosteCommentaire_(Commentaire
):
301 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='+')
306 class PosteCommentaire(PosteCommentaire_
):
315 SITUATION_CHOICES
= (
316 ('C', 'Célibataire'),
321 class Employe(AUFMetadata
):
322 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
323 Dossiers qu'il occupe ou a occupé de Postes.
325 Cette classe aurait pu avantageusement s'appeler Personne car la notion
326 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
329 nom
= models
.CharField(max_length
=255)
330 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
331 nom_affichage
= models
.CharField(max_length
=255,
332 verbose_name
= u
"Nom d'affichage",
333 null
=True, blank
=True)
334 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
335 db_column
='nationalite',
336 related_name
='employes_nationalite',
337 verbose_name
= u
"Nationalité",
338 blank
=True, null
=True)
339 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
340 validators
=[validate_date_passee
],
341 null
=True, blank
=True)
342 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
345 situation_famille
= models
.CharField(max_length
=1,
346 choices
=SITUATION_CHOICES
,
347 verbose_name
= u
"Situation familiale",
348 null
=True, blank
=True)
349 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
350 null
=True, blank
=True)
353 tel_domicile
= models
.CharField(max_length
=255,
354 verbose_name
= u
"Tél. domicile",
355 null
=True, blank
=True)
356 tel_cellulaire
= models
.CharField(max_length
=255,
357 verbose_name
= u
"Tél. cellulaire",
358 null
=True, blank
=True)
359 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
360 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
361 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
362 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
363 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
364 related_name
='employes',
365 null
=True, blank
=True)
368 ordering
= ['nom_affichage','nom','prenom']
369 verbose_name
= u
"Employé"
370 verbose_name_plural
= u
"Employés"
372 def __unicode__(self
):
373 return u
'%s [%s]' % (self
.get_nom(), self
.id)
376 nom_affichage
= self
.nom_affichage
377 if not nom_affichage
:
378 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
383 if self
.genre
.upper() == u
'M':
385 elif self
.genre
.upper() == u
'F':
390 """Retourne l'URL du service retournant la photo de l'Employe.
391 Équivalent reverse url 'rh_photo' avec id en param.
393 from django
.core
.urlresolvers
import reverse
394 return reverse('rh_photo', kwargs
={'id':self
.id})
396 def dossiers_passes(self
):
398 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
399 for d
in dossiers_passes
:
401 return dossiers_passes
403 def dossiers_futurs(self
):
405 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
407 def dossiers_encours(self
):
408 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
409 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
410 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
412 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
413 for d
in dossiers_encours
:
414 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
415 return dossiers_encours
417 def postes_encours(self
):
418 postes_encours
= set()
419 for d
in self
.dossiers_encours():
420 postes_encours
.add(d
.poste
)
421 return postes_encours
423 def poste_principal(self
):
425 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
427 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
429 poste
= Poste
.objects
.none()
431 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
436 prefix_implantation
= "dossiers__poste__implantation__region"
437 def get_regions(self
):
439 for d
in self
.dossiers
.all():
440 regions
.append(d
.poste
.implantation
.region
)
444 class EmployePiece(models
.Model
):
445 """Documents relatifs à un employé.
448 employe
= models
.ForeignKey('Employe', db_column
='employe')
449 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
450 fichier
= models
.FileField(verbose_name
="Fichier",
451 upload_to
=employe_piece_dispatch
,
452 storage
=storage_prive
)
456 verbose_name
= u
"Employé pièce"
457 verbose_name_plural
= u
"Employé pièces"
459 def __unicode__(self
):
460 return u
'%s' % (self
.nom
)
462 class EmployeCommentaire(Commentaire
):
463 employe
= models
.ForeignKey('Employe', db_column
='employe',
467 verbose_name
= u
"Employé commentaire"
468 verbose_name_plural
= u
"Employé commentaires"
471 LIEN_PARENTE_CHOICES
= (
472 ('Conjoint', 'Conjoint'),
473 ('Conjointe', 'Conjointe'),
478 class AyantDroit(AUFMetadata
):
479 """Personne en relation avec un Employe.
482 nom
= models
.CharField(max_length
=255)
483 prenom
= models
.CharField(max_length
=255,
484 verbose_name
= u
"Prénom",)
485 nom_affichage
= models
.CharField(max_length
=255,
486 verbose_name
= u
"Nom d'affichage",
487 null
=True, blank
=True)
488 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
489 db_column
='nationalite',
490 related_name
='ayantdroits_nationalite',
491 verbose_name
= u
"Nationalité",
492 null
=True, blank
=True)
493 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
494 validators
=[validate_date_passee
],
495 null
=True, blank
=True)
496 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
499 employe
= models
.ForeignKey('Employe', db_column
='employe',
500 related_name
='ayantdroits',
501 verbose_name
= u
"Employé")
502 lien_parente
= models
.CharField(max_length
=10,
503 choices
=LIEN_PARENTE_CHOICES
,
504 verbose_name
= u
"Lien de parenté",
505 null
=True, blank
=True)
508 ordering
= ['nom_affichage']
509 verbose_name
= u
"Ayant droit"
510 verbose_name_plural
= u
"Ayants droit"
512 def __unicode__(self
):
513 return u
'%s' % (self
.get_nom())
516 nom_affichage
= self
.nom_affichage
517 if not nom_affichage
:
518 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
521 prefix_implantation
= "employe__dossiers__poste__implantation__region"
522 def get_regions(self
):
524 for d
in self
.employe
.dossiers
.all():
525 regions
.append(d
.poste
.implantation
.region
)
529 class AyantDroitCommentaire(Commentaire
):
530 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
536 STATUT_RESIDENCE_CHOICES
= (
538 ('expat', 'Expatrié'),
541 COMPTE_COMPTA_CHOICES
= (
547 class Dossier_(AUFMetadata
):
548 """Le Dossier regroupe les informations relatives à l'occupation
549 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
552 Plusieurs Contrats peuvent être associés au Dossier.
553 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
554 lequel aucun Dossier n'existe est un poste vacant.
557 objects
= DossierManager()
560 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
562 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
563 db_column
='organisme_bstg',
565 verbose_name
= u
"Organisme",
566 help_text
="Si détaché (DET) ou \
567 mis à disposition (MAD), \
568 préciser l'organisme.",
569 null
=True, blank
=True)
572 remplacement
= models
.BooleanField(default
=False)
573 remplacement_de
= models
.ForeignKey('self', related_name
='+',
574 null
=True, blank
=True)
575 statut_residence
= models
.CharField(max_length
=10, default
='local',
576 verbose_name
= u
"Statut", null
=True,
577 choices
=STATUT_RESIDENCE_CHOICES
)
580 classement
= models
.ForeignKey('Classement', db_column
='classement',
582 null
=True, blank
=True)
583 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
585 default
=REGIME_TRAVAIL_DEFAULT
,
586 verbose_name
= u
"Régime de travail",
587 help_text
="% du temps complet")
588 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
589 decimal_places
=2, null
=True,
590 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
591 verbose_name
= u
"Nb. heures par semaine")
593 # Occupation du Poste par cet Employe (anciennement "mandat")
594 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
596 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
598 null
=True, blank
=True)
605 ordering
= ['employe__nom', ]
606 verbose_name
= u
"Dossier"
607 verbose_name_plural
= "Dossiers"
609 def salaire_theorique(self
):
610 annee
= date
.today().year
611 coeff
= self
.classement
.coefficient
612 implantation
= self
.poste
.implantation
613 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
615 montant
= coeff
* point
.valeur
616 devise
= point
.devise
617 return {'montant':montant
, 'devise':devise
}
619 def __unicode__(self
):
620 poste
= self
.poste
.nom
621 if self
.employe
.genre
== 'F':
622 poste
= self
.poste
.nom_feminin
623 return u
'%s - %s' % (self
.employe
, poste
)
625 prefix_implantation
= "poste__implantation__region"
626 def get_regions(self
):
627 return [self
.poste
.implantation
.region
]
630 def remunerations(self
):
631 return self
.rh_remunerations
.all().order_by('date_debut')
633 def get_salaire(self
):
635 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
639 class Dossier(Dossier_
):
640 __doc__
= Dossier_
.__doc__
641 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_dossiers')
642 employe
= models
.ForeignKey('Employe', db_column
='employe',
643 related_name
='%(app_label)s_dossiers',
644 verbose_name
=u
"Employé")
647 class DossierPiece_(models
.Model
):
648 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
649 Ex.: Lettre de motivation.
651 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
652 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
653 fichier
= models
.FileField(verbose_name
= u
"Fichier",
654 upload_to
=dossier_piece_dispatch
,
655 storage
=storage_prive
)
661 def __unicode__(self
):
662 return u
'%s' % (self
.nom
)
664 class DossierPiece(DossierPiece_
):
667 class DossierCommentaire_(Commentaire
):
668 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
672 class DossierCommentaire(DossierCommentaire_
):
675 class DossierComparaison_(models
.Model
):
677 Photo d'une comparaison salariale au moment de l'embauche.
679 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), related_name
='%(app_label)s_comparaisons')
680 objects
= DossierComparaisonManager()
682 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
683 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
684 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
685 montant
= models
.IntegerField(null
=True)
686 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
691 def taux_devise(self
):
692 annee
= self
.dossier
.poste
.date_debut
.year
693 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
696 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
))
700 def montant_euros(self
):
701 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
703 class DossierComparaison(DossierComparaison_
):
708 class RemunerationMixin(AUFMetadata
):
709 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_remunerations')
711 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
713 verbose_name
= u
"Type de rémunération")
714 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
715 db_column
='type_revalorisation',
717 verbose_name
= u
"Type de revalorisation",
718 null
=True, blank
=True)
719 montant
= models
.FloatField(null
=True, blank
=True,
721 # Annuel (12 mois, 52 semaines, 364 jours?)
722 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+', default
=5)
723 # commentaire = precision
724 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
725 # date_debut = anciennement date_effectif
726 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
727 null
=True, blank
=True)
728 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
729 null
=True, blank
=True)
733 ordering
= ['type__nom', '-date_fin']
735 def __unicode__(self
):
736 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
738 class Remuneration_(RemunerationMixin
):
739 """Structure de rémunération (données budgétaires) en situation normale
740 pour un Dossier. Si un Evenement existe, utiliser la structure de
741 rémunération EvenementRemuneration de cet événement.
744 def montant_mois(self
):
745 return round(self
.montant
/ 12, 2)
747 def taux_devise(self
):
748 if self
.devise
.code
== "EUR":
751 annee
= datetime
.datetime
.now().year
752 if self
.date_debut
is not None:
753 annee
= self
.date_debut
.year
754 if self
.dossier
.poste
.date_debut
is not None:
755 annee
= self
.dossier
.poste
.date_debut
.year
757 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise_id
, annee
=annee
)]
760 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
))
764 def montant_euro(self
):
765 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
767 def montant_euro_mois(self
):
768 return round(self
.montant_euro() / 12, 2)
770 def __unicode__(self
):
772 devise
= self
.devise
.code
775 return "%s %s" % (self
.montant
, devise
)
779 verbose_name
= u
"Rémunération"
780 verbose_name_plural
= u
"Rémunérations"
783 class Remuneration(Remuneration_
):
789 class ContratManager(NoDeleteManager
):
790 def get_query_set(self
):
791 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
794 class Contrat_(AUFMetadata
):
795 """Document juridique qui encadre la relation de travail d'un Employe
796 pour un Poste particulier. Pour un Dossier (qui documente cette
797 relation de travail) plusieurs contrats peuvent être associés.
799 objects
= ContratManager()
800 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_contrats')
801 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
803 verbose_name
= u
"Type de contrat")
804 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
805 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
806 null
=True, blank
=True)
810 ordering
= ['dossier__employe__nom_affichage']
811 verbose_name
= u
"Contrat"
812 verbose_name_plural
= u
"Contrats"
814 def __unicode__(self
):
815 return u
'%s - %s' % (self
.dossier
, self
.id)
817 class Contrat(Contrat_
):
823 #class Evenement_(AUFMetadata):
824 # """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
825 # d'un Dossier qui vient altérer des informations normales liées à un Dossier
826 # (ex.: la Remuneration).
828 # Ex.: congé de maternité, maladie...
830 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
831 # différent et une rémunération en conséquence. On souhaite toutefois
832 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
833 # du retour à la normale.
835 # dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
837 # nom = models.CharField(max_length=255)
838 # date_debut = models.DateField(verbose_name = u"Date de début")
839 # date_fin = models.DateField(verbose_name = u"Date de fin",
840 # null=True, blank=True)
845 # verbose_name = u"Évènement"
846 # verbose_name_plural = u"Évènements"
848 # def __unicode__(self):
849 # return u'%s' % (self.nom)
852 #class Evenement(Evenement_):
853 # __doc__ = Evenement_.__doc__
856 #class EvenementRemuneration_(RemunerationMixin):
857 # """Structure de rémunération liée à un Evenement qui remplace
858 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
861 # evenement = models.ForeignKey("Evenement", db_column='evenement',
863 # verbose_name = u"Évènement")
864 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
865 # # de l'Evenement associé
869 # ordering = ['evenement', 'type__nom', '-date_fin']
870 # verbose_name = u"Évènement - rémunération"
871 # verbose_name_plural = u"Évènements - rémunérations"
874 #class EvenementRemuneration(EvenementRemuneration_):
875 # __doc__ = EvenementRemuneration_.__doc__
881 #class EvenementRemuneration(EvenementRemuneration_):
882 # __doc__ = EvenementRemuneration_.__doc__
883 # TODO? class ContratPiece(models.Model):
888 class FamilleEmploi(AUFMetadata
):
889 """Catégorie utilisée dans la gestion des Postes.
890 Catégorie supérieure à TypePoste.
892 nom
= models
.CharField(max_length
=255)
896 verbose_name
= u
"Famille d'emploi"
897 verbose_name_plural
= u
"Familles d'emploi"
899 def __unicode__(self
):
900 return u
'%s' % (self
.nom
)
902 class TypePoste(AUFMetadata
):
903 """Catégorie de Poste.
905 nom
= models
.CharField(max_length
=255)
906 nom_feminin
= models
.CharField(max_length
=255,
907 verbose_name
= u
"Nom féminin")
909 is_responsable
= models
.BooleanField(default
=False,
910 verbose_name
= u
"Poste de responsabilité")
911 famille_emploi
= models
.ForeignKey('FamilleEmploi',
912 db_column
='famille_emploi',
914 verbose_name
= u
"Famille d'emploi")
918 verbose_name
= u
"Type de poste"
919 verbose_name_plural
= u
"Types de poste"
921 def __unicode__(self
):
922 return u
'%s' % (self
.nom
)
925 TYPE_PAIEMENT_CHOICES
= (
926 ('Régulier', 'Régulier'),
927 ('Ponctuel', 'Ponctuel'),
930 NATURE_REMUNERATION_CHOICES
= (
931 ('Accessoire', 'Accessoire'),
932 ('Charges', 'Charges'),
933 ('Indemnité', 'Indemnité'),
934 ('RAS', 'Rémunération autre source'),
935 ('Traitement', 'Traitement'),
938 class TypeRemuneration(AUFMetadata
):
939 """Catégorie de Remuneration.
941 nom
= models
.CharField(max_length
=255)
942 type_paiement
= models
.CharField(max_length
=30,
943 choices
=TYPE_PAIEMENT_CHOICES
,
944 verbose_name
= u
"Type de paiement")
945 nature_remuneration
= models
.CharField(max_length
=30,
946 choices
=NATURE_REMUNERATION_CHOICES
,
947 verbose_name
= u
"Nature de la rémunération")
951 verbose_name
= u
"Type de rémunération"
952 verbose_name_plural
= u
"Types de rémunération"
954 def __unicode__(self
):
955 return u
'%s' % (self
.nom
)
957 class TypeRevalorisation(AUFMetadata
):
958 """Justification du changement de la Remuneration.
959 (Actuellement utilisé dans aucun traitement informatique.)
961 nom
= models
.CharField(max_length
=255)
965 verbose_name
= u
"Type de revalorisation"
966 verbose_name_plural
= u
"Types de revalorisation"
968 def __unicode__(self
):
969 return u
'%s' % (self
.nom
)
971 class Service(AUFMetadata
):
972 """Unité administrative où les Postes sont rattachés.
974 nom
= models
.CharField(max_length
=255)
978 verbose_name
= u
"Service"
979 verbose_name_plural
= u
"Services"
981 def __unicode__(self
):
982 return u
'%s' % (self
.nom
)
985 TYPE_ORGANISME_CHOICES
= (
986 ('MAD', 'Mise à disposition'),
987 ('DET', 'Détachement'),
990 class OrganismeBstg(AUFMetadata
):
991 """Organisation d'où provient un Employe mis à disposition (MAD) de
992 ou détaché (DET) à l'AUF à titre gratuit.
994 (BSTG = bien et service à titre gratuit.)
996 nom
= models
.CharField(max_length
=255)
997 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
998 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1000 related_name
='organismes_bstg',
1001 null
=True, blank
=True)
1004 ordering
= ['type', 'nom']
1005 verbose_name
= u
"Organisme BSTG"
1006 verbose_name_plural
= u
"Organismes BSTG"
1008 def __unicode__(self
):
1009 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1011 prefix_implantation
= "pays__region"
1012 def get_regions(self
):
1013 return [self
.pays
.region
]
1016 class Statut(AUFMetadata
):
1017 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1020 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.")
1021 nom
= models
.CharField(max_length
=255)
1025 verbose_name
= u
"Statut d'employé"
1026 verbose_name_plural
= u
"Statuts d'employé"
1028 def __unicode__(self
):
1029 return u
'%s : %s' % (self
.code
, self
.nom
)
1032 TYPE_CLASSEMENT_CHOICES
= (
1033 ('S', 'S -Soutien'),
1034 ('T', 'T - Technicien'),
1035 ('P', 'P - Professionel'),
1037 ('D', 'D - Direction'),
1038 ('SO', 'SO - Sans objet [expatriés]'),
1039 ('HG', 'HG - Hors grille [direction]'),
1043 class Classement_(AUFMetadata
):
1044 """Éléments de classement de la
1045 "Grille générique de classement hiérarchique".
1047 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1048 classement dans la grille. Le classement donne le coefficient utilisé dans:
1050 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1053 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1054 echelon
= models
.IntegerField(verbose_name
=u
"Échelon", blank
=True, default
=0)
1055 degre
= models
.IntegerField(verbose_name
=u
"Degré", blank
=True, default
=0)
1056 coefficient
= models
.FloatField(default
=0, verbose_name
=u
"Coefficient",
1059 # annee # au lieu de date_debut et date_fin
1060 commentaire
= models
.TextField(null
=True, blank
=True)
1064 ordering
= ['type','echelon','degre','coefficient']
1065 verbose_name
= u
"Classement"
1066 verbose_name_plural
= u
"Classements"
1068 def __unicode__(self
):
1069 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1072 class Classement(Classement_
):
1073 __doc__
= Classement_
.__doc__
1076 class TauxChange_(AUFMetadata
):
1077 """Taux de change de la devise vers l'euro (EUR)
1078 pour chaque année budgétaire.
1081 devise
= models
.ForeignKey('Devise', db_column
='devise')
1082 annee
= models
.IntegerField(verbose_name
= u
"Année")
1083 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1087 ordering
= ['-annee', 'devise__code']
1088 verbose_name
= u
"Taux de change"
1089 verbose_name_plural
= u
"Taux de change"
1091 def __unicode__(self
):
1092 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1095 class TauxChange(TauxChange_
):
1096 __doc__
= TauxChange_
.__doc__
1098 class ValeurPointManager(NoDeleteManager
):
1099 def get_query_set(self
):
1100 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1103 class ValeurPoint_(AUFMetadata
):
1104 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1105 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1106 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1108 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1111 actuelles
= ValeurPointManager()
1113 valeur
= models
.FloatField(null
=True)
1114 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1115 related_name
='+', default
=5)
1116 implantation
= models
.ForeignKey(ref
.Implantation
,
1117 db_column
='implantation',
1118 related_name
='%(app_label)s_valeur_point')
1120 annee
= models
.IntegerField()
1123 ordering
= ['-annee', 'implantation__nom']
1125 verbose_name
= u
"Valeur du point"
1126 verbose_name_plural
= u
"Valeurs du point"
1128 def __unicode__(self
):
1129 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1132 class ValeurPoint(ValeurPoint_
):
1133 __doc__
= ValeurPoint_
.__doc__
1136 class Devise(AUFMetadata
):
1137 """Devise monétaire.
1139 code
= models
.CharField(max_length
=10, unique
=True)
1140 nom
= models
.CharField(max_length
=255)
1144 verbose_name
= u
"Devise"
1145 verbose_name_plural
= u
"Devises"
1147 def __unicode__(self
):
1148 return u
'%s - %s' % (self
.code
, self
.nom
)
1150 class TypeContrat(AUFMetadata
):
1153 nom
= models
.CharField(max_length
=255)
1154 nom_long
= models
.CharField(max_length
=255)
1158 verbose_name
= u
"Type de contrat"
1159 verbose_name_plural
= u
"Types de contrat"
1161 def __unicode__(self
):
1162 return u
'%s' % (self
.nom
)
1167 class ResponsableImplantation(AUFMetadata
):
1168 """Le responsable d'une implantation.
1169 Anciennement géré sur le Dossier du responsable.
1171 employe
= models
.ForeignKey('Employe', db_column
='employe',
1173 null
=True, blank
=True)
1174 implantation
= models
.ForeignKey(ref
.Implantation
,
1175 db_column
='implantation', related_name
='+',
1178 def __unicode__(self
):
1179 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1182 ordering
= ['implantation__nom']
1183 verbose_name
= "Responsable d'implantation"
1184 verbose_name_plural
= "Responsables d'implantation"