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
190 # TODO : si existe un dossier actif pour ce poste, return False
191 # self.dossier_set.all() fonctionne pas
194 prefix_implantation
= "implantation__region"
195 def get_regions(self
):
196 return [self
.implantation
.region
]
200 __doc__
= Poste_
.__doc__
204 __doc__
= Poste_
.__doc__
207 POSTE_FINANCEMENT_CHOICES
= (
208 ('A', 'A - Frais de personnel'),
209 ('B', 'B - Projet(s)-Titre(s)'),
214 class PosteFinancement_(models
.Model
):
215 """Pour un Poste, structure d'informations décrivant comment on prévoit
218 poste
= models
.ForeignKey('Poste', db_column
='poste',
219 related_name
='%(app_label)s_financements')
220 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
221 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
222 help_text
="ex.: 33.33 % (décimale avec point)")
223 commentaire
= models
.TextField(
224 help_text
="Spécifiez la source de financement.")
230 def __unicode__(self
):
231 return u
'%s : %s %' % (self
.type, self
.pourcentage
)
234 class PosteFinancement(PosteFinancement_
):
235 __doc__
= PosteFinancement_
.__doc__
238 class PostePiece(models
.Model
):
239 """Documents relatifs au Poste.
240 Ex.: Description de poste
242 poste
= models
.ForeignKey('Poste', db_column
='poste',
243 related_name
='pieces')
244 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
245 fichier
= models
.FileField(verbose_name
= u
"Fichier",
246 upload_to
=poste_piece_dispatch
,
247 storage
=storage_prive
)
252 def __unicode__(self
):
253 return u
'%s' % (self
.nom
)
255 class PosteComparaison(models
.Model
):
257 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
259 poste
= models
.ForeignKey('Poste', related_name
='comparaisons_internes')
260 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
261 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
262 montant
= models
.IntegerField(null
=True)
263 devise
= models
.ForeignKey("Devise", default
=5, related_name
='+', null
=True, blank
=True)
265 def taux_devise(self
):
266 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.implantation
)
267 if len(liste_taux
) == 0:
268 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.implantation
))
270 return liste_taux
[0].taux
272 def montant_euros(self
):
273 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
276 class PosteCommentaire(Commentaire
):
277 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='+')
286 SITUATION_CHOICES
= (
287 ('C', 'Célibataire'),
292 class Employe(AUFMetadata
):
293 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
294 Dossiers qu'il occupe ou a occupé de Postes.
296 Cette classe aurait pu avantageusement s'appeler Personne car la notion
297 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
300 nom
= models
.CharField(max_length
=255)
301 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
302 nom_affichage
= models
.CharField(max_length
=255,
303 verbose_name
= u
"Nom d'affichage",
304 null
=True, blank
=True)
305 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
306 db_column
='nationalite',
307 related_name
='employes_nationalite',
308 verbose_name
= u
"Nationalité")
309 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
310 validators
=[validate_date_passee
],
311 null
=True, blank
=True)
312 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
315 situation_famille
= models
.CharField(max_length
=1,
316 choices
=SITUATION_CHOICES
,
317 verbose_name
= u
"Situation familiale",
318 null
=True, blank
=True)
319 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
320 null
=True, blank
=True)
323 tel_domicile
= models
.CharField(max_length
=255,
324 verbose_name
= u
"Tél. domicile",
325 null
=True, blank
=True)
326 tel_cellulaire
= models
.CharField(max_length
=255,
327 verbose_name
= u
"Tél. cellulaire",
328 null
=True, blank
=True)
329 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
330 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
331 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
332 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
333 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
334 related_name
='employes',
335 null
=True, blank
=True)
338 ordering
= ['nom_affichage','nom','prenom']
339 verbose_name
= u
"Employé"
340 verbose_name_plural
= u
"Employés"
342 def __unicode__(self
):
343 return u
'%s [%s]' % (self
.get_nom(), self
.id)
346 nom_affichage
= self
.nom_affichage
347 if not nom_affichage
:
348 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
353 if self
.genre
.upper() == u
'M':
355 elif self
.genre
.upper() == u
'F':
360 """Retourne l'URL du service retournant la photo de l'Employe.
361 Équivalent reverse url 'rh_photo' avec id en param.
363 from django
.core
.urlresolvers
import reverse
364 return reverse('rh_photo', kwargs
={'id':self
.id})
366 def dossiers_passes(self
):
368 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
369 for d
in dossiers_passes
:
371 return dossiers_passes
373 def dossiers_futurs(self
):
375 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
377 def dossiers_encours(self
):
378 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
379 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
380 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
382 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
383 for d
in dossiers_encours
:
384 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
385 return dossiers_encours
387 def postes_encours(self
):
388 postes_encours
= set()
389 for d
in self
.dossiers_encours():
390 postes_encours
.add(d
.poste
)
391 return postes_encours
393 def poste_principal(self
):
395 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
397 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
399 poste
= Poste
.objects
.none()
401 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
406 prefix_implantation
= "dossiers__poste__implantation__region"
407 def get_regions(self
):
409 for d
in self
.dossiers
.all():
410 regions
.append(d
.poste
.implantation
.region
)
414 class EmployeInactif(Employe
):
417 ordering
= ['nom_affichage','nom','prenom']
418 verbose_name
= u
"Employé inactif"
419 verbose_name_plural
= u
"Employés inactifs"
422 class EmployePiece(models
.Model
):
423 """Documents relatifs à un employé.
426 employe
= models
.ForeignKey('Employe', db_column
='employe')
427 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
428 fichier
= models
.FileField(verbose_name
="Fichier",
429 upload_to
=employe_piece_dispatch
,
430 storage
=storage_prive
)
434 verbose_name
= u
"Employé pièce"
435 verbose_name_plural
= u
"Employé pièces"
437 def __unicode__(self
):
438 return u
'%s' % (self
.nom
)
440 class EmployeCommentaire(Commentaire
):
441 employe
= models
.ForeignKey('Employe', db_column
='employe',
445 verbose_name
= u
"Employé commentaire"
446 verbose_name_plural
= u
"Employé commentaires"
449 LIEN_PARENTE_CHOICES
= (
450 ('Conjoint', 'Conjoint'),
451 ('Conjointe', 'Conjointe'),
456 class AyantDroit(AUFMetadata
):
457 """Personne en relation avec un Employe.
460 nom
= models
.CharField(max_length
=255)
461 prenom
= models
.CharField(max_length
=255,
462 verbose_name
= u
"Prénom",)
463 nom_affichage
= models
.CharField(max_length
=255,
464 verbose_name
= u
"Nom d'affichage",
465 null
=True, blank
=True)
466 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
467 db_column
='nationalite',
468 related_name
='ayantdroits_nationalite',
469 verbose_name
= u
"Nationalité")
470 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
471 validators
=[validate_date_passee
],
472 null
=True, blank
=True)
473 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
476 employe
= models
.ForeignKey('Employe', db_column
='employe',
477 related_name
='ayantdroits',
478 verbose_name
= u
"Employé")
479 lien_parente
= models
.CharField(max_length
=10,
480 choices
=LIEN_PARENTE_CHOICES
,
481 verbose_name
= u
"Lien de parenté",
482 null
=True, blank
=True)
485 ordering
= ['nom_affichage']
486 verbose_name
= u
"Ayant droit"
487 verbose_name_plural
= u
"Ayants droit"
489 def __unicode__(self
):
490 return u
'%s' % (self
.get_nom())
493 nom_affichage
= self
.nom_affichage
494 if not nom_affichage
:
495 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
498 prefix_implantation
= "employe__dossiers__poste__implantation__region"
499 def get_regions(self
):
501 for d
in self
.employe
.dossiers
.all():
502 regions
.append(d
.poste
.implantation
.region
)
506 class AyantDroitCommentaire(Commentaire
):
507 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
513 STATUT_RESIDENCE_CHOICES
= (
515 ('expat', 'Expatrié'),
518 COMPTE_COMPTA_CHOICES
= (
524 class Dossier_(AUFMetadata
):
525 """Le Dossier regroupe les informations relatives à l'occupation
526 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
529 Plusieurs Contrats peuvent être associés au Dossier.
530 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
531 lequel aucun Dossier n'existe est un poste vacant.
534 employe
= models
.ForeignKey('Employe', db_column
='employe',
535 related_name
='dossiers',
536 verbose_name
=u
"Employé")
538 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='dossiers')
539 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
541 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
542 db_column
='organisme_bstg',
544 verbose_name
= u
"Organisme",
545 help_text
="Si détaché (DET) ou \
546 mis à disposition (MAD), \
547 préciser l'organisme.",
548 null
=True, blank
=True)
551 remplacement
= models
.BooleanField(default
=False)
552 remplacement_de
= models
.ForeignKey('self', related_name
='+',
553 null
=True, blank
=True)
554 statut_residence
= models
.CharField(max_length
=10, default
='local',
555 verbose_name
= u
"Statut", null
=True,
556 choices
=STATUT_RESIDENCE_CHOICES
)
559 classement
= models
.ForeignKey('Classement', db_column
='classement',
561 null
=True, blank
=True)
562 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
564 default
=REGIME_TRAVAIL_DEFAULT
,
565 verbose_name
= u
"Régime de travail",
566 help_text
="% du temps complet")
567 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
568 decimal_places
=2, null
=True,
569 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
570 verbose_name
= u
"Nb. heures par semaine")
572 # Occupation du Poste par cet Employe (anciennement "mandat")
573 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
575 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
577 null
=True, blank
=True)
584 ordering
= ['employe__nom', ]
585 verbose_name
= u
"Dossier"
586 verbose_name_plural
= "Dossiers"
588 def salaire_theorique(self
):
589 annee
= date
.today().year
590 coeff
= self
.classement
.coefficient
591 implantation
= self
.poste
.implantation
592 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
594 montant
= coeff
* point
.valeur
595 devise
= point
.devise
596 return {'montant':montant
, 'devise':devise
}
598 def __unicode__(self
):
599 poste
= self
.poste
.nom
600 if self
.employe
.genre
== 'F':
601 poste
= self
.poste
.nom_feminin
602 return u
'%s - %s' % (self
.employe
, poste
)
604 prefix_implantation
= "poste__implantation__region"
605 def get_regions(self
):
606 return [self
.poste
.implantation
.region
]
609 class Dossier(Dossier_
):
610 __doc__
= Dossier_
.__doc__
613 class DossierInactif(Dossier
):
617 ordering
= ['employe__nom', ]
618 verbose_name
= u
"Dossier inactif"
619 verbose_name_plural
= u
"Dossiers inactifs"
622 class DossierPiece(models
.Model
):
623 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
624 Ex.: Lettre de motivation.
626 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
628 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
629 fichier
= models
.FileField(verbose_name
= u
"Fichier",
630 upload_to
=dossier_piece_dispatch
,
631 storage
=storage_prive
)
636 def __unicode__(self
):
637 return u
'%s' % (self
.nom
)
639 class DossierCommentaire(Commentaire
):
640 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
643 class DossierComparaison(models
.Model
):
645 Photo d'une comparaison salariale au moment de l'embauche.
647 dossier
= models
.ForeignKey('Dossier', related_name
='comparaisons')
648 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
649 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
650 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
651 montant
= models
.IntegerField(null
=True)
652 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
654 def taux_devise(self
):
655 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.dossier
.poste
.implantation
)
656 if len(liste_taux
) == 0:
657 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.dossier
.poste
.implantation
))
659 return liste_taux
[0].taux
661 def montant_euros(self
):
662 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
667 class RemunerationMixin(AUFMetadata
):
669 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
670 related_name
='%(app_label)s_%(class)s_remunerations')
671 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
673 verbose_name
= u
"Type de rémunération")
674 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
675 db_column
='type_revalorisation',
677 verbose_name
= u
"Type de revalorisation",
678 null
=True, blank
=True)
679 montant
= models
.FloatField(null
=True, blank
=True,
681 # Annuel (12 mois, 52 semaines, 364 jours?)
682 devise
= models
.ForeignKey('Devise', to_field
='id',
683 db_column
='devise', related_name
='+',
685 # commentaire = precision
686 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
687 # date_debut = anciennement date_effectif
688 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
689 null
=True, blank
=True)
690 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
691 null
=True, blank
=True)
695 ordering
= ['type__nom', '-date_fin']
697 def __unicode__(self
):
698 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
700 class Remuneration_(RemunerationMixin
):
701 """Structure de rémunération (données budgétaires) en situation normale
702 pour un Dossier. Si un Evenement existe, utiliser la structure de
703 rémunération EvenementRemuneration de cet événement.
706 def montant_mois(self
):
707 return round(self
.montant
/ 12, 2)
709 def taux_devise(self
):
710 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
712 def montant_euro(self
):
713 return round(float(self
.montant
) / float(self
.taux_devise()), 2)
715 def montant_euro_mois(self
):
716 return round(self
.montant_euro() / 12, 2)
718 def __unicode__(self
):
720 devise
= self
.devise
.code
723 return "%s %s" % (self
.montant
, devise
)
727 verbose_name
= u
"Rémunération"
728 verbose_name_plural
= u
"Rémunérations"
731 class Remuneration(Remuneration_
):
732 __doc__
= Remuneration_
.__doc__
737 class ContratManager(NoDeleteManager
):
738 def get_query_set(self
):
739 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
742 class Contrat(AUFMetadata
):
743 """Document juridique qui encadre la relation de travail d'un Employe
744 pour un Poste particulier. Pour un Dossier (qui documente cette
745 relation de travail) plusieurs contrats peuvent être associés.
748 objects
= ContratManager()
750 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
751 related_name
='contrats')
752 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
754 verbose_name
= u
"Type de contrat")
755 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
756 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
757 null
=True, blank
=True)
760 ordering
= ['dossier__employe__nom_affichage']
761 verbose_name
= u
"Contrat"
762 verbose_name_plural
= u
"Contrats"
764 def __unicode__(self
):
765 return u
'%s - %s' % (self
.dossier
, self
.id)
767 # TODO? class ContratPiece(models.Model):
772 class Evenement_(AUFMetadata
):
773 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
774 d'un Dossier qui vient altérer des informations normales liées à un Dossier
775 (ex.: la Remuneration).
777 Ex.: congé de maternité, maladie...
779 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
780 différent et une rémunération en conséquence. On souhaite toutefois
781 conserver le Dossier intact afin d'éviter une re-saisie des données lors
782 du retour à la normale.
784 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
786 nom
= models
.CharField(max_length
=255)
787 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
788 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
789 null
=True, blank
=True)
794 verbose_name
= u
"Évènement"
795 verbose_name_plural
= u
"Évènements"
797 def __unicode__(self
):
798 return u
'%s' % (self
.nom
)
801 class Evenement(Evenement_
):
802 __doc__
= Evenement_
.__doc__
805 class EvenementRemuneration_(RemunerationMixin
):
806 """Structure de rémunération liée à un Evenement qui remplace
807 temporairement la Remuneration normale d'un Dossier, pour toute la durée
810 evenement
= models
.ForeignKey("Evenement", db_column
='evenement',
812 verbose_name
= u
"Évènement")
813 # TODO : le champ dossier hérité de Remuneration doit être dérivé
814 # de l'Evenement associé
818 ordering
= ['evenement', 'type__nom', '-date_fin']
819 verbose_name
= u
"Évènement - rémunération"
820 verbose_name_plural
= u
"Évènements - rémunérations"
823 class EvenementRemuneration(EvenementRemuneration_
):
824 __doc__
= EvenementRemuneration_
.__doc__
830 class EvenementRemuneration(EvenementRemuneration_
):
831 __doc__
= EvenementRemuneration_
.__doc__
836 class FamilleEmploi(AUFMetadata
):
837 """Catégorie utilisée dans la gestion des Postes.
838 Catégorie supérieure à TypePoste.
840 nom
= models
.CharField(max_length
=255)
844 verbose_name
= u
"Famille d'emploi"
845 verbose_name_plural
= u
"Familles d'emploi"
847 def __unicode__(self
):
848 return u
'%s' % (self
.nom
)
850 class TypePoste(AUFMetadata
):
851 """Catégorie de Poste.
853 nom
= models
.CharField(max_length
=255)
854 nom_feminin
= models
.CharField(max_length
=255,
855 verbose_name
= u
"Nom féminin")
857 is_responsable
= models
.BooleanField(default
=False,
858 verbose_name
= u
"Poste de responsabilité")
859 famille_emploi
= models
.ForeignKey('FamilleEmploi',
860 db_column
='famille_emploi',
862 verbose_name
= u
"Famille d'emploi")
866 verbose_name
= u
"Type de poste"
867 verbose_name_plural
= u
"Types de poste"
869 def __unicode__(self
):
870 return u
'%s' % (self
.nom
)
873 TYPE_PAIEMENT_CHOICES
= (
874 ('Régulier', 'Régulier'),
875 ('Ponctuel', 'Ponctuel'),
878 NATURE_REMUNERATION_CHOICES
= (
879 ('Accessoire', 'Accessoire'),
880 ('Charges', 'Charges'),
881 ('Indemnité', 'Indemnité'),
882 ('RAS', 'Rémunération autre source'),
883 ('Traitement', 'Traitement'),
886 class TypeRemuneration(AUFMetadata
):
887 """Catégorie de Remuneration.
889 nom
= models
.CharField(max_length
=255)
890 type_paiement
= models
.CharField(max_length
=30,
891 choices
=TYPE_PAIEMENT_CHOICES
,
892 verbose_name
= u
"Type de paiement")
893 nature_remuneration
= models
.CharField(max_length
=30,
894 choices
=NATURE_REMUNERATION_CHOICES
,
895 verbose_name
= u
"Nature de la rémunération")
899 verbose_name
= u
"Type de rémunération"
900 verbose_name_plural
= u
"Types de rémunération"
902 def __unicode__(self
):
903 return u
'%s' % (self
.nom
)
905 class TypeRevalorisation(AUFMetadata
):
906 """Justification du changement de la Remuneration.
907 (Actuellement utilisé dans aucun traitement informatique.)
909 nom
= models
.CharField(max_length
=255)
913 verbose_name
= u
"Type de revalorisation"
914 verbose_name_plural
= u
"Types de revalorisation"
916 def __unicode__(self
):
917 return u
'%s' % (self
.nom
)
919 class Service(AUFMetadata
):
920 """Unité administrative où les Postes sont rattachés.
922 nom
= models
.CharField(max_length
=255)
926 verbose_name
= u
"Service"
927 verbose_name_plural
= u
"Services"
929 def __unicode__(self
):
930 return u
'%s' % (self
.nom
)
933 TYPE_ORGANISME_CHOICES
= (
934 ('MAD', 'Mise à disposition'),
935 ('DET', 'Détachement'),
938 class OrganismeBstg(AUFMetadata
):
939 """Organisation d'où provient un Employe mis à disposition (MAD) de
940 ou détaché (DET) à l'AUF à titre gratuit.
942 (BSTG = bien et service à titre gratuit.)
944 nom
= models
.CharField(max_length
=255)
945 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
946 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
948 related_name
='organismes_bstg',
949 null
=True, blank
=True)
952 ordering
= ['type', 'nom']
953 verbose_name
= u
"Organisme BSTG"
954 verbose_name_plural
= u
"Organismes BSTG"
956 def __unicode__(self
):
957 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
959 prefix_implantation
= "pays__region"
960 def get_regions(self
):
961 return [self
.pays
.region
]
964 class Statut(AUFMetadata
):
965 """Statut de l'Employe dans le cadre d'un Dossier particulier.
968 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.")
969 nom
= models
.CharField(max_length
=255)
973 verbose_name
= u
"Statut d'employé"
974 verbose_name_plural
= u
"Statuts d'employé"
976 def __unicode__(self
):
977 return u
'%s : %s' % (self
.code
, self
.nom
)
980 TYPE_CLASSEMENT_CHOICES
= (
982 ('T', 'T - Technicien'),
983 ('P', 'P - Professionel'),
985 ('D', 'D - Direction'),
986 ('SO', 'SO - Sans objet [expatriés]'),
987 ('HG', 'HG - Hors grille [direction]'),
991 class Classement_(AUFMetadata
):
992 """Éléments de classement de la
993 "Grille générique de classement hiérarchique".
995 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
996 classement dans la grille. Le classement donne le coefficient utilisé dans:
998 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1001 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1002 echelon
= models
.IntegerField(verbose_name
= u
"Échelon")
1003 degre
= models
.IntegerField(verbose_name
= u
"Degré")
1004 coefficient
= models
.FloatField(default
=0, verbose_name
= u
"Coéfficient",
1007 # annee # au lieu de date_debut et date_fin
1008 commentaire
= models
.TextField(null
=True, blank
=True)
1012 ordering
= ['type','echelon','degre','coefficient']
1013 verbose_name
= u
"Classement"
1014 verbose_name_plural
= u
"Classements"
1016 def __unicode__(self
):
1017 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1020 class Classement(Classement_
):
1021 __doc__
= Classement_
.__doc__
1024 class TauxChange_(AUFMetadata
):
1025 """Taux de change de la devise vers l'euro (EUR)
1026 pour chaque année budgétaire.
1029 devise
= models
.ForeignKey('Devise', db_column
='devise')
1030 annee
= models
.IntegerField(verbose_name
= u
"Année")
1031 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1035 ordering
= ['-annee', 'devise__code']
1036 verbose_name
= u
"Taux de change"
1037 verbose_name_plural
= u
"Taux de change"
1039 def __unicode__(self
):
1040 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1043 class TauxChange(TauxChange_
):
1044 __doc__
= TauxChange_
.__doc__
1046 class ValeurPointManager(NoDeleteManager
):
1047 def get_query_set(self
):
1048 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1051 class ValeurPoint_(AUFMetadata
):
1052 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1053 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1054 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1056 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1059 objects
= ValeurPointManager()
1061 valeur
= models
.FloatField(null
=True)
1062 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1063 related_name
='+', default
=5)
1064 implantation
= models
.ForeignKey(ref
.Implantation
,
1065 db_column
='implantation',
1066 related_name
='%(app_label)s_valeur_point')
1068 annee
= models
.IntegerField()
1071 ordering
= ['-annee', 'implantation__nom']
1073 verbose_name
= u
"Valeur du point"
1074 verbose_name_plural
= u
"Valeurs du point"
1076 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
1077 def get_tauxchange_courant(self
):
1079 Recherche le taux courant associé à la valeur d'un point.
1080 Tous les taux de l'année courante sont chargés, pour optimiser un
1081 affichage en liste. (On pourrait probablement améliorer le manager pour
1082 lui greffer le taux courant sous forme de JOIN)
1084 for tauxchange
in self
.tauxchange
:
1085 if tauxchange
.implantation_id
== self
.implantation_id
:
1089 def __unicode__(self
):
1090 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1093 class ValeurPoint(ValeurPoint_
):
1094 __doc__
= ValeurPoint_
.__doc__
1097 class Devise(AUFMetadata
):
1098 """Devise monétaire.
1100 code
= models
.CharField(max_length
=10, unique
=True)
1101 nom
= models
.CharField(max_length
=255)
1105 verbose_name
= u
"Devise"
1106 verbose_name_plural
= u
"Devises"
1108 def __unicode__(self
):
1109 return u
'%s - %s' % (self
.code
, self
.nom
)
1111 class TypeContrat(AUFMetadata
):
1114 nom
= models
.CharField(max_length
=255)
1115 nom_long
= models
.CharField(max_length
=255)
1119 verbose_name
= u
"Type de contrat"
1120 verbose_name_plural
= u
"Types de contrat"
1122 def __unicode__(self
):
1123 return u
'%s' % (self
.nom
)
1128 class ResponsableImplantation(AUFMetadata
):
1129 """Le responsable d'une implantation.
1130 Anciennement géré sur le Dossier du responsable.
1132 employe
= models
.ForeignKey('Employe', db_column
='employe',
1134 null
=True, blank
=True)
1135 implantation
= models
.ForeignKey(ref
.Implantation
,
1136 db_column
='implantation', related_name
='+',
1139 def __unicode__(self
):
1140 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1143 ordering
= ['implantation__nom']
1144 verbose_name
= "Responsable d'implantation"
1145 verbose_name_plural
= "Responsables d'implantation"