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 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
30 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
33 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
34 base_url
=settings
.PRIVE_MEDIA_URL
)
36 def poste_piece_dispatch(instance
, filename
):
37 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
40 def dossier_piece_dispatch(instance
, filename
):
41 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
44 def employe_piece_dispatch(instance
, filename
):
45 path
= "employe/%s/%s" % (instance
.employe_id
, filename
)
49 class Commentaire(AUFMetadata
):
50 texte
= models
.TextField()
51 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+')
55 ordering
= ['-date_creation']
57 def __unicode__(self
):
58 return u
'%s' % (self
.texte
)
63 POSTE_APPEL_CHOICES
= (
64 ('interne', 'Interne'),
65 ('externe', 'Externe'),
68 class Poste_(AUFMetadata
):
69 """Un Poste est un emploi (job) à combler dans une implantation.
70 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
71 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
74 objects
= PosteManager()
77 nom
= models
.CharField(max_length
=255,
78 verbose_name
= u
"Titre du poste", )
79 nom_feminin
= models
.CharField(max_length
=255,
80 verbose_name
= u
"Titre du poste (au féminin)",
82 implantation
= models
.ForeignKey(ref
.Implantation
,
83 db_column
='implantation', related_name
='+')
84 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste',
87 service
= models
.ForeignKey('Service', db_column
='service',
89 verbose_name
= u
"Direction/Service/Pôle support", )
90 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
91 related_name
='+', null
=True,
92 verbose_name
= u
"Poste du responsable", )
95 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
96 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
97 verbose_name
= u
"Temps de travail",
98 help_text
="% du temps complet")
99 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
100 decimal_places
=2, null
=True,
101 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
102 verbose_name
= u
"Nb. heures par semaine")
105 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
106 null
=True, blank
=True)
107 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
108 null
=True, blank
=True)
109 mise_a_disposition
= models
.NullBooleanField(
110 verbose_name
= u
"Mise à disposition",
111 null
=True, default
=False)
112 appel
= models
.CharField(max_length
=10, null
=True,
113 verbose_name
= u
"Appel à candidature",
114 choices
=POSTE_APPEL_CHOICES
,
118 classement_min
= models
.ForeignKey('Classement',
119 db_column
='classement_min', related_name
='+',
120 null
=True, blank
=True)
121 classement_max
= models
.ForeignKey('Classement',
122 db_column
='classement_max', related_name
='+',
123 null
=True, blank
=True)
124 valeur_point_min
= models
.ForeignKey('ValeurPoint',
125 db_column
='valeur_point_min', related_name
='+',
126 null
=True, blank
=True)
127 valeur_point_max
= models
.ForeignKey('ValeurPoint',
128 db_column
='valeur_point_max', related_name
='+',
129 null
=True, blank
=True)
130 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
131 related_name
='+', default
=5)
132 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
133 related_name
='+', default
=5)
134 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
135 null
=True, default
=0)
136 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
137 null
=True, default
=0)
138 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
139 null
=True, default
=0)
140 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
141 null
=True, default
=0)
142 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
143 null
=True, default
=0)
144 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
145 null
=True, default
=0)
147 # Comparatifs de rémunération
148 devise_comparaison
= models
.ForeignKey('Devise', null
=True,
149 db_column
='devise_comparaison',
152 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
153 null
=True, blank
=True)
154 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
155 null
=True, blank
=True)
156 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
157 null
=True, blank
=True)
158 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
159 null
=True, blank
=True)
160 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
161 null
=True, blank
=True)
162 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
163 null
=True, blank
=True)
164 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
165 null
=True, blank
=True)
166 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
167 null
=True, blank
=True)
168 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
169 null
=True, blank
=True)
170 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
171 null
=True, blank
=True)
174 justification
= models
.TextField(null
=True, blank
=True)
177 date_validation
= models
.DateTimeField(null
=True, blank
=True) # de dae
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
,
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 validators
=[validate_date_passee
],
332 null
=True, blank
=True)
333 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
336 situation_famille
= models
.CharField(max_length
=1,
337 choices
=SITUATION_CHOICES
,
338 verbose_name
= u
"Situation familiale",
339 null
=True, blank
=True)
340 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
341 null
=True, blank
=True)
344 tel_domicile
= models
.CharField(max_length
=255,
345 verbose_name
= u
"Tél. domicile",
346 null
=True, blank
=True)
347 tel_cellulaire
= models
.CharField(max_length
=255,
348 verbose_name
= u
"Tél. cellulaire",
349 null
=True, blank
=True)
350 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
351 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
352 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
353 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
354 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
355 related_name
='employes',
356 null
=True, blank
=True)
359 ordering
= ['nom_affichage','nom','prenom']
360 verbose_name
= u
"Employé"
361 verbose_name_plural
= u
"Employés"
363 def __unicode__(self
):
364 return u
'%s [%s]' % (self
.get_nom(), self
.id)
367 nom_affichage
= self
.nom_affichage
368 if not nom_affichage
:
369 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
374 if self
.genre
.upper() == u
'M':
376 elif self
.genre
.upper() == u
'F':
381 """Retourne l'URL du service retournant la photo de l'Employe.
382 Équivalent reverse url 'rh_photo' avec id en param.
384 from django
.core
.urlresolvers
import reverse
385 return reverse('rh_photo', kwargs
={'id':self
.id})
387 def dossiers_passes(self
):
389 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
390 for d
in dossiers_passes
:
392 return dossiers_passes
394 def dossiers_futurs(self
):
396 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
398 def dossiers_encours(self
):
399 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
400 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
401 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
403 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
404 for d
in dossiers_encours
:
405 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
406 return dossiers_encours
408 def postes_encours(self
):
409 postes_encours
= set()
410 for d
in self
.dossiers_encours():
411 postes_encours
.add(d
.poste
)
412 return postes_encours
414 def poste_principal(self
):
416 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
418 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
420 poste
= Poste
.objects
.none()
422 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
427 prefix_implantation
= "dossiers__poste__implantation__region"
428 def get_regions(self
):
430 for d
in self
.dossiers
.all():
431 regions
.append(d
.poste
.implantation
.region
)
435 class EmployePiece(models
.Model
):
436 """Documents relatifs à un employé.
439 employe
= models
.ForeignKey('Employe', db_column
='employe')
440 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
441 fichier
= models
.FileField(verbose_name
="Fichier",
442 upload_to
=employe_piece_dispatch
,
443 storage
=storage_prive
)
447 verbose_name
= u
"Employé pièce"
448 verbose_name_plural
= u
"Employé pièces"
450 def __unicode__(self
):
451 return u
'%s' % (self
.nom
)
453 class EmployeCommentaire(Commentaire
):
454 employe
= models
.ForeignKey('Employe', db_column
='employe',
458 verbose_name
= u
"Employé commentaire"
459 verbose_name_plural
= u
"Employé commentaires"
462 LIEN_PARENTE_CHOICES
= (
463 ('Conjoint', 'Conjoint'),
464 ('Conjointe', 'Conjointe'),
469 class AyantDroit(AUFMetadata
):
470 """Personne en relation avec un Employe.
473 nom
= models
.CharField(max_length
=255)
474 prenom
= models
.CharField(max_length
=255,
475 verbose_name
= u
"Prénom",)
476 nom_affichage
= models
.CharField(max_length
=255,
477 verbose_name
= u
"Nom d'affichage",
478 null
=True, blank
=True)
479 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
480 db_column
='nationalite',
481 related_name
='ayantdroits_nationalite',
482 verbose_name
= u
"Nationalité",
483 null
=True, blank
=True)
484 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
485 validators
=[validate_date_passee
],
486 null
=True, blank
=True)
487 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
490 employe
= models
.ForeignKey('Employe', db_column
='employe',
491 related_name
='ayantdroits',
492 verbose_name
= u
"Employé")
493 lien_parente
= models
.CharField(max_length
=10,
494 choices
=LIEN_PARENTE_CHOICES
,
495 verbose_name
= u
"Lien de parenté",
496 null
=True, blank
=True)
499 ordering
= ['nom_affichage']
500 verbose_name
= u
"Ayant droit"
501 verbose_name_plural
= u
"Ayants droit"
503 def __unicode__(self
):
504 return u
'%s' % (self
.get_nom())
507 nom_affichage
= self
.nom_affichage
508 if not nom_affichage
:
509 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
512 prefix_implantation
= "employe__dossiers__poste__implantation__region"
513 def get_regions(self
):
515 for d
in self
.employe
.dossiers
.all():
516 regions
.append(d
.poste
.implantation
.region
)
520 class AyantDroitCommentaire(Commentaire
):
521 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
527 STATUT_RESIDENCE_CHOICES
= (
529 ('expat', 'Expatrié'),
532 COMPTE_COMPTA_CHOICES
= (
538 class Dossier_(AUFMetadata
):
539 """Le Dossier regroupe les informations relatives à l'occupation
540 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
543 Plusieurs Contrats peuvent être associés au Dossier.
544 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
545 lequel aucun Dossier n'existe est un poste vacant.
548 objects
= DossierManager()
551 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
553 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
554 db_column
='organisme_bstg',
556 verbose_name
= u
"Organisme",
557 help_text
="Si détaché (DET) ou \
558 mis à disposition (MAD), \
559 préciser l'organisme.",
560 null
=True, blank
=True)
563 remplacement
= models
.BooleanField(default
=False)
564 remplacement_de
= models
.ForeignKey('self', related_name
='+',
565 null
=True, blank
=True)
566 statut_residence
= models
.CharField(max_length
=10, default
='local',
567 verbose_name
= u
"Statut", null
=True,
568 choices
=STATUT_RESIDENCE_CHOICES
)
571 classement
= models
.ForeignKey('Classement', db_column
='classement',
573 null
=True, blank
=True)
574 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
576 default
=REGIME_TRAVAIL_DEFAULT
,
577 verbose_name
= u
"Régime de travail",
578 help_text
="% du temps complet")
579 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
580 decimal_places
=2, null
=True,
581 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
582 verbose_name
= u
"Nb. heures par semaine")
584 # Occupation du Poste par cet Employe (anciennement "mandat")
585 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
587 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
589 null
=True, blank
=True)
596 ordering
= ['employe__nom', ]
597 verbose_name
= u
"Dossier"
598 verbose_name_plural
= "Dossiers"
600 def salaire_theorique(self
):
601 annee
= date
.today().year
602 coeff
= self
.classement
.coefficient
603 implantation
= self
.poste
.implantation
604 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
606 montant
= coeff
* point
.valeur
607 devise
= point
.devise
608 return {'montant':montant
, 'devise':devise
}
610 def __unicode__(self
):
611 poste
= self
.poste
.nom
612 if self
.employe
.genre
== 'F':
613 poste
= self
.poste
.nom_feminin
614 return u
'%s - %s' % (self
.employe
, poste
)
616 prefix_implantation
= "poste__implantation__region"
617 def get_regions(self
):
618 return [self
.poste
.implantation
.region
]
621 def remunerations(self
):
622 return self
.rh_remunerations
.all().order_by('date_debut')
624 def get_salaire(self
):
626 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
630 class Dossier(Dossier_
):
631 __doc__
= Dossier_
.__doc__
632 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_dossiers')
633 employe
= models
.ForeignKey('Employe', db_column
='employe',
634 related_name
='%(app_label)s_dossiers',
635 verbose_name
=u
"Employé")
638 class DossierPiece_(models
.Model
):
639 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
640 Ex.: Lettre de motivation.
642 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
643 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
644 fichier
= models
.FileField(verbose_name
= u
"Fichier",
645 upload_to
=dossier_piece_dispatch
,
646 storage
=storage_prive
)
652 def __unicode__(self
):
653 return u
'%s' % (self
.nom
)
655 class DossierPiece(DossierPiece_
):
658 class DossierCommentaire_(Commentaire
):
659 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
663 class DossierCommentaire(DossierCommentaire_
):
666 class DossierComparaison_(models
.Model
):
668 Photo d'une comparaison salariale au moment de l'embauche.
670 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), related_name
='%(app_label)s_comparaisons')
671 objects
= DossierComparaisonManager()
673 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
674 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
675 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
676 montant
= models
.IntegerField(null
=True)
677 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
682 def taux_devise(self
):
683 annee
= self
.dossier
.poste
.date_debut
.year
684 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
687 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
))
691 def montant_euros(self
):
692 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
694 class DossierComparaison(DossierComparaison_
):
699 class RemunerationMixin(AUFMetadata
):
700 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_remunerations')
702 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
704 verbose_name
= u
"Type de rémunération")
705 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
706 db_column
='type_revalorisation',
708 verbose_name
= u
"Type de revalorisation",
709 null
=True, blank
=True)
710 montant
= models
.FloatField(null
=True, blank
=True,
712 # Annuel (12 mois, 52 semaines, 364 jours?)
713 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+', default
=5)
714 # commentaire = precision
715 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
716 # date_debut = anciennement date_effectif
717 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
718 null
=True, blank
=True)
719 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
720 null
=True, blank
=True)
724 ordering
= ['type__nom', '-date_fin']
726 def __unicode__(self
):
727 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
729 class Remuneration_(RemunerationMixin
):
730 """Structure de rémunération (données budgétaires) en situation normale
731 pour un Dossier. Si un Evenement existe, utiliser la structure de
732 rémunération EvenementRemuneration de cet événement.
735 def montant_mois(self
):
736 return round(self
.montant
/ 12, 2)
738 def taux_devise(self
):
739 if self
.devise
.code
== "EUR":
742 annee
= datetime
.datetime
.now().year
743 if self
.date_debut
is not None:
744 annee
= self
.date_debut
.year
745 if self
.dossier
.poste
.date_debut
is not None:
746 annee
= self
.dossier
.poste
.date_debut
.year
748 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise_id
, annee
=annee
)]
751 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
))
755 def montant_euro(self
):
756 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
758 def montant_euro_mois(self
):
759 return round(self
.montant_euro() / 12, 2)
761 def __unicode__(self
):
763 devise
= self
.devise
.code
766 return "%s %s" % (self
.montant
, devise
)
770 verbose_name
= u
"Rémunération"
771 verbose_name_plural
= u
"Rémunérations"
774 class Remuneration(Remuneration_
):
780 class ContratManager(NoDeleteManager
):
781 def get_query_set(self
):
782 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
785 class Contrat_(AUFMetadata
):
786 """Document juridique qui encadre la relation de travail d'un Employe
787 pour un Poste particulier. Pour un Dossier (qui documente cette
788 relation de travail) plusieurs contrats peuvent être associés.
790 objects
= ContratManager()
791 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_contrats')
792 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
794 verbose_name
= u
"Type de contrat")
795 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
796 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
797 null
=True, blank
=True)
801 ordering
= ['dossier__employe__nom_affichage']
802 verbose_name
= u
"Contrat"
803 verbose_name_plural
= u
"Contrats"
805 def __unicode__(self
):
806 return u
'%s - %s' % (self
.dossier
, self
.id)
808 class Contrat(Contrat_
):
814 #class Evenement_(AUFMetadata):
815 # """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
816 # d'un Dossier qui vient altérer des informations normales liées à un Dossier
817 # (ex.: la Remuneration).
819 # Ex.: congé de maternité, maladie...
821 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
822 # différent et une rémunération en conséquence. On souhaite toutefois
823 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
824 # du retour à la normale.
826 # dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
828 # nom = models.CharField(max_length=255)
829 # date_debut = models.DateField(verbose_name = u"Date de début")
830 # date_fin = models.DateField(verbose_name = u"Date de fin",
831 # null=True, blank=True)
836 # verbose_name = u"Évènement"
837 # verbose_name_plural = u"Évènements"
839 # def __unicode__(self):
840 # return u'%s' % (self.nom)
843 #class Evenement(Evenement_):
844 # __doc__ = Evenement_.__doc__
847 #class EvenementRemuneration_(RemunerationMixin):
848 # """Structure de rémunération liée à un Evenement qui remplace
849 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
852 # evenement = models.ForeignKey("Evenement", db_column='evenement',
854 # verbose_name = u"Évènement")
855 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
856 # # de l'Evenement associé
860 # ordering = ['evenement', 'type__nom', '-date_fin']
861 # verbose_name = u"Évènement - rémunération"
862 # verbose_name_plural = u"Évènements - rémunérations"
865 #class EvenementRemuneration(EvenementRemuneration_):
866 # __doc__ = EvenementRemuneration_.__doc__
872 #class EvenementRemuneration(EvenementRemuneration_):
873 # __doc__ = EvenementRemuneration_.__doc__
874 # TODO? class ContratPiece(models.Model):
879 class FamilleEmploi(AUFMetadata
):
880 """Catégorie utilisée dans la gestion des Postes.
881 Catégorie supérieure à TypePoste.
883 nom
= models
.CharField(max_length
=255)
887 verbose_name
= u
"Famille d'emploi"
888 verbose_name_plural
= u
"Familles d'emploi"
890 def __unicode__(self
):
891 return u
'%s' % (self
.nom
)
893 class TypePoste(AUFMetadata
):
894 """Catégorie de Poste.
896 nom
= models
.CharField(max_length
=255)
897 nom_feminin
= models
.CharField(max_length
=255,
898 verbose_name
= u
"Nom féminin")
900 is_responsable
= models
.BooleanField(default
=False,
901 verbose_name
= u
"Poste de responsabilité")
902 famille_emploi
= models
.ForeignKey('FamilleEmploi',
903 db_column
='famille_emploi',
905 verbose_name
= u
"Famille d'emploi")
909 verbose_name
= u
"Type de poste"
910 verbose_name_plural
= u
"Types de poste"
912 def __unicode__(self
):
913 return u
'%s' % (self
.nom
)
916 TYPE_PAIEMENT_CHOICES
= (
917 ('Régulier', 'Régulier'),
918 ('Ponctuel', 'Ponctuel'),
921 NATURE_REMUNERATION_CHOICES
= (
922 ('Accessoire', 'Accessoire'),
923 ('Charges', 'Charges'),
924 ('Indemnité', 'Indemnité'),
925 ('RAS', 'Rémunération autre source'),
926 ('Traitement', 'Traitement'),
929 class TypeRemuneration(AUFMetadata
):
930 """Catégorie de Remuneration.
932 nom
= models
.CharField(max_length
=255)
933 type_paiement
= models
.CharField(max_length
=30,
934 choices
=TYPE_PAIEMENT_CHOICES
,
935 verbose_name
= u
"Type de paiement")
936 nature_remuneration
= models
.CharField(max_length
=30,
937 choices
=NATURE_REMUNERATION_CHOICES
,
938 verbose_name
= u
"Nature de la rémunération")
942 verbose_name
= u
"Type de rémunération"
943 verbose_name_plural
= u
"Types de rémunération"
945 def __unicode__(self
):
946 return u
'%s' % (self
.nom
)
948 class TypeRevalorisation(AUFMetadata
):
949 """Justification du changement de la Remuneration.
950 (Actuellement utilisé dans aucun traitement informatique.)
952 nom
= models
.CharField(max_length
=255)
956 verbose_name
= u
"Type de revalorisation"
957 verbose_name_plural
= u
"Types de revalorisation"
959 def __unicode__(self
):
960 return u
'%s' % (self
.nom
)
962 class Service(AUFMetadata
):
963 """Unité administrative où les Postes sont rattachés.
965 nom
= models
.CharField(max_length
=255)
969 verbose_name
= u
"Service"
970 verbose_name_plural
= u
"Services"
972 def __unicode__(self
):
973 return u
'%s' % (self
.nom
)
976 TYPE_ORGANISME_CHOICES
= (
977 ('MAD', 'Mise à disposition'),
978 ('DET', 'Détachement'),
981 class OrganismeBstg(AUFMetadata
):
982 """Organisation d'où provient un Employe mis à disposition (MAD) de
983 ou détaché (DET) à l'AUF à titre gratuit.
985 (BSTG = bien et service à titre gratuit.)
987 nom
= models
.CharField(max_length
=255)
988 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
989 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
991 related_name
='organismes_bstg',
992 null
=True, blank
=True)
995 ordering
= ['type', 'nom']
996 verbose_name
= u
"Organisme BSTG"
997 verbose_name_plural
= u
"Organismes BSTG"
999 def __unicode__(self
):
1000 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1002 prefix_implantation
= "pays__region"
1003 def get_regions(self
):
1004 return [self
.pays
.region
]
1007 class Statut(AUFMetadata
):
1008 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1011 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.")
1012 nom
= models
.CharField(max_length
=255)
1016 verbose_name
= u
"Statut d'employé"
1017 verbose_name_plural
= u
"Statuts d'employé"
1019 def __unicode__(self
):
1020 return u
'%s : %s' % (self
.code
, self
.nom
)
1023 TYPE_CLASSEMENT_CHOICES
= (
1024 ('S', 'S -Soutien'),
1025 ('T', 'T - Technicien'),
1026 ('P', 'P - Professionel'),
1028 ('D', 'D - Direction'),
1029 ('SO', 'SO - Sans objet [expatriés]'),
1030 ('HG', 'HG - Hors grille [direction]'),
1034 class Classement_(AUFMetadata
):
1035 """Éléments de classement de la
1036 "Grille générique de classement hiérarchique".
1038 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1039 classement dans la grille. Le classement donne le coefficient utilisé dans:
1041 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1044 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1045 echelon
= models
.IntegerField(verbose_name
=u
"Échelon", blank
=True, default
=0)
1046 degre
= models
.IntegerField(verbose_name
=u
"Degré", blank
=True, default
=0)
1047 coefficient
= models
.FloatField(default
=0, verbose_name
=u
"Coefficient",
1050 # annee # au lieu de date_debut et date_fin
1051 commentaire
= models
.TextField(null
=True, blank
=True)
1055 ordering
= ['type','echelon','degre','coefficient']
1056 verbose_name
= u
"Classement"
1057 verbose_name_plural
= u
"Classements"
1059 def __unicode__(self
):
1060 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1063 class Classement(Classement_
):
1064 __doc__
= Classement_
.__doc__
1067 class TauxChange_(AUFMetadata
):
1068 """Taux de change de la devise vers l'euro (EUR)
1069 pour chaque année budgétaire.
1072 devise
= models
.ForeignKey('Devise', db_column
='devise')
1073 annee
= models
.IntegerField(verbose_name
= u
"Année")
1074 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1078 ordering
= ['-annee', 'devise__code']
1079 verbose_name
= u
"Taux de change"
1080 verbose_name_plural
= u
"Taux de change"
1082 def __unicode__(self
):
1083 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1086 class TauxChange(TauxChange_
):
1087 __doc__
= TauxChange_
.__doc__
1089 class ValeurPointManager(NoDeleteManager
):
1090 def get_query_set(self
):
1091 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1094 class ValeurPoint_(AUFMetadata
):
1095 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1096 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1097 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1099 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1102 actuelles
= ValeurPointManager()
1104 valeur
= models
.FloatField(null
=True)
1105 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1106 related_name
='+', default
=5)
1107 implantation
= models
.ForeignKey(ref
.Implantation
,
1108 db_column
='implantation',
1109 related_name
='%(app_label)s_valeur_point')
1111 annee
= models
.IntegerField()
1114 ordering
= ['-annee', 'implantation__nom']
1116 verbose_name
= u
"Valeur du point"
1117 verbose_name_plural
= u
"Valeurs du point"
1119 def __unicode__(self
):
1120 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1123 class ValeurPoint(ValeurPoint_
):
1124 __doc__
= ValeurPoint_
.__doc__
1127 class Devise(AUFMetadata
):
1128 """Devise monétaire.
1130 code
= models
.CharField(max_length
=10, unique
=True)
1131 nom
= models
.CharField(max_length
=255)
1135 verbose_name
= u
"Devise"
1136 verbose_name_plural
= u
"Devises"
1138 def __unicode__(self
):
1139 return u
'%s - %s' % (self
.code
, self
.nom
)
1141 class TypeContrat(AUFMetadata
):
1144 nom
= models
.CharField(max_length
=255)
1145 nom_long
= models
.CharField(max_length
=255)
1149 verbose_name
= u
"Type de contrat"
1150 verbose_name_plural
= u
"Types de contrat"
1152 def __unicode__(self
):
1153 return u
'%s' % (self
.nom
)
1158 class ResponsableImplantation(AUFMetadata
):
1159 """Le responsable d'une implantation.
1160 Anciennement géré sur le Dossier du responsable.
1162 employe
= models
.ForeignKey('Employe', db_column
='employe',
1164 null
=True, blank
=True)
1165 implantation
= models
.ForeignKey(ref
.Implantation
,
1166 db_column
='implantation', related_name
='+',
1169 def __unicode__(self
):
1170 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1173 ordering
= ['implantation__nom']
1174 verbose_name
= "Responsable d'implantation"
1175 verbose_name_plural
= "Responsables d'implantation"