1 # -=- encoding: utf-8 -=-
3 from datetime
import date
5 from django
.core
.files
.storage
import FileSystemStorage
6 from django
.db
import models
7 from django
.conf
import settings
9 from auf
.django
.metadata
.models
import AUFMetadata
10 from auf
.django
.metadata
.managers
import NoDeleteManager
11 import datamaster_modeles
.models
as ref
12 from validators
import validate_date_passee
15 REGIME_TRAVAIL_DEFAULT
= 100.00
16 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= 35.00
20 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
21 base_url
=settings
.PRIVE_MEDIA_URL
)
23 def poste_piece_dispatch(instance
, filename
):
24 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
27 def dossier_piece_dispatch(instance
, filename
):
28 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
31 def employe_piece_dispatch(instance
, filename
):
32 path
= "employe/%s/%s" % (instance
.employe_id
, filename
)
36 class Commentaire(AUFMetadata
):
37 texte
= models
.TextField()
38 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+')
42 ordering
= ['-date_creation']
44 def __unicode__(self
):
45 return u
'%s' % (self
.texte
)
50 POSTE_APPEL_CHOICES
= (
51 ('interne', 'Interne'),
52 ('externe', 'Externe'),
55 class PosteManager(NoDeleteManager
):
56 def get_query_set(self
):
57 return super(PosteManager
, self
).get_query_set().select_related('implantation')
59 class Poste_(AUFMetadata
):
60 """Un Poste est un emploi (job) à combler dans une implantation.
61 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
62 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
65 objects
= PosteManager()
68 nom
= models
.CharField(max_length
=255,
69 verbose_name
= u
"Titre du poste", )
70 nom_feminin
= models
.CharField(max_length
=255,
71 verbose_name
= u
"Titre du poste (au féminin)",
73 implantation
= models
.ForeignKey(ref
.Implantation
,
74 db_column
='implantation', related_name
='+')
75 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste',
78 service
= models
.ForeignKey('Service', db_column
='service', null
=True,
80 verbose_name
= u
"Direction/Service/Pôle support",
81 default
=1) # default = Rectorat
82 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
83 related_name
='+', null
=True,
84 verbose_name
= u
"Poste du responsable",
85 default
=149) # default = Recteur
88 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
89 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
90 verbose_name
= u
"Temps de travail",
91 help_text
="% du temps complet")
92 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
93 decimal_places
=2, null
=True,
94 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
95 verbose_name
= u
"Nb. heures par semaine")
98 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
99 null
=True, blank
=True)
100 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
101 null
=True, blank
=True)
102 mise_a_disposition
= models
.NullBooleanField(
103 verbose_name
= u
"Mise à disposition",
104 null
=True, default
=False)
105 appel
= models
.CharField(max_length
=10, null
=True,
106 verbose_name
= u
"Appel à candidature",
107 choices
=POSTE_APPEL_CHOICES
,
111 classement_min
= models
.ForeignKey('Classement',
112 db_column
='classement_min', related_name
='+',
113 null
=True, blank
=True)
114 classement_max
= models
.ForeignKey('Classement',
115 db_column
='classement_max', related_name
='+',
116 null
=True, blank
=True)
117 valeur_point_min
= models
.ForeignKey('ValeurPoint',
118 db_column
='valeur_point_min', related_name
='+',
119 null
=True, blank
=True)
120 valeur_point_max
= models
.ForeignKey('ValeurPoint',
121 db_column
='valeur_point_max', related_name
='+',
122 null
=True, blank
=True)
123 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
124 related_name
='+', default
=5)
125 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
126 related_name
='+', default
=5)
127 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
128 null
=True, default
=0)
129 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
130 null
=True, default
=0)
131 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
132 null
=True, default
=0)
133 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
134 null
=True, default
=0)
135 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
136 null
=True, default
=0)
137 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
138 null
=True, default
=0)
140 # Comparatifs de rémunération
141 devise_comparaison
= models
.ForeignKey('Devise', null
=True,
142 db_column
='devise_comparaison',
145 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
146 null
=True, blank
=True)
147 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
148 null
=True, blank
=True)
149 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
150 null
=True, blank
=True)
151 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
152 null
=True, blank
=True)
153 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
154 null
=True, blank
=True)
155 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
156 null
=True, blank
=True)
157 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
158 null
=True, blank
=True)
159 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
160 null
=True, blank
=True)
161 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
162 null
=True, blank
=True)
163 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
164 null
=True, blank
=True)
167 justification
= models
.TextField(null
=True, blank
=True)
170 date_validation
= models
.DateTimeField(null
=True, blank
=True) # de dae
171 date_debut
= models
.DateField(verbose_name
=u
"Date de début",
172 null
=True, blank
=True)
173 date_fin
= models
.DateField(verbose_name
=u
"Date de fin",
174 null
=True, blank
=True)
178 ordering
= ['implantation__nom', 'nom']
179 verbose_name
= u
"Poste"
180 verbose_name_plural
= u
"Postes"
182 def __unicode__(self
):
183 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
186 representation
= representation
+ u
' (VACANT)'
187 return representation
191 if self
.occupe_par():
195 def occupe_par(self
):
196 """Retourne la liste d'employé occupant ce poste.
197 Généralement, retourne une liste d'un élément.
198 Si poste inoccupé, retourne liste vide.
200 return [d
.employe
for d
in self
.dossiers
.filter(actif
=True, supprime
=False) \
201 .exclude(date_fin__lt
=date
.today())]
203 prefix_implantation
= "implantation__region"
204 def get_regions(self
):
205 return [self
.implantation
.region
]
209 __doc__
= Poste_
.__doc__
213 __doc__
= Poste_
.__doc__
216 POSTE_FINANCEMENT_CHOICES
= (
217 ('A', 'A - Frais de personnel'),
218 ('B', 'B - Projet(s)-Titre(s)'),
223 class PosteFinancement_(models
.Model
):
224 """Pour un Poste, structure d'informations décrivant comment on prévoit
227 poste
= models
.ForeignKey('Poste', db_column
='poste',
228 related_name
='%(app_label)s_financements')
229 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
230 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
231 help_text
="ex.: 33.33 % (décimale avec point)")
232 commentaire
= models
.TextField(
233 help_text
="Spécifiez la source de financement.")
239 def __unicode__(self
):
240 return u
'%s : %s %' % (self
.type, self
.pourcentage
)
243 class PosteFinancement(PosteFinancement_
):
244 __doc__
= PosteFinancement_
.__doc__
247 class PostePiece(models
.Model
):
248 """Documents relatifs au Poste.
249 Ex.: Description de poste
251 poste
= models
.ForeignKey('Poste', db_column
='poste',
252 related_name
='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
)
261 def __unicode__(self
):
262 return u
'%s' % (self
.nom
)
264 class PosteComparaison(models
.Model
):
266 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
268 poste
= models
.ForeignKey('Poste', related_name
='comparaisons_internes')
269 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
270 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
271 montant
= models
.IntegerField(null
=True)
272 devise
= models
.ForeignKey("Devise", default
=5, related_name
='+', null
=True, blank
=True)
274 def taux_devise(self
):
275 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.implantation
)
276 if len(liste_taux
) == 0:
277 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.implantation
))
279 return liste_taux
[0].taux
281 def montant_euros(self
):
282 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
285 class PosteCommentaire(Commentaire
):
286 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='+')
295 SITUATION_CHOICES
= (
296 ('C', 'Célibataire'),
301 class Employe(AUFMetadata
):
302 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
303 Dossiers qu'il occupe ou a occupé de Postes.
305 Cette classe aurait pu avantageusement s'appeler Personne car la notion
306 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
309 nom
= models
.CharField(max_length
=255)
310 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
311 nom_affichage
= models
.CharField(max_length
=255,
312 verbose_name
= u
"Nom d'affichage",
313 null
=True, blank
=True)
314 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
315 db_column
='nationalite',
316 related_name
='employes_nationalite',
317 verbose_name
= u
"Nationalité")
318 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
319 validators
=[validate_date_passee
],
320 null
=True, blank
=True)
321 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
324 situation_famille
= models
.CharField(max_length
=1,
325 choices
=SITUATION_CHOICES
,
326 verbose_name
= u
"Situation familiale",
327 null
=True, blank
=True)
328 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
329 null
=True, blank
=True)
332 tel_domicile
= models
.CharField(max_length
=255,
333 verbose_name
= u
"Tél. domicile",
334 null
=True, blank
=True)
335 tel_cellulaire
= models
.CharField(max_length
=255,
336 verbose_name
= u
"Tél. cellulaire",
337 null
=True, blank
=True)
338 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
339 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
340 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
341 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
342 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
343 related_name
='employes',
344 null
=True, blank
=True)
347 ordering
= ['nom_affichage','nom','prenom']
348 verbose_name
= u
"Employé"
349 verbose_name_plural
= u
"Employés"
351 def __unicode__(self
):
352 return u
'%s [%s]' % (self
.get_nom(), self
.id)
355 nom_affichage
= self
.nom_affichage
356 if not nom_affichage
:
357 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
362 if self
.genre
.upper() == u
'M':
364 elif self
.genre
.upper() == u
'F':
369 """Retourne l'URL du service retournant la photo de l'Employe.
370 Équivalent reverse url 'rh_photo' avec id en param.
372 from django
.core
.urlresolvers
import reverse
373 return reverse('rh_photo', kwargs
={'id':self
.id})
375 def dossiers_passes(self
):
377 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
378 for d
in dossiers_passes
:
380 return dossiers_passes
382 def dossiers_futurs(self
):
384 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
386 def dossiers_encours(self
):
387 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
388 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
389 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
391 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
392 for d
in dossiers_encours
:
393 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
394 return dossiers_encours
396 def postes_encours(self
):
397 postes_encours
= set()
398 for d
in self
.dossiers_encours():
399 postes_encours
.add(d
.poste
)
400 return postes_encours
402 def poste_principal(self
):
404 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
406 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
408 poste
= Poste
.objects
.none()
410 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
415 prefix_implantation
= "dossiers__poste__implantation__region"
416 def get_regions(self
):
418 for d
in self
.dossiers
.all():
419 regions
.append(d
.poste
.implantation
.region
)
423 class EmployeInactif(Employe
):
426 ordering
= ['nom_affichage','nom','prenom']
427 verbose_name
= u
"Employé inactif"
428 verbose_name_plural
= u
"Employés inactifs"
431 class EmployePiece(models
.Model
):
432 """Documents relatifs à un employé.
435 employe
= models
.ForeignKey('Employe', db_column
='employe')
436 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
437 fichier
= models
.FileField(verbose_name
="Fichier",
438 upload_to
=employe_piece_dispatch
,
439 storage
=storage_prive
)
443 verbose_name
= u
"Employé pièce"
444 verbose_name_plural
= u
"Employé pièces"
446 def __unicode__(self
):
447 return u
'%s' % (self
.nom
)
449 class EmployeCommentaire(Commentaire
):
450 employe
= models
.ForeignKey('Employe', db_column
='employe',
454 verbose_name
= u
"Employé commentaire"
455 verbose_name_plural
= u
"Employé commentaires"
458 LIEN_PARENTE_CHOICES
= (
459 ('Conjoint', 'Conjoint'),
460 ('Conjointe', 'Conjointe'),
465 class AyantDroit(AUFMetadata
):
466 """Personne en relation avec un Employe.
469 nom
= models
.CharField(max_length
=255)
470 prenom
= models
.CharField(max_length
=255,
471 verbose_name
= u
"Prénom",)
472 nom_affichage
= models
.CharField(max_length
=255,
473 verbose_name
= u
"Nom d'affichage",
474 null
=True, blank
=True)
475 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
476 db_column
='nationalite',
477 related_name
='ayantdroits_nationalite',
478 verbose_name
= u
"Nationalité")
479 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
480 validators
=[validate_date_passee
],
481 null
=True, blank
=True)
482 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
485 employe
= models
.ForeignKey('Employe', db_column
='employe',
486 related_name
='ayantdroits',
487 verbose_name
= u
"Employé")
488 lien_parente
= models
.CharField(max_length
=10,
489 choices
=LIEN_PARENTE_CHOICES
,
490 verbose_name
= u
"Lien de parenté",
491 null
=True, blank
=True)
494 ordering
= ['nom_affichage']
495 verbose_name
= u
"Ayant droit"
496 verbose_name_plural
= u
"Ayants droit"
498 def __unicode__(self
):
499 return u
'%s' % (self
.get_nom())
502 nom_affichage
= self
.nom_affichage
503 if not nom_affichage
:
504 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
507 prefix_implantation
= "employe__dossiers__poste__implantation__region"
508 def get_regions(self
):
510 for d
in self
.employe
.dossiers
.all():
511 regions
.append(d
.poste
.implantation
.region
)
515 class AyantDroitCommentaire(Commentaire
):
516 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
522 STATUT_RESIDENCE_CHOICES
= (
524 ('expat', 'Expatrié'),
527 COMPTE_COMPTA_CHOICES
= (
533 class Dossier_(AUFMetadata
):
534 """Le Dossier regroupe les informations relatives à l'occupation
535 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
538 Plusieurs Contrats peuvent être associés au Dossier.
539 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
540 lequel aucun Dossier n'existe est un poste vacant.
543 employe
= models
.ForeignKey('Employe', db_column
='employe',
544 related_name
='dossiers',
545 verbose_name
=u
"Employé")
547 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='dossiers')
548 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
550 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
551 db_column
='organisme_bstg',
553 verbose_name
= u
"Organisme",
554 help_text
="Si détaché (DET) ou \
555 mis à disposition (MAD), \
556 préciser l'organisme.",
557 null
=True, blank
=True)
560 remplacement
= models
.BooleanField(default
=False)
561 remplacement_de
= models
.ForeignKey('self', related_name
='+',
562 null
=True, blank
=True)
563 statut_residence
= models
.CharField(max_length
=10, default
='local',
564 verbose_name
= u
"Statut", null
=True,
565 choices
=STATUT_RESIDENCE_CHOICES
)
568 classement
= models
.ForeignKey('Classement', db_column
='classement',
570 null
=True, blank
=True)
571 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
573 default
=REGIME_TRAVAIL_DEFAULT
,
574 verbose_name
= u
"Régime de travail",
575 help_text
="% du temps complet")
576 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
577 decimal_places
=2, null
=True,
578 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
579 verbose_name
= u
"Nb. heures par semaine")
581 # Occupation du Poste par cet Employe (anciennement "mandat")
582 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
584 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
586 null
=True, blank
=True)
593 ordering
= ['employe__nom', ]
594 verbose_name
= u
"Dossier"
595 verbose_name_plural
= "Dossiers"
597 def salaire_theorique(self
):
598 annee
= date
.today().year
599 coeff
= self
.classement
.coefficient
600 implantation
= self
.poste
.implantation
601 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
603 montant
= coeff
* point
.valeur
604 devise
= point
.devise
605 return {'montant':montant
, 'devise':devise
}
607 def __unicode__(self
):
608 poste
= self
.poste
.nom
609 if self
.employe
.genre
== 'F':
610 poste
= self
.poste
.nom_feminin
611 return u
'%s - %s' % (self
.employe
, poste
)
613 prefix_implantation
= "poste__implantation__region"
614 def get_regions(self
):
615 return [self
.poste
.implantation
.region
]
618 class Dossier(Dossier_
):
619 __doc__
= Dossier_
.__doc__
622 class DossierInactif(Dossier
):
626 ordering
= ['employe__nom', ]
627 verbose_name
= u
"Dossier inactif"
628 verbose_name_plural
= u
"Dossiers inactifs"
631 class DossierPiece(models
.Model
):
632 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
633 Ex.: Lettre de motivation.
635 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
637 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
638 fichier
= models
.FileField(verbose_name
= u
"Fichier",
639 upload_to
=dossier_piece_dispatch
,
640 storage
=storage_prive
)
645 def __unicode__(self
):
646 return u
'%s' % (self
.nom
)
648 class DossierCommentaire(Commentaire
):
649 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
652 class DossierComparaison(models
.Model
):
654 Photo d'une comparaison salariale au moment de l'embauche.
656 dossier
= models
.ForeignKey('Dossier', related_name
='comparaisons')
657 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
658 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
659 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
660 montant
= models
.IntegerField(null
=True)
661 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
663 def taux_devise(self
):
664 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.dossier
.poste
.implantation
)
665 if len(liste_taux
) == 0:
666 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.dossier
.poste
.implantation
))
668 return liste_taux
[0].taux
670 def montant_euros(self
):
671 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
676 class RemunerationMixin(AUFMetadata
):
678 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
679 related_name
='%(app_label)s_%(class)s_remunerations')
680 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
682 verbose_name
= u
"Type de rémunération")
683 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
684 db_column
='type_revalorisation',
686 verbose_name
= u
"Type de revalorisation",
687 null
=True, blank
=True)
688 montant
= models
.FloatField(null
=True, blank
=True,
690 # Annuel (12 mois, 52 semaines, 364 jours?)
691 devise
= models
.ForeignKey('Devise', to_field
='id',
692 db_column
='devise', related_name
='+',
694 # commentaire = precision
695 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
696 # date_debut = anciennement date_effectif
697 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
698 null
=True, blank
=True)
699 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
700 null
=True, blank
=True)
704 ordering
= ['type__nom', '-date_fin']
706 def __unicode__(self
):
707 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
709 class Remuneration_(RemunerationMixin
):
710 """Structure de rémunération (données budgétaires) en situation normale
711 pour un Dossier. Si un Evenement existe, utiliser la structure de
712 rémunération EvenementRemuneration de cet événement.
715 def montant_mois(self
):
716 return round(self
.montant
/ 12, 2)
718 def taux_devise(self
):
719 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
721 def montant_euro(self
):
722 return round(float(self
.montant
) / float(self
.taux_devise()), 2)
724 def montant_euro_mois(self
):
725 return round(self
.montant_euro() / 12, 2)
727 def __unicode__(self
):
729 devise
= self
.devise
.code
732 return "%s %s" % (self
.montant
, devise
)
736 verbose_name
= u
"Rémunération"
737 verbose_name_plural
= u
"Rémunérations"
740 class Remuneration(Remuneration_
):
741 __doc__
= Remuneration_
.__doc__
746 class ContratManager(NoDeleteManager
):
747 def get_query_set(self
):
748 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
751 class Contrat(AUFMetadata
):
752 """Document juridique qui encadre la relation de travail d'un Employe
753 pour un Poste particulier. Pour un Dossier (qui documente cette
754 relation de travail) plusieurs contrats peuvent être associés.
757 objects
= ContratManager()
759 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
760 related_name
='contrats')
761 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
763 verbose_name
= u
"Type de contrat")
764 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
765 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
766 null
=True, blank
=True)
769 ordering
= ['dossier__employe__nom_affichage']
770 verbose_name
= u
"Contrat"
771 verbose_name_plural
= u
"Contrats"
773 def __unicode__(self
):
774 return u
'%s - %s' % (self
.dossier
, self
.id)
776 # TODO? class ContratPiece(models.Model):
781 class Evenement_(AUFMetadata
):
782 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
783 d'un Dossier qui vient altérer des informations normales liées à un Dossier
784 (ex.: la Remuneration).
786 Ex.: congé de maternité, maladie...
788 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
789 différent et une rémunération en conséquence. On souhaite toutefois
790 conserver le Dossier intact afin d'éviter une re-saisie des données lors
791 du retour à la normale.
793 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
795 nom
= models
.CharField(max_length
=255)
796 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
797 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
798 null
=True, blank
=True)
803 verbose_name
= u
"Évènement"
804 verbose_name_plural
= u
"Évènements"
806 def __unicode__(self
):
807 return u
'%s' % (self
.nom
)
810 class Evenement(Evenement_
):
811 __doc__
= Evenement_
.__doc__
814 class EvenementRemuneration_(RemunerationMixin
):
815 """Structure de rémunération liée à un Evenement qui remplace
816 temporairement la Remuneration normale d'un Dossier, pour toute la durée
819 evenement
= models
.ForeignKey("Evenement", db_column
='evenement',
821 verbose_name
= u
"Évènement")
822 # TODO : le champ dossier hérité de Remuneration doit être dérivé
823 # de l'Evenement associé
827 ordering
= ['evenement', 'type__nom', '-date_fin']
828 verbose_name
= u
"Évènement - rémunération"
829 verbose_name_plural
= u
"Évènements - rémunérations"
832 class EvenementRemuneration(EvenementRemuneration_
):
833 __doc__
= EvenementRemuneration_
.__doc__
839 class EvenementRemuneration(EvenementRemuneration_
):
840 __doc__
= EvenementRemuneration_
.__doc__
845 class FamilleEmploi(AUFMetadata
):
846 """Catégorie utilisée dans la gestion des Postes.
847 Catégorie supérieure à TypePoste.
849 nom
= models
.CharField(max_length
=255)
853 verbose_name
= u
"Famille d'emploi"
854 verbose_name_plural
= u
"Familles d'emploi"
856 def __unicode__(self
):
857 return u
'%s' % (self
.nom
)
859 class TypePoste(AUFMetadata
):
860 """Catégorie de Poste.
862 nom
= models
.CharField(max_length
=255)
863 nom_feminin
= models
.CharField(max_length
=255,
864 verbose_name
= u
"Nom féminin")
866 is_responsable
= models
.BooleanField(default
=False,
867 verbose_name
= u
"Poste de responsabilité")
868 famille_emploi
= models
.ForeignKey('FamilleEmploi',
869 db_column
='famille_emploi',
871 verbose_name
= u
"Famille d'emploi")
875 verbose_name
= u
"Type de poste"
876 verbose_name_plural
= u
"Types de poste"
878 def __unicode__(self
):
879 return u
'%s' % (self
.nom
)
882 TYPE_PAIEMENT_CHOICES
= (
883 ('Régulier', 'Régulier'),
884 ('Ponctuel', 'Ponctuel'),
887 NATURE_REMUNERATION_CHOICES
= (
888 ('Accessoire', 'Accessoire'),
889 ('Charges', 'Charges'),
890 ('Indemnité', 'Indemnité'),
891 ('RAS', 'Rémunération autre source'),
892 ('Traitement', 'Traitement'),
895 class TypeRemuneration(AUFMetadata
):
896 """Catégorie de Remuneration.
898 nom
= models
.CharField(max_length
=255)
899 type_paiement
= models
.CharField(max_length
=30,
900 choices
=TYPE_PAIEMENT_CHOICES
,
901 verbose_name
= u
"Type de paiement")
902 nature_remuneration
= models
.CharField(max_length
=30,
903 choices
=NATURE_REMUNERATION_CHOICES
,
904 verbose_name
= u
"Nature de la rémunération")
908 verbose_name
= u
"Type de rémunération"
909 verbose_name_plural
= u
"Types de rémunération"
911 def __unicode__(self
):
912 return u
'%s' % (self
.nom
)
914 class TypeRevalorisation(AUFMetadata
):
915 """Justification du changement de la Remuneration.
916 (Actuellement utilisé dans aucun traitement informatique.)
918 nom
= models
.CharField(max_length
=255)
922 verbose_name
= u
"Type de revalorisation"
923 verbose_name_plural
= u
"Types de revalorisation"
925 def __unicode__(self
):
926 return u
'%s' % (self
.nom
)
928 class Service(AUFMetadata
):
929 """Unité administrative où les Postes sont rattachés.
931 nom
= models
.CharField(max_length
=255)
935 verbose_name
= u
"Service"
936 verbose_name_plural
= u
"Services"
938 def __unicode__(self
):
939 return u
'%s' % (self
.nom
)
942 TYPE_ORGANISME_CHOICES
= (
943 ('MAD', 'Mise à disposition'),
944 ('DET', 'Détachement'),
947 class OrganismeBstg(AUFMetadata
):
948 """Organisation d'où provient un Employe mis à disposition (MAD) de
949 ou détaché (DET) à l'AUF à titre gratuit.
951 (BSTG = bien et service à titre gratuit.)
953 nom
= models
.CharField(max_length
=255)
954 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
955 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
957 related_name
='organismes_bstg',
958 null
=True, blank
=True)
961 ordering
= ['type', 'nom']
962 verbose_name
= u
"Organisme BSTG"
963 verbose_name_plural
= u
"Organismes BSTG"
965 def __unicode__(self
):
966 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
968 prefix_implantation
= "pays__region"
969 def get_regions(self
):
970 return [self
.pays
.region
]
973 class Statut(AUFMetadata
):
974 """Statut de l'Employe dans le cadre d'un Dossier particulier.
977 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.")
978 nom
= models
.CharField(max_length
=255)
982 verbose_name
= u
"Statut d'employé"
983 verbose_name_plural
= u
"Statuts d'employé"
985 def __unicode__(self
):
986 return u
'%s : %s' % (self
.code
, self
.nom
)
989 TYPE_CLASSEMENT_CHOICES
= (
991 ('T', 'T - Technicien'),
992 ('P', 'P - Professionel'),
994 ('D', 'D - Direction'),
995 ('SO', 'SO - Sans objet [expatriés]'),
996 ('HG', 'HG - Hors grille [direction]'),
1000 class Classement_(AUFMetadata
):
1001 """Éléments de classement de la
1002 "Grille générique de classement hiérarchique".
1004 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1005 classement dans la grille. Le classement donne le coefficient utilisé dans:
1007 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1010 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1011 echelon
= models
.IntegerField(verbose_name
= u
"Échelon")
1012 degre
= models
.IntegerField(verbose_name
= u
"Degré")
1013 coefficient
= models
.FloatField(default
=0, verbose_name
= u
"Coéfficient",
1016 # annee # au lieu de date_debut et date_fin
1017 commentaire
= models
.TextField(null
=True, blank
=True)
1021 ordering
= ['type','echelon','degre','coefficient']
1022 verbose_name
= u
"Classement"
1023 verbose_name_plural
= u
"Classements"
1025 def __unicode__(self
):
1026 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1029 class Classement(Classement_
):
1030 __doc__
= Classement_
.__doc__
1033 class TauxChange_(AUFMetadata
):
1034 """Taux de change de la devise vers l'euro (EUR)
1035 pour chaque année budgétaire.
1038 devise
= models
.ForeignKey('Devise', db_column
='devise')
1039 annee
= models
.IntegerField(verbose_name
= u
"Année")
1040 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1044 ordering
= ['-annee', 'devise__code']
1045 verbose_name
= u
"Taux de change"
1046 verbose_name_plural
= u
"Taux de change"
1048 def __unicode__(self
):
1049 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1052 class TauxChange(TauxChange_
):
1053 __doc__
= TauxChange_
.__doc__
1055 class ValeurPointManager(NoDeleteManager
):
1056 def get_query_set(self
):
1057 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1060 class ValeurPoint_(AUFMetadata
):
1061 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1062 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1063 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1065 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1068 objects
= ValeurPointManager()
1070 valeur
= models
.FloatField(null
=True)
1071 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1072 related_name
='+', default
=5)
1073 implantation
= models
.ForeignKey(ref
.Implantation
,
1074 db_column
='implantation',
1075 related_name
='%(app_label)s_valeur_point')
1077 annee
= models
.IntegerField()
1080 ordering
= ['-annee', 'implantation__nom']
1082 verbose_name
= u
"Valeur du point"
1083 verbose_name_plural
= u
"Valeurs du point"
1085 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
1086 def get_tauxchange_courant(self
):
1088 Recherche le taux courant associé à la valeur d'un point.
1089 Tous les taux de l'année courante sont chargés, pour optimiser un
1090 affichage en liste. (On pourrait probablement améliorer le manager pour
1091 lui greffer le taux courant sous forme de JOIN)
1093 for tauxchange
in self
.tauxchange
:
1094 if tauxchange
.implantation_id
== self
.implantation_id
:
1098 def __unicode__(self
):
1099 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1102 class ValeurPoint(ValeurPoint_
):
1103 __doc__
= ValeurPoint_
.__doc__
1106 class Devise(AUFMetadata
):
1107 """Devise monétaire.
1109 code
= models
.CharField(max_length
=10, unique
=True)
1110 nom
= models
.CharField(max_length
=255)
1114 verbose_name
= u
"Devise"
1115 verbose_name_plural
= u
"Devises"
1117 def __unicode__(self
):
1118 return u
'%s - %s' % (self
.code
, self
.nom
)
1120 class TypeContrat(AUFMetadata
):
1123 nom
= models
.CharField(max_length
=255)
1124 nom_long
= models
.CharField(max_length
=255)
1128 verbose_name
= u
"Type de contrat"
1129 verbose_name_plural
= u
"Types de contrat"
1131 def __unicode__(self
):
1132 return u
'%s' % (self
.nom
)
1137 class ResponsableImplantation(AUFMetadata
):
1138 """Le responsable d'une implantation.
1139 Anciennement géré sur le Dossier du responsable.
1141 employe
= models
.ForeignKey('Employe', db_column
='employe',
1143 null
=True, blank
=True)
1144 implantation
= models
.ForeignKey(ref
.Implantation
,
1145 db_column
='implantation', related_name
='+',
1148 def __unicode__(self
):
1149 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1152 ordering
= ['implantation__nom']
1153 verbose_name
= "Responsable d'implantation"
1154 verbose_name_plural
= "Responsables d'implantation"