1 # -=- encoding: utf-8 -=-
4 from datetime
import date
6 from django
.core
.files
.storage
import FileSystemStorage
7 from django
.db
import models
8 from django
.conf
import settings
10 from auf
.django
.metadata
.models
import AUFMetadata
11 from auf
.django
.metadata
.managers
import NoDeleteManager
12 import auf
.django
.references
.models
as ref
13 from validators
import validate_date_passee
14 from managers
import PosteManager
, DossierManager
, DossierComparaisonManager
, PosteComparaisonManager
18 # Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
19 # Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
22 models_stack
= [s
[1].split('/')[-2] for s
in inspect
.stack() if s
[1].endswith('models.py')]
23 return models_stack
[-1]
27 REGIME_TRAVAIL_DEFAULT
= 100.00
28 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= 35.00
31 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
32 base_url
=settings
.PRIVE_MEDIA_URL
)
34 def poste_piece_dispatch(instance
, filename
):
35 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
38 def dossier_piece_dispatch(instance
, filename
):
39 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
42 def employe_piece_dispatch(instance
, filename
):
43 path
= "employe/%s/%s" % (instance
.employe_id
, filename
)
47 class Commentaire(AUFMetadata
):
48 texte
= models
.TextField()
49 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+')
53 ordering
= ['-date_creation']
55 def __unicode__(self
):
56 return u
'%s' % (self
.texte
)
61 POSTE_APPEL_CHOICES
= (
62 ('interne', 'Interne'),
63 ('externe', 'Externe'),
66 class Poste_(AUFMetadata
):
67 """Un Poste est un emploi (job) à combler dans une implantation.
68 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
69 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
72 objects
= PosteManager()
75 nom
= models
.CharField(max_length
=255,
76 verbose_name
= u
"Titre du poste", )
77 nom_feminin
= models
.CharField(max_length
=255,
78 verbose_name
= u
"Titre du poste (au féminin)",
80 implantation
= models
.ForeignKey(ref
.Implantation
,
81 db_column
='implantation', related_name
='+')
82 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste',
85 service
= models
.ForeignKey('Service', db_column
='service', null
=True,
87 verbose_name
= u
"Direction/Service/Pôle support", )
88 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
89 related_name
='+', null
=True,
90 verbose_name
= u
"Poste du responsable", )
93 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
94 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
95 verbose_name
= u
"Temps de travail",
96 help_text
="% du temps complet")
97 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
98 decimal_places
=2, null
=True,
99 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
100 verbose_name
= u
"Nb. heures par semaine")
103 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
104 null
=True, blank
=True)
105 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
106 null
=True, blank
=True)
107 mise_a_disposition
= models
.NullBooleanField(
108 verbose_name
= u
"Mise à disposition",
109 null
=True, default
=False)
110 appel
= models
.CharField(max_length
=10, null
=True,
111 verbose_name
= u
"Appel à candidature",
112 choices
=POSTE_APPEL_CHOICES
,
116 classement_min
= models
.ForeignKey('Classement',
117 db_column
='classement_min', related_name
='+',
118 null
=True, blank
=True)
119 classement_max
= models
.ForeignKey('Classement',
120 db_column
='classement_max', related_name
='+',
121 null
=True, blank
=True)
122 valeur_point_min
= models
.ForeignKey('ValeurPoint',
123 db_column
='valeur_point_min', related_name
='+',
124 null
=True, blank
=True)
125 valeur_point_max
= models
.ForeignKey('ValeurPoint',
126 db_column
='valeur_point_max', related_name
='+',
127 null
=True, blank
=True)
128 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
129 related_name
='+', default
=5)
130 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
131 related_name
='+', default
=5)
132 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
133 null
=True, default
=0)
134 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
135 null
=True, default
=0)
136 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
137 null
=True, default
=0)
138 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
139 null
=True, default
=0)
140 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
141 null
=True, default
=0)
142 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
143 null
=True, default
=0)
145 # Comparatifs de rémunération
146 devise_comparaison
= models
.ForeignKey('Devise', null
=True,
147 db_column
='devise_comparaison',
150 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
151 null
=True, blank
=True)
152 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
153 null
=True, blank
=True)
154 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
155 null
=True, blank
=True)
156 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
157 null
=True, blank
=True)
158 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
159 null
=True, blank
=True)
160 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
161 null
=True, blank
=True)
162 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
163 null
=True, blank
=True)
164 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
165 null
=True, blank
=True)
166 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
167 null
=True, blank
=True)
168 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
169 null
=True, blank
=True)
172 justification
= models
.TextField(null
=True, blank
=True)
175 date_validation
= models
.DateTimeField(null
=True, blank
=True) # de dae
176 date_debut
= models
.DateField(verbose_name
=u
"Date de début",
177 null
=True, blank
=True)
178 date_fin
= models
.DateField(verbose_name
=u
"Date de fin",
179 null
=True, blank
=True)
183 ordering
= ['implantation__nom', 'nom']
184 verbose_name
= u
"Poste"
185 verbose_name_plural
= u
"Postes"
187 def __unicode__(self
):
188 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
191 representation
= representation
+ u
' (VACANT)'
192 return representation
196 if self
.occupe_par():
200 def occupe_par(self
):
201 """Retourne la liste d'employé occupant ce poste.
202 Généralement, retourne une liste d'un élément.
203 Si poste inoccupé, retourne liste vide.
205 return [d
.employe
for d
in self
.rh_dossiers
.filter(actif
=True, supprime
=False) \
206 .exclude(date_fin__lt
=date
.today())]
208 prefix_implantation
= "implantation__region"
209 def get_regions(self
):
210 return [self
.implantation
.region
]
214 __doc__
= Poste_
.__doc__
218 __doc__
= Poste_
.__doc__
221 POSTE_FINANCEMENT_CHOICES
= (
222 ('A', 'A - Frais de personnel'),
223 ('B', 'B - Projet(s)-Titre(s)'),
228 class PosteFinancement_(models
.Model
):
229 """Pour un Poste, structure d'informations décrivant comment on prévoit
232 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_financements')
233 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
234 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
235 help_text
="ex.: 33.33 % (décimale avec point)")
236 commentaire
= models
.TextField(
237 help_text
="Spécifiez la source de financement.")
243 def __unicode__(self
):
244 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
247 class PosteFinancement(PosteFinancement_
):
251 class PostePiece_(models
.Model
):
252 """Documents relatifs au Poste.
253 Ex.: Description de poste
255 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_pieces')
256 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
257 fichier
= models
.FileField(verbose_name
= u
"Fichier",
258 upload_to
=poste_piece_dispatch
,
259 storage
=storage_prive
)
265 def __unicode__(self
):
266 return u
'%s' % (self
.nom
)
268 class PostePiece(PostePiece_
):
271 class PosteComparaison_(models
.Model
):
273 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
275 poste
= models
.ForeignKey('%s.Poste' % app_context(), related_name
='%(app_label)s_comparaisons_internes')
276 objects
= PosteComparaisonManager()
278 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
279 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
280 montant
= models
.IntegerField(null
=True)
281 devise
= models
.ForeignKey("Devise", default
=5, related_name
='+', null
=True, blank
=True)
286 def taux_devise(self
):
287 if self
.devise
.code
== "EUR":
289 annee
= self
.poste
.date_debut
.year
290 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
293 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
))
297 def montant_euros(self
):
298 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
300 class PosteComparaison(PosteComparaison_
):
303 class PosteCommentaire_(Commentaire
):
304 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='+')
309 class PosteCommentaire(PosteCommentaire_
):
318 SITUATION_CHOICES
= (
319 ('C', 'Célibataire'),
324 class Employe(AUFMetadata
):
325 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
326 Dossiers qu'il occupe ou a occupé de Postes.
328 Cette classe aurait pu avantageusement s'appeler Personne car la notion
329 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
332 nom
= models
.CharField(max_length
=255)
333 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
334 nom_affichage
= models
.CharField(max_length
=255,
335 verbose_name
= u
"Nom d'affichage",
336 null
=True, blank
=True)
337 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
338 db_column
='nationalite',
339 related_name
='employes_nationalite',
340 verbose_name
= u
"Nationalité")
341 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
342 validators
=[validate_date_passee
],
343 null
=True, blank
=True)
344 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
347 situation_famille
= models
.CharField(max_length
=1,
348 choices
=SITUATION_CHOICES
,
349 verbose_name
= u
"Situation familiale",
350 null
=True, blank
=True)
351 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
352 null
=True, blank
=True)
355 tel_domicile
= models
.CharField(max_length
=255,
356 verbose_name
= u
"Tél. domicile",
357 null
=True, blank
=True)
358 tel_cellulaire
= models
.CharField(max_length
=255,
359 verbose_name
= u
"Tél. cellulaire",
360 null
=True, blank
=True)
361 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
362 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
363 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
364 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
365 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
366 related_name
='employes',
367 null
=True, blank
=True)
370 ordering
= ['nom_affichage','nom','prenom']
371 verbose_name
= u
"Employé"
372 verbose_name_plural
= u
"Employés"
374 def __unicode__(self
):
375 return u
'%s [%s]' % (self
.get_nom(), self
.id)
378 nom_affichage
= self
.nom_affichage
379 if not nom_affichage
:
380 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
385 if self
.genre
.upper() == u
'M':
387 elif self
.genre
.upper() == u
'F':
392 """Retourne l'URL du service retournant la photo de l'Employe.
393 Équivalent reverse url 'rh_photo' avec id en param.
395 from django
.core
.urlresolvers
import reverse
396 return reverse('rh_photo', kwargs
={'id':self
.id})
398 def dossiers_passes(self
):
400 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
401 for d
in dossiers_passes
:
403 return dossiers_passes
405 def dossiers_futurs(self
):
407 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
409 def dossiers_encours(self
):
410 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
411 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
412 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
414 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
415 for d
in dossiers_encours
:
416 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
417 return dossiers_encours
419 def postes_encours(self
):
420 postes_encours
= set()
421 for d
in self
.dossiers_encours():
422 postes_encours
.add(d
.poste
)
423 return postes_encours
425 def poste_principal(self
):
427 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
429 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
431 poste
= Poste
.objects
.none()
433 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
438 prefix_implantation
= "dossiers__poste__implantation__region"
439 def get_regions(self
):
441 for d
in self
.dossiers
.all():
442 regions
.append(d
.poste
.implantation
.region
)
446 class EmployePiece(models
.Model
):
447 """Documents relatifs à un employé.
450 employe
= models
.ForeignKey('Employe', db_column
='employe')
451 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
452 fichier
= models
.FileField(verbose_name
="Fichier",
453 upload_to
=employe_piece_dispatch
,
454 storage
=storage_prive
)
458 verbose_name
= u
"Employé pièce"
459 verbose_name_plural
= u
"Employé pièces"
461 def __unicode__(self
):
462 return u
'%s' % (self
.nom
)
464 class EmployeCommentaire(Commentaire
):
465 employe
= models
.ForeignKey('Employe', db_column
='employe',
469 verbose_name
= u
"Employé commentaire"
470 verbose_name_plural
= u
"Employé commentaires"
473 LIEN_PARENTE_CHOICES
= (
474 ('Conjoint', 'Conjoint'),
475 ('Conjointe', 'Conjointe'),
480 class AyantDroit(AUFMetadata
):
481 """Personne en relation avec un Employe.
484 nom
= models
.CharField(max_length
=255)
485 prenom
= models
.CharField(max_length
=255,
486 verbose_name
= u
"Prénom",)
487 nom_affichage
= models
.CharField(max_length
=255,
488 verbose_name
= u
"Nom d'affichage",
489 null
=True, blank
=True)
490 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
491 db_column
='nationalite',
492 related_name
='ayantdroits_nationalite',
493 verbose_name
= u
"Nationalité")
494 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
495 validators
=[validate_date_passee
],
496 null
=True, blank
=True)
497 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
500 employe
= models
.ForeignKey('Employe', db_column
='employe',
501 related_name
='ayantdroits',
502 verbose_name
= u
"Employé")
503 lien_parente
= models
.CharField(max_length
=10,
504 choices
=LIEN_PARENTE_CHOICES
,
505 verbose_name
= u
"Lien de parenté",
506 null
=True, blank
=True)
509 ordering
= ['nom_affichage']
510 verbose_name
= u
"Ayant droit"
511 verbose_name_plural
= u
"Ayants droit"
513 def __unicode__(self
):
514 return u
'%s' % (self
.get_nom())
517 nom_affichage
= self
.nom_affichage
518 if not nom_affichage
:
519 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
522 prefix_implantation
= "employe__dossiers__poste__implantation__region"
523 def get_regions(self
):
525 for d
in self
.employe
.dossiers
.all():
526 regions
.append(d
.poste
.implantation
.region
)
530 class AyantDroitCommentaire(Commentaire
):
531 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
537 STATUT_RESIDENCE_CHOICES
= (
539 ('expat', 'Expatrié'),
542 COMPTE_COMPTA_CHOICES
= (
548 class Dossier_(AUFMetadata
):
549 """Le Dossier regroupe les informations relatives à l'occupation
550 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
553 Plusieurs Contrats peuvent être associés au Dossier.
554 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
555 lequel aucun Dossier n'existe est un poste vacant.
558 objects
= DossierManager()
561 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
563 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
564 db_column
='organisme_bstg',
566 verbose_name
= u
"Organisme",
567 help_text
="Si détaché (DET) ou \
568 mis à disposition (MAD), \
569 préciser l'organisme.",
570 null
=True, blank
=True)
573 remplacement
= models
.BooleanField(default
=False)
574 remplacement_de
= models
.ForeignKey('self', related_name
='+',
575 null
=True, blank
=True)
576 statut_residence
= models
.CharField(max_length
=10, default
='local',
577 verbose_name
= u
"Statut", null
=True,
578 choices
=STATUT_RESIDENCE_CHOICES
)
581 classement
= models
.ForeignKey('Classement', db_column
='classement',
583 null
=True, blank
=True)
584 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
586 default
=REGIME_TRAVAIL_DEFAULT
,
587 verbose_name
= u
"Régime de travail",
588 help_text
="% du temps complet")
589 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
590 decimal_places
=2, null
=True,
591 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
592 verbose_name
= u
"Nb. heures par semaine")
594 # Occupation du Poste par cet Employe (anciennement "mandat")
595 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
597 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
599 null
=True, blank
=True)
606 ordering
= ['employe__nom', ]
607 verbose_name
= u
"Dossier"
608 verbose_name_plural
= "Dossiers"
610 def salaire_theorique(self
):
611 annee
= date
.today().year
612 coeff
= self
.classement
.coefficient
613 implantation
= self
.poste
.implantation
614 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
616 montant
= coeff
* point
.valeur
617 devise
= point
.devise
618 return {'montant':montant
, 'devise':devise
}
620 def __unicode__(self
):
621 poste
= self
.poste
.nom
622 if self
.employe
.genre
== 'F':
623 poste
= self
.poste
.nom_feminin
624 return u
'%s - %s' % (self
.employe
, poste
)
626 prefix_implantation
= "poste__implantation__region"
627 def get_regions(self
):
628 return [self
.poste
.implantation
.region
]
631 def remunerations(self
):
632 return self
.rh_remunerations
.all().order_by('date_debut')
634 def get_salaire(self
):
636 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
640 class Dossier(Dossier_
):
641 __doc__
= Dossier_
.__doc__
642 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_dossiers')
643 employe
= models
.ForeignKey('Employe', db_column
='employe',
644 related_name
='%(app_label)s_dossiers',
645 verbose_name
=u
"Employé")
648 class DossierPiece_(models
.Model
):
649 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
650 Ex.: Lettre de motivation.
652 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
653 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
654 fichier
= models
.FileField(verbose_name
= u
"Fichier",
655 upload_to
=dossier_piece_dispatch
,
656 storage
=storage_prive
)
662 def __unicode__(self
):
663 return u
'%s' % (self
.nom
)
665 class DossierPiece(DossierPiece_
):
668 class DossierCommentaire_(Commentaire
):
669 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
673 class DossierCommentaire(DossierCommentaire_
):
676 class DossierComparaison_(models
.Model
):
678 Photo d'une comparaison salariale au moment de l'embauche.
680 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), related_name
='%(app_label)s_comparaisons')
681 objects
= DossierComparaisonManager()
683 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
684 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
685 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
686 montant
= models
.IntegerField(null
=True)
687 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
692 def taux_devise(self
):
693 annee
= self
.dossier
.poste
.date_debut
.year
694 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
697 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
))
701 def montant_euros(self
):
702 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
704 class DossierComparaison(DossierComparaison_
):
709 class RemunerationMixin(AUFMetadata
):
710 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_remunerations')
712 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
714 verbose_name
= u
"Type de rémunération")
715 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
716 db_column
='type_revalorisation',
718 verbose_name
= u
"Type de revalorisation",
719 null
=True, blank
=True)
720 montant
= models
.FloatField(null
=True, blank
=True,
722 # Annuel (12 mois, 52 semaines, 364 jours?)
723 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+', default
=5)
724 # commentaire = precision
725 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
726 # date_debut = anciennement date_effectif
727 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
728 null
=True, blank
=True)
729 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
730 null
=True, blank
=True)
734 ordering
= ['type__nom', '-date_fin']
736 def __unicode__(self
):
737 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
739 class Remuneration_(RemunerationMixin
):
740 """Structure de rémunération (données budgétaires) en situation normale
741 pour un Dossier. Si un Evenement existe, utiliser la structure de
742 rémunération EvenementRemuneration de cet événement.
745 def montant_mois(self
):
746 return round(self
.montant
/ 12, 2)
748 def taux_devise(self
):
749 if self
.devise
.code
== "EUR":
752 annee
= datetime
.datetime
.now().year
753 if self
.date_debut
is not None:
754 annee
= self
.date_debut
.year
755 if self
.dossier
.poste
.date_debut
is not None:
756 annee
= self
.dossier
.poste
.date_debut
758 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise_id
, annee
=annee
)]
761 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
))
765 def montant_euro(self
):
766 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
768 def montant_euro_mois(self
):
769 return round(self
.montant_euro() / 12, 2)
771 def __unicode__(self
):
773 devise
= self
.devise
.code
776 return "%s %s" % (self
.montant
, devise
)
780 verbose_name
= u
"Rémunération"
781 verbose_name_plural
= u
"Rémunérations"
784 class Remuneration(Remuneration_
):
790 class ContratManager(NoDeleteManager
):
791 def get_query_set(self
):
792 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
795 class Contrat_(AUFMetadata
):
796 """Document juridique qui encadre la relation de travail d'un Employe
797 pour un Poste particulier. Pour un Dossier (qui documente cette
798 relation de travail) plusieurs contrats peuvent être associés.
800 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_contrats')
801 objects
= ContratManager()
803 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
805 verbose_name
= u
"Type de contrat")
806 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
807 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
808 null
=True, blank
=True)
812 ordering
= ['dossier__employe__nom_affichage']
813 verbose_name
= u
"Contrat"
814 verbose_name_plural
= u
"Contrats"
816 def __unicode__(self
):
817 return u
'%s - %s' % (self
.dossier
, self
.id)
819 class Contrat(Contrat_
):
825 #class Evenement_(AUFMetadata):
826 # """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
827 # d'un Dossier qui vient altérer des informations normales liées à un Dossier
828 # (ex.: la Remuneration).
830 # Ex.: congé de maternité, maladie...
832 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
833 # différent et une rémunération en conséquence. On souhaite toutefois
834 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
835 # du retour à la normale.
837 # dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
839 # nom = models.CharField(max_length=255)
840 # date_debut = models.DateField(verbose_name = u"Date de début")
841 # date_fin = models.DateField(verbose_name = u"Date de fin",
842 # null=True, blank=True)
847 # verbose_name = u"Évènement"
848 # verbose_name_plural = u"Évènements"
850 # def __unicode__(self):
851 # return u'%s' % (self.nom)
854 #class Evenement(Evenement_):
855 # __doc__ = Evenement_.__doc__
858 #class EvenementRemuneration_(RemunerationMixin):
859 # """Structure de rémunération liée à un Evenement qui remplace
860 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
863 # evenement = models.ForeignKey("Evenement", db_column='evenement',
865 # verbose_name = u"Évènement")
866 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
867 # # de l'Evenement associé
871 # ordering = ['evenement', 'type__nom', '-date_fin']
872 # verbose_name = u"Évènement - rémunération"
873 # verbose_name_plural = u"Évènements - rémunérations"
876 #class EvenementRemuneration(EvenementRemuneration_):
877 # __doc__ = EvenementRemuneration_.__doc__
883 #class EvenementRemuneration(EvenementRemuneration_):
884 # __doc__ = EvenementRemuneration_.__doc__
889 class FamilleEmploi(AUFMetadata
):
890 """Catégorie utilisée dans la gestion des Postes.
891 Catégorie supérieure à TypePoste.
893 nom
= models
.CharField(max_length
=255)
897 verbose_name
= u
"Famille d'emploi"
898 verbose_name_plural
= u
"Familles d'emploi"
900 def __unicode__(self
):
901 return u
'%s' % (self
.nom
)
903 class TypePoste(AUFMetadata
):
904 """Catégorie de Poste.
906 nom
= models
.CharField(max_length
=255)
907 nom_feminin
= models
.CharField(max_length
=255,
908 verbose_name
= u
"Nom féminin")
910 is_responsable
= models
.BooleanField(default
=False,
911 verbose_name
= u
"Poste de responsabilité")
912 famille_emploi
= models
.ForeignKey('FamilleEmploi',
913 db_column
='famille_emploi',
915 verbose_name
= u
"Famille d'emploi")
919 verbose_name
= u
"Type de poste"
920 verbose_name_plural
= u
"Types de poste"
922 def __unicode__(self
):
923 return u
'%s' % (self
.nom
)
926 TYPE_PAIEMENT_CHOICES
= (
927 ('Régulier', 'Régulier'),
928 ('Ponctuel', 'Ponctuel'),
931 NATURE_REMUNERATION_CHOICES
= (
932 ('Accessoire', 'Accessoire'),
933 ('Charges', 'Charges'),
934 ('Indemnité', 'Indemnité'),
935 ('RAS', 'Rémunération autre source'),
936 ('Traitement', 'Traitement'),
939 class TypeRemuneration(AUFMetadata
):
940 """Catégorie de Remuneration.
942 nom
= models
.CharField(max_length
=255)
943 type_paiement
= models
.CharField(max_length
=30,
944 choices
=TYPE_PAIEMENT_CHOICES
,
945 verbose_name
= u
"Type de paiement")
946 nature_remuneration
= models
.CharField(max_length
=30,
947 choices
=NATURE_REMUNERATION_CHOICES
,
948 verbose_name
= u
"Nature de la rémunération")
952 verbose_name
= u
"Type de rémunération"
953 verbose_name_plural
= u
"Types de rémunération"
955 def __unicode__(self
):
956 return u
'%s' % (self
.nom
)
958 class TypeRevalorisation(AUFMetadata
):
959 """Justification du changement de la Remuneration.
960 (Actuellement utilisé dans aucun traitement informatique.)
962 nom
= models
.CharField(max_length
=255)
966 verbose_name
= u
"Type de revalorisation"
967 verbose_name_plural
= u
"Types de revalorisation"
969 def __unicode__(self
):
970 return u
'%s' % (self
.nom
)
972 class Service(AUFMetadata
):
973 """Unité administrative où les Postes sont rattachés.
975 nom
= models
.CharField(max_length
=255)
979 verbose_name
= u
"Service"
980 verbose_name_plural
= u
"Services"
982 def __unicode__(self
):
983 return u
'%s' % (self
.nom
)
986 TYPE_ORGANISME_CHOICES
= (
987 ('MAD', 'Mise à disposition'),
988 ('DET', 'Détachement'),
991 class OrganismeBstg(AUFMetadata
):
992 """Organisation d'où provient un Employe mis à disposition (MAD) de
993 ou détaché (DET) à l'AUF à titre gratuit.
995 (BSTG = bien et service à titre gratuit.)
997 nom
= models
.CharField(max_length
=255)
998 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
999 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1001 related_name
='organismes_bstg',
1002 null
=True, blank
=True)
1005 ordering
= ['type', 'nom']
1006 verbose_name
= u
"Organisme BSTG"
1007 verbose_name_plural
= u
"Organismes BSTG"
1009 def __unicode__(self
):
1010 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1012 prefix_implantation
= "pays__region"
1013 def get_regions(self
):
1014 return [self
.pays
.region
]
1017 class Statut(AUFMetadata
):
1018 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1021 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.")
1022 nom
= models
.CharField(max_length
=255)
1026 verbose_name
= u
"Statut d'employé"
1027 verbose_name_plural
= u
"Statuts d'employé"
1029 def __unicode__(self
):
1030 return u
'%s : %s' % (self
.code
, self
.nom
)
1033 TYPE_CLASSEMENT_CHOICES
= (
1034 ('S', 'S -Soutien'),
1035 ('T', 'T - Technicien'),
1036 ('P', 'P - Professionel'),
1038 ('D', 'D - Direction'),
1039 ('SO', 'SO - Sans objet [expatriés]'),
1040 ('HG', 'HG - Hors grille [direction]'),
1044 class Classement_(AUFMetadata
):
1045 """Éléments de classement de la
1046 "Grille générique de classement hiérarchique".
1048 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1049 classement dans la grille. Le classement donne le coefficient utilisé dans:
1051 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1054 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1055 echelon
= models
.IntegerField(verbose_name
= u
"Échelon")
1056 degre
= models
.IntegerField(verbose_name
= u
"Degré")
1057 coefficient
= models
.FloatField(default
=0, verbose_name
= u
"Coéfficient",
1060 # annee # au lieu de date_debut et date_fin
1061 commentaire
= models
.TextField(null
=True, blank
=True)
1065 ordering
= ['type','echelon','degre','coefficient']
1066 verbose_name
= u
"Classement"
1067 verbose_name_plural
= u
"Classements"
1069 def __unicode__(self
):
1070 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1073 class Classement(Classement_
):
1074 __doc__
= Classement_
.__doc__
1077 class TauxChange_(AUFMetadata
):
1078 """Taux de change de la devise vers l'euro (EUR)
1079 pour chaque année budgétaire.
1082 devise
= models
.ForeignKey('Devise', db_column
='devise')
1083 annee
= models
.IntegerField(verbose_name
= u
"Année")
1084 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1088 ordering
= ['-annee', 'devise__code']
1089 verbose_name
= u
"Taux de change"
1090 verbose_name_plural
= u
"Taux de change"
1092 def __unicode__(self
):
1093 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1096 class TauxChange(TauxChange_
):
1097 __doc__
= TauxChange_
.__doc__
1099 class ValeurPointManager(NoDeleteManager
):
1100 def get_query_set(self
):
1101 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1104 class ValeurPoint_(AUFMetadata
):
1105 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1106 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1107 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1109 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1112 actuelles
= ValeurPointManager()
1114 valeur
= models
.FloatField(null
=True)
1115 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1116 related_name
='+', default
=5)
1117 implantation
= models
.ForeignKey(ref
.Implantation
,
1118 db_column
='implantation',
1119 related_name
='%(app_label)s_valeur_point')
1121 annee
= models
.IntegerField()
1124 ordering
= ['-annee', 'implantation__nom']
1126 verbose_name
= u
"Valeur du point"
1127 verbose_name_plural
= u
"Valeurs du point"
1129 def __unicode__(self
):
1130 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1133 class ValeurPoint(ValeurPoint_
):
1134 __doc__
= ValeurPoint_
.__doc__
1137 class Devise(AUFMetadata
):
1138 """Devise monétaire.
1140 code
= models
.CharField(max_length
=10, unique
=True)
1141 nom
= models
.CharField(max_length
=255)
1145 verbose_name
= u
"Devise"
1146 verbose_name_plural
= u
"Devises"
1148 def __unicode__(self
):
1149 return u
'%s - %s' % (self
.code
, self
.nom
)
1151 class TypeContrat(AUFMetadata
):
1154 nom
= models
.CharField(max_length
=255)
1155 nom_long
= models
.CharField(max_length
=255)
1159 verbose_name
= u
"Type de contrat"
1160 verbose_name_plural
= u
"Types de contrat"
1162 def __unicode__(self
):
1163 return u
'%s' % (self
.nom
)
1168 class ResponsableImplantation(AUFMetadata
):
1169 """Le responsable d'une implantation.
1170 Anciennement géré sur le Dossier du responsable.
1172 employe
= models
.ForeignKey('Employe', db_column
='employe',
1174 null
=True, blank
=True)
1175 implantation
= models
.ForeignKey(ref
.Implantation
,
1176 db_column
='implantation', related_name
='+',
1179 def __unicode__(self
):
1180 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1183 ordering
= ['implantation__nom']
1184 verbose_name
= "Responsable d'implantation"
1185 verbose_name_plural
= "Responsables d'implantation"