1 # -=- encoding: utf-8 -=-
3 from django
.core
.files
.storage
import FileSystemStorage
4 from django
.db
import models
5 from django
.conf
import settings
6 from auf
.django
.metadata
.models
import AUFMetadata
7 from auf
.django
.metadata
.managers
import NoDeleteManager
8 import datamaster_modeles
.models
as ref
9 from validators
import validate_date_passee
12 HELP_TEXT_DATE
= "format: aaaa-mm-jj"
13 REGIME_TRAVAIL_DEFAULT
= 100.00
14 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= 35.00
18 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
19 base_url
=settings
.PRIVE_MEDIA_URL
)
21 def poste_piece_dispatch(instance
, filename
):
22 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
25 def dossier_piece_dispatch(instance
, filename
):
26 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
29 def employe_piece_dispatch(instance
, filename
):
30 path
= "employe/%s/%s" % (instance
.employe_id
, filename
)
34 class Commentaire(AUFMetadata
):
35 texte
= models
.TextField()
36 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+')
40 ordering
= ['-date_creation']
42 def __unicode__(self
):
43 return u
'%s' % (self
.texte
)
48 POSTE_APPEL_CHOICES
= (
49 ('interne', 'Interne'),
50 ('externe', 'Externe'),
53 class PosteManager(NoDeleteManager
):
54 def get_query_set(self
):
55 return super(PosteManager
, self
).get_query_set().select_related('implantation')
57 class Poste_(AUFMetadata
):
58 """Un Poste est un emploi (job) à combler dans une implantation.
59 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
60 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
63 objects
= PosteManager()
66 nom
= models
.CharField(max_length
=255,
67 verbose_name
="Titre du poste", )
68 nom_feminin
= models
.CharField(max_length
=255,
69 verbose_name
="Titre du poste (au féminin)",
71 implantation
= models
.ForeignKey(ref
.Implantation
,
72 db_column
='implantation', related_name
='+')
73 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste',
76 service
= models
.ForeignKey('Service', db_column
='service', null
=True,
78 verbose_name
="Direction/Service/Pôle support",
79 default
=1) # default = Rectorat
80 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
81 related_name
='+', null
=True,
82 verbose_name
="Poste du responsable",
83 default
=149) # default = Recteur
86 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
87 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
88 verbose_name
="Temps de travail",
89 help_text
="% du temps complet")
90 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
91 decimal_places
=2, null
=True,
92 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
93 verbose_name
="Nb. heures par semaine")
96 local
= models
.NullBooleanField(verbose_name
="Local", default
=True,
97 null
=True, blank
=True)
98 expatrie
= models
.NullBooleanField(verbose_name
="Expatrié", default
=False,
99 null
=True, blank
=True)
100 mise_a_disposition
= models
.NullBooleanField(
101 verbose_name
="Mise à disposition",
102 null
=True, default
=False)
103 appel
= models
.CharField(max_length
=10, null
=True,
104 verbose_name
="Appel à candidature",
105 choices
=POSTE_APPEL_CHOICES
,
109 classement_min
= models
.ForeignKey('Classement',
110 db_column
='classement_min', related_name
='+',
111 null
=True, blank
=True)
112 classement_max
= models
.ForeignKey('Classement',
113 db_column
='classement_max', related_name
='+',
114 null
=True, blank
=True)
115 valeur_point_min
= models
.ForeignKey('ValeurPoint',
116 db_column
='valeur_point_min', related_name
='+',
117 null
=True, blank
=True)
118 valeur_point_max
= models
.ForeignKey('ValeurPoint',
119 db_column
='valeur_point_max', related_name
='+',
120 null
=True, blank
=True)
121 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
122 related_name
='+', default
=5)
123 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
124 related_name
='+', default
=5)
125 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
126 null
=True, default
=0)
127 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
128 null
=True, default
=0)
129 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
130 null
=True, default
=0)
131 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
132 null
=True, default
=0)
133 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
134 null
=True, default
=0)
135 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
136 null
=True, default
=0)
138 # Comparatifs de rémunération
139 devise_comparaison
= models
.ForeignKey('Devise', null
=True,
140 db_column
='devise_comparaison',
143 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
144 null
=True, blank
=True)
145 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
146 null
=True, blank
=True)
147 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
148 null
=True, blank
=True)
149 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
150 null
=True, blank
=True)
151 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
152 null
=True, blank
=True)
153 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
154 null
=True, blank
=True)
155 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
156 null
=True, blank
=True)
157 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
158 null
=True, blank
=True)
159 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
160 null
=True, blank
=True)
161 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
162 null
=True, blank
=True)
165 justification
= models
.TextField(null
=True, blank
=True)
168 date_validation
= models
.DateTimeField(null
=True, blank
=True) # de dae
169 date_debut
= models
.DateField(verbose_name
="Date de début",
170 help_text
=HELP_TEXT_DATE
,
171 null
=True, blank
=True)
172 date_fin
= models
.DateField(verbose_name
="Date de fin",
173 help_text
=HELP_TEXT_DATE
,
174 null
=True, blank
=True)
178 ordering
= ['implantation__nom', 'nom']
179 verbose_name
= "Poste"
180 verbose_name_plural
= "Postes"
182 def __unicode__(self
):
183 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
186 representation
= representation
+ u
' (vacant)'
187 return representation
190 # TODO : si existe un dossier actif pour ce poste, return False
191 # self.dossier_set.all() fonctionne pas
196 __doc__
= Poste_
.__doc__
200 __doc__
= Poste_
.__doc__
203 POSTE_FINANCEMENT_CHOICES
= (
204 ('A', 'A - Frais de personnel'),
205 ('B', 'B - Projet(s)-Titre(s)'),
210 class PosteFinancement_(models
.Model
):
211 """Pour un Poste, structure d'informations décrivant comment on prévoit
214 poste
= models
.ForeignKey('Poste', db_column
='poste',
215 related_name
='%(app_label)s_financements')
216 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
217 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
218 help_text
="ex.: 33.33 % (décimale avec point)")
219 commentaire
= models
.TextField(
220 help_text
="Spécifiez la source de financement.")
226 def __unicode__(self
):
227 return u
'%s : %s %' % (self
.type, self
.pourcentage
)
230 class PosteFinancement(PosteFinancement_
):
231 __doc__
= PosteFinancement_
.__doc__
234 class PostePiece(models
.Model
):
235 """Documents relatifs au Poste.
236 Ex.: Description de poste
238 poste
= models
.ForeignKey('Poste', db_column
='poste',
239 related_name
='pieces')
240 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
241 fichier
= models
.FileField(verbose_name
="Fichier",
242 upload_to
=poste_piece_dispatch
,
243 storage
=storage_prive
)
248 def __unicode__(self
):
249 return u
'%s' % (self
.nom
)
251 class PosteComparaison(models
.Model
):
253 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
255 poste
= models
.ForeignKey('Poste', related_name
='comparaisons_internes')
256 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
257 nom
= models
.CharField(verbose_name
="Poste", max_length
=255, null
=True, blank
=True)
258 montant
= models
.IntegerField(null
=True)
259 devise
= models
.ForeignKey("Devise", default
=5, related_name
='+', null
=True, blank
=True)
261 def taux_devise(self
):
262 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.implantation
)
263 if len(liste_taux
) == 0:
264 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.implantation
))
266 return liste_taux
[0].taux
268 def montant_euros(self
):
269 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
272 class PosteCommentaire(Commentaire
):
273 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='+')
282 SITUATION_CHOICES
= (
283 ('C', 'Célibataire'),
288 class Employe(AUFMetadata
):
289 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
290 Dossiers qu'il occupe ou a occupé de Postes.
292 Cette classe aurait pu avantageusement s'appeler Personne car la notion
293 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
296 nom
= models
.CharField(max_length
=255)
297 prenom
= models
.CharField(max_length
=255, verbose_name
="Prénom")
298 nom_affichage
= models
.CharField(max_length
=255,
299 verbose_name
="Nom d'affichage",
300 null
=True, blank
=True)
301 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
302 db_column
='nationalite',
303 related_name
='employes_nationalite',
304 verbose_name
="Nationalité")
305 date_naissance
= models
.DateField(help_text
=HELP_TEXT_DATE
,
306 verbose_name
="Date de naissance",
307 validators
=[validate_date_passee
],
308 null
=True, blank
=True)
309 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
312 situation_famille
= models
.CharField(max_length
=1,
313 choices
=SITUATION_CHOICES
,
314 verbose_name
="Situation familiale",
315 null
=True, blank
=True)
316 date_entree
= models
.DateField(verbose_name
="Date d'entrée à l'AUF",
317 help_text
=HELP_TEXT_DATE
,
318 null
=True, blank
=True)
321 tel_domicile
= models
.CharField(max_length
=255,
322 verbose_name
="Tél. domicile",
323 null
=True, blank
=True)
324 tel_cellulaire
= models
.CharField(max_length
=255,
325 verbose_name
="Tél. cellulaire",
326 null
=True, blank
=True)
327 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
328 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
329 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
330 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
331 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
332 related_name
='employes',
333 null
=True, blank
=True)
336 ordering
= ['nom_affichage','nom','prenom']
337 verbose_name
= "Employé"
338 verbose_name_plural
= "Employés"
340 def __unicode__(self
):
341 return u
'%s' % (self
.get_nom())
344 nom_affichage
= self
.nom_affichage
345 if not nom_affichage
:
346 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
351 if self
.genre
.upper() == u
'M':
353 elif self
.genre
.upper() == u
'F':
358 """Retourne l'URL du service retournant la photo de l'Employe.
359 Équivalent reverse url 'rh_photo' avec id en param.
361 from django
.core
.urlresolvers
import reverse
362 return reverse('rh_photo', kwargs
={'id':self
.id})
364 class EmployePiece(models
.Model
):
365 """Documents relatifs à un employé.
368 employe
= models
.ForeignKey('Employe', db_column
='employe',
370 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
371 fichier
= models
.FileField(verbose_name
="Fichier",
372 upload_to
=employe_piece_dispatch
,
373 storage
=storage_prive
)
378 def __unicode__(self
):
379 return u
'%s' % (self
.nom
)
381 class EmployeCommentaire(Commentaire
):
382 employe
= models
.ForeignKey('Employe', db_column
='employe',
386 LIEN_PARENTE_CHOICES
= (
387 ('Conjoint', 'Conjoint'),
388 ('Conjointe', 'Conjointe'),
393 class AyantDroit(AUFMetadata
):
394 """Personne en relation avec un Employe.
397 nom
= models
.CharField(max_length
=255)
398 prenom
= models
.CharField(max_length
=255,
399 verbose_name
="Prénom",)
400 nom_affichage
= models
.CharField(max_length
=255,
401 verbose_name
="Nom d'affichage",
402 null
=True, blank
=True)
403 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
404 db_column
='nationalite',
405 related_name
='ayantdroits_nationalite',
406 verbose_name
="Nationalité")
407 date_naissance
= models
.DateField(help_text
=HELP_TEXT_DATE
,
408 verbose_name
="Date de naissance",
409 validators
=[validate_date_passee
],
410 null
=True, blank
=True)
411 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
414 employe
= models
.ForeignKey('Employe', db_column
='employe',
415 related_name
='ayantdroits',
416 verbose_name
="Employé")
417 lien_parente
= models
.CharField(max_length
=10,
418 choices
=LIEN_PARENTE_CHOICES
,
419 verbose_name
="Lien de parenté",
420 null
=True, blank
=True)
423 ordering
= ['nom_affichage']
424 verbose_name
= "Ayant droit"
425 verbose_name_plural
= "Ayants droit"
427 def __unicode__(self
):
428 return u
'%s' % (self
.get_nom())
431 nom_affichage
= self
.nom_affichage
432 if not nom_affichage
:
433 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
436 class AyantDroitCommentaire(Commentaire
):
437 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
443 STATUT_RESIDENCE_CHOICES
= (
445 ('expat', 'Expatrié'),
448 COMPTE_COMPTA_CHOICES
= (
454 class Dossier_(AUFMetadata
):
455 """Le Dossier regroupe les informations relatives à l'occupation
456 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
459 Plusieurs Contrats peuvent être associés au Dossier.
460 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
461 lequel aucun Dossier n'existe est un poste vacant.
464 employe
= models
.ForeignKey('Employe', db_column
='employe',
465 related_name
='dossiers',
466 verbose_name
="Employé")
467 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='+')
468 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
470 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
471 db_column
='organisme_bstg',
473 verbose_name
="Organisme",
474 help_text
="Si détaché (DET) ou \
475 mis à disposition (MAD), \
476 préciser l'organisme.",
477 null
=True, blank
=True)
480 remplacement
= models
.BooleanField(default
=False)
481 statut_residence
= models
.CharField(max_length
=10, default
='local',
482 verbose_name
="Statut", null
=True,
483 choices
=STATUT_RESIDENCE_CHOICES
)
486 classement
= models
.ForeignKey('Classement', db_column
='classement',
488 null
=True, blank
=True)
489 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
491 default
=REGIME_TRAVAIL_DEFAULT
,
492 verbose_name
="Régime de travail",
493 help_text
="% du temps complet")
494 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
495 decimal_places
=2, null
=True,
496 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
497 verbose_name
="Nb. heures par semaine")
499 # Occupation du Poste par cet Employe (anciennement "mandat")
500 date_debut
= models
.DateField(verbose_name
="Date de début d'occupation \
502 help_text
=HELP_TEXT_DATE
)
503 date_fin
= models
.DateField(verbose_name
="Date de fin d'occupation \
505 help_text
=HELP_TEXT_DATE
,
506 null
=True, blank
=True)
513 ordering
= ['employe__nom', ]
514 verbose_name
= "Dossier"
515 verbose_name_plural
= "Dossiers"
517 def __unicode__(self
):
518 poste
= self
.poste
.nom
519 if self
.employe
.genre
== 'F':
520 poste
= self
.poste
.nom_feminin
521 return u
'%s - %s' % (self
.employe
, poste
)
524 class Dossier(Dossier_
):
525 __doc__
= Dossier_
.__doc__
528 class DossierPiece(models
.Model
):
529 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
530 Ex.: Lettre de motivation.
532 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
534 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
535 fichier
= models
.FileField(verbose_name
="Fichier",
536 upload_to
=dossier_piece_dispatch
,
537 storage
=storage_prive
)
542 def __unicode__(self
):
543 return u
'%s' % (self
.nom
)
545 class DossierCommentaire(Commentaire
):
546 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
549 class DossierComparaison(models
.Model
):
551 Photo d'une comparaison salariale au moment de l'embauche.
553 dossier
= models
.ForeignKey('Dossier', related_name
='comparaisons')
554 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
555 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
556 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
557 montant
= models
.IntegerField(null
=True)
558 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
560 def taux_devise(self
):
561 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.dossier
.poste
.implantation
)
562 if len(liste_taux
) == 0:
563 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.dossier
.poste
.implantation
))
565 return liste_taux
[0].taux
567 def montant_euros(self
):
568 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
573 class RemunerationMixin(AUFMetadata
):
575 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
576 related_name
='%(app_label)s_%(class)s_remunerations')
577 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
579 verbose_name
="Type de rémunération")
580 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
581 db_column
='type_revalorisation',
583 verbose_name
="Type de revalorisation",
584 null
=True, blank
=True)
585 montant
= models
.FloatField(null
=True, blank
=True,
587 # Annuel (12 mois, 52 semaines, 364 jours?)
588 devise
= models
.ForeignKey('Devise', to_field
='id',
589 db_column
='devise', related_name
='+',
591 # commentaire = precision
592 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
593 # date_debut = anciennement date_effectif
594 date_debut
= models
.DateField(help_text
=HELP_TEXT_DATE
,
595 verbose_name
="Date de début",
596 null
=True, blank
=True)
597 date_fin
= models
.DateField(help_text
=HELP_TEXT_DATE
,
598 verbose_name
="Date de fin",
599 null
=True, blank
=True)
603 ordering
= ['type__nom', '-date_fin']
605 def __unicode__(self
):
606 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
608 class Remuneration_(RemunerationMixin
):
609 """Structure de rémunération (données budgétaires) en situation normale
610 pour un Dossier. Si un Evenement existe, utiliser la structure de
611 rémunération EvenementRemuneration de cet événement.
614 def montant_mois(self
):
615 return round(self
.montant
/ 12, 2)
617 def taux_devise(self
):
618 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
620 def montant_euro(self
):
621 return round(float(self
.montant
) / float(self
.taux_devise()), 2)
623 def montant_euro_mois(self
):
624 return round(self
.montant_euro() / 12, 2)
626 def __unicode__(self
):
628 devise
= self
.devise
.code
631 return "%s %s" % (self
.montant
, devise
)
635 verbose_name
= "Rémunération"
636 verbose_name_plural
= "Rémunérations"
639 class Remuneration(Remuneration_
):
640 __doc__
= Remuneration_
.__doc__
645 class ContratManager(NoDeleteManager
):
646 def get_query_set(self
):
647 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
650 class Contrat(AUFMetadata
):
651 """Document juridique qui encadre la relation de travail d'un Employe
652 pour un Poste particulier. Pour un Dossier (qui documente cette
653 relation de travail) plusieurs contrats peuvent être associés.
656 objects
= ContratManager()
658 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
660 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
662 verbose_name
="Type de contrat")
663 date_debut
= models
.DateField(help_text
=HELP_TEXT_DATE
,
664 verbose_name
="Date de début")
665 date_fin
= models
.DateField(help_text
=HELP_TEXT_DATE
,
666 verbose_name
="Date de fin",
667 null
=True, blank
=True)
670 ordering
= ['dossier__employe__nom_affichage']
671 verbose_name
= "Contrat"
672 verbose_name_plural
= "Contrats"
674 def __unicode__(self
):
675 return u
'%s - %s' % (self
.dossier
, self
.id)
677 # TODO? class ContratPiece(models.Model):
682 class Evenement_(AUFMetadata
):
683 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
684 d'un Dossier qui vient altérer des informations normales liées à un Dossier
685 (ex.: la Remuneration).
687 Ex.: congé de maternité, maladie...
689 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
690 différent et une rémunération en conséquence. On souhaite toutefois
691 conserver le Dossier intact afin d'éviter une re-saisie des données lors
692 du retour à la normale.
694 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
696 nom
= models
.CharField(max_length
=255)
697 date_debut
= models
.DateField(help_text
=HELP_TEXT_DATE
,
698 verbose_name
="Date de début")
699 date_fin
= models
.DateField(help_text
=HELP_TEXT_DATE
,
700 verbose_name
="Date de fin",
701 null
=True, blank
=True)
706 verbose_name
= "Évènement"
707 verbose_name_plural
= "Évènements"
709 def __unicode__(self
):
710 return u
'%s' % (self
.nom
)
713 class Evenement(Evenement_
):
714 __doc__
= Evenement_
.__doc__
717 class EvenementRemuneration_(RemunerationMixin
):
718 """Structure de rémunération liée à un Evenement qui remplace
719 temporairement la Remuneration normale d'un Dossier, pour toute la durée
722 evenement
= models
.ForeignKey("Evenement", db_column
='evenement',
724 verbose_name
="Évènement")
725 # TODO : le champ dossier hérité de Remuneration doit être dérivé
726 # de l'Evenement associé
730 ordering
= ['evenement', 'type__nom', '-date_fin']
731 verbose_name
= "Évènement - rémunération"
732 verbose_name_plural
= "Évènements - rémunérations"
735 class EvenementRemuneration(EvenementRemuneration_
):
736 __doc__
= EvenementRemuneration_
.__doc__
742 class EvenementRemuneration(EvenementRemuneration_
):
743 __doc__
= EvenementRemuneration_
.__doc__
748 class FamilleEmploi(AUFMetadata
):
749 """Catégorie utilisée dans la gestion des Postes.
750 Catégorie supérieure à TypePoste.
752 nom
= models
.CharField(max_length
=255)
756 verbose_name
= "Famille d'emploi"
757 verbose_name_plural
= "Familles d'emploi"
759 def __unicode__(self
):
760 return u
'%s' % (self
.nom
)
762 class TypePoste(AUFMetadata
):
763 """Catégorie de Poste.
765 nom
= models
.CharField(max_length
=255)
766 nom_feminin
= models
.CharField(max_length
=255,
767 verbose_name
="Nom féminin")
769 is_responsable
= models
.BooleanField(default
=False,
770 verbose_name
="Poste de responsabilité")
771 famille_emploi
= models
.ForeignKey('FamilleEmploi',
772 db_column
='famille_emploi',
774 verbose_name
="Famille d'emploi")
778 verbose_name
= "Type de poste"
779 verbose_name_plural
= "Types de poste"
781 def __unicode__(self
):
782 return u
'%s' % (self
.nom
)
785 TYPE_PAIEMENT_CHOICES
= (
786 ('Régulier', 'Régulier'),
787 ('Ponctuel', 'Ponctuel'),
790 NATURE_REMUNERATION_CHOICES
= (
791 ('Accessoire', 'Accessoire'),
792 ('Charges', 'Charges'),
793 ('Indemnité', 'Indemnité'),
794 ('RAS', 'Rémunération autre source'),
795 ('Traitement', 'Traitement'),
798 class TypeRemuneration(AUFMetadata
):
799 """Catégorie de Remuneration.
801 nom
= models
.CharField(max_length
=255)
802 type_paiement
= models
.CharField(max_length
=30,
803 choices
=TYPE_PAIEMENT_CHOICES
,
804 verbose_name
="Type de paiement")
805 nature_remuneration
= models
.CharField(max_length
=30,
806 choices
=NATURE_REMUNERATION_CHOICES
,
807 verbose_name
="Nature de la rémunération")
811 verbose_name
= "Type de rémunération"
812 verbose_name_plural
= "Types de rémunération"
814 def __unicode__(self
):
815 return u
'%s' % (self
.nom
)
817 class TypeRevalorisation(AUFMetadata
):
818 """Justification du changement de la Remuneration.
819 (Actuellement utilisé dans aucun traitement informatique.)
821 nom
= models
.CharField(max_length
=255)
825 verbose_name
= "Type de revalorisation"
826 verbose_name_plural
= "Types de revalorisation"
828 def __unicode__(self
):
829 return u
'%s' % (self
.nom
)
831 class Service(AUFMetadata
):
832 """Unité administrative où les Postes sont rattachés.
834 nom
= models
.CharField(max_length
=255)
838 verbose_name
= "Service"
839 verbose_name_plural
= "Services"
841 def __unicode__(self
):
842 return u
'%s' % (self
.nom
)
845 TYPE_ORGANISME_CHOICES
= (
846 ('MAD', 'Mise à disposition'),
847 ('DET', 'Détachement'),
850 class OrganismeBstg(AUFMetadata
):
851 """Organisation d'où provient un Employe mis à disposition (MAD) de
852 ou détaché (DET) à l'AUF à titre gratuit.
854 (BSTG = bien et service à titre gratuit.)
856 nom
= models
.CharField(max_length
=255)
857 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
858 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
860 related_name
='organismes_bstg',
861 null
=True, blank
=True)
864 ordering
= ['type', 'nom']
865 verbose_name
= "Organisme BSTG"
866 verbose_name_plural
= "Organismes BSTG"
868 def __unicode__(self
):
869 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
871 class Statut(AUFMetadata
):
872 """Statut de l'Employe dans le cadre d'un Dossier particulier.
875 code
= models
.CharField(max_length
=25, unique
=True)
876 nom
= models
.CharField(max_length
=255)
880 verbose_name
= "Statut d'employé"
881 verbose_name_plural
= "Statuts d'employé"
883 def __unicode__(self
):
884 return u
'%s : %s' % (self
.code
, self
.nom
)
887 TYPE_CLASSEMENT_CHOICES
= (
889 ('T', 'T - Technicien'),
890 ('P', 'P - Professionel'),
892 ('D', 'D - Direction'),
893 ('SO', 'SO - Sans objet [expatriés]'),
894 ('HG', 'HG - Hors grille [direction]'),
898 class Classement_(AUFMetadata
):
899 """Éléments de classement de la
900 "Grille générique de classement hiérarchique".
902 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
903 classement dans la grille. Le classement donne le coefficient utilisé dans:
905 salaire de base = coefficient * valeur du point de l'Implantation du Poste
908 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
909 echelon
= models
.IntegerField(verbose_name
="Échelon")
910 degre
= models
.IntegerField(verbose_name
="Degré")
911 coefficient
= models
.FloatField(default
=0, verbose_name
="Coéfficient",
914 # annee # au lieu de date_debut et date_fin
915 commentaire
= models
.TextField(null
=True, blank
=True)
919 ordering
= ['type','echelon','degre','coefficient']
920 verbose_name
= "Classement"
921 verbose_name_plural
= "Classements"
923 def __unicode__(self
):
924 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
927 class Classement(Classement_
):
928 __doc__
= Classement_
.__doc__
931 class TauxChange_(AUFMetadata
):
932 """Taux de change de la devise vers l'euro (EUR)
933 pour chaque année budgétaire.
936 devise
= models
.ForeignKey('Devise', db_column
='devise',
938 annee
= models
.IntegerField(verbose_name
="Année")
939 taux
= models
.FloatField(verbose_name
="Taux vers l'euro")
943 ordering
= ['-annee', 'devise__code']
944 verbose_name
= "Taux de change"
945 verbose_name_plural
= "Taux de change"
947 def __unicode__(self
):
948 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
951 class TauxChange(TauxChange_
):
952 __doc__
= TauxChange_
.__doc__
954 class ValeurPointManager(NoDeleteManager
):
955 def get_query_set(self
):
956 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
959 class ValeurPoint_(AUFMetadata
):
960 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
961 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
962 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
964 salaire de base = coefficient * valeur du point de l'Implantation du Poste
967 objects
= ValeurPointManager()
969 valeur
= models
.FloatField(null
=True)
970 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
971 related_name
='+', default
=5)
972 implantation
= models
.ForeignKey(ref
.Implantation
,
973 db_column
='implantation',
974 related_name
='%(app_label)s_valeur_point')
976 annee
= models
.IntegerField()
979 ordering
= ['-annee', 'implantation__nom']
981 verbose_name
= "Valeur du point"
982 verbose_name_plural
= "Valeurs du point"
984 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
985 def get_tauxchange_courant(self
):
987 Recherche le taux courant associé à la valeur d'un point.
988 Tous les taux de l'année courante sont chargés, pour optimiser un
989 affichage en liste. (On pourrait probablement améliorer le manager pour
990 lui greffer le taux courant sous forme de JOIN)
992 for tauxchange
in self
.tauxchange
:
993 if tauxchange
.implantation_id
== self
.implantation_id
:
997 def __unicode__(self
):
998 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1001 class ValeurPoint(ValeurPoint_
):
1002 __doc__
= ValeurPoint_
.__doc__
1005 class Devise(AUFMetadata
):
1006 """Devise monétaire.
1008 code
= models
.CharField(max_length
=10, unique
=True)
1009 nom
= models
.CharField(max_length
=255)
1013 verbose_name
= "Devise"
1014 verbose_name_plural
= "Devises"
1016 def __unicode__(self
):
1017 return u
'%s - %s' % (self
.code
, self
.nom
)
1019 class TypeContrat(AUFMetadata
):
1022 nom
= models
.CharField(max_length
=255)
1023 nom_long
= models
.CharField(max_length
=255)
1027 verbose_name
= "Type de contrat"
1028 verbose_name_plural
= "Types de contrat"
1030 def __unicode__(self
):
1031 return u
'%s' % (self
.nom
)
1036 class ResponsableImplantation(AUFMetadata
):
1037 """Le responsable d'une implantation.
1038 Anciennement géré sur le Dossier du responsable.
1040 employe
= models
.ForeignKey('Employe', db_column
='employe',
1042 null
=True, blank
=True)
1043 implantation
= models
.ForeignKey(ref
.Implantation
,
1044 db_column
='implantation', related_name
='+',
1047 def __unicode__(self
):
1048 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1051 ordering
= ['implantation__nom']
1052 verbose_name
= "Responsable d'implantation"
1053 verbose_name_plural
= "Responsables d'implantation"
1055 def dossier_piece_dispatch(instance
, filename
):
1056 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)