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 EmployePiece(models
.Model
):
424 """Documents relatifs à un employé.
427 employe
= models
.ForeignKey('Employe', db_column
='employe')
428 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
429 fichier
= models
.FileField(verbose_name
="Fichier",
430 upload_to
=employe_piece_dispatch
,
431 storage
=storage_prive
)
435 verbose_name
= u
"Employé pièce"
436 verbose_name_plural
= u
"Employé pièces"
438 def __unicode__(self
):
439 return u
'%s' % (self
.nom
)
441 class EmployeCommentaire(Commentaire
):
442 employe
= models
.ForeignKey('Employe', db_column
='employe',
446 verbose_name
= u
"Employé commentaire"
447 verbose_name_plural
= u
"Employé commentaires"
450 LIEN_PARENTE_CHOICES
= (
451 ('Conjoint', 'Conjoint'),
452 ('Conjointe', 'Conjointe'),
457 class AyantDroit(AUFMetadata
):
458 """Personne en relation avec un Employe.
461 nom
= models
.CharField(max_length
=255)
462 prenom
= models
.CharField(max_length
=255,
463 verbose_name
= u
"Prénom",)
464 nom_affichage
= models
.CharField(max_length
=255,
465 verbose_name
= u
"Nom d'affichage",
466 null
=True, blank
=True)
467 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
468 db_column
='nationalite',
469 related_name
='ayantdroits_nationalite',
470 verbose_name
= u
"Nationalité")
471 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
472 validators
=[validate_date_passee
],
473 null
=True, blank
=True)
474 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
477 employe
= models
.ForeignKey('Employe', db_column
='employe',
478 related_name
='ayantdroits',
479 verbose_name
= u
"Employé")
480 lien_parente
= models
.CharField(max_length
=10,
481 choices
=LIEN_PARENTE_CHOICES
,
482 verbose_name
= u
"Lien de parenté",
483 null
=True, blank
=True)
486 ordering
= ['nom_affichage']
487 verbose_name
= u
"Ayant droit"
488 verbose_name_plural
= u
"Ayants droit"
490 def __unicode__(self
):
491 return u
'%s' % (self
.get_nom())
494 nom_affichage
= self
.nom_affichage
495 if not nom_affichage
:
496 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
499 prefix_implantation
= "employe__dossiers__poste__implantation__region"
500 def get_regions(self
):
502 for d
in self
.employe
.dossiers
.all():
503 regions
.append(d
.poste
.implantation
.region
)
507 class AyantDroitCommentaire(Commentaire
):
508 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
514 STATUT_RESIDENCE_CHOICES
= (
516 ('expat', 'Expatrié'),
519 COMPTE_COMPTA_CHOICES
= (
525 class Dossier_(AUFMetadata
):
526 """Le Dossier regroupe les informations relatives à l'occupation
527 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
530 Plusieurs Contrats peuvent être associés au Dossier.
531 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
532 lequel aucun Dossier n'existe est un poste vacant.
535 employe
= models
.ForeignKey('Employe', db_column
='employe',
536 related_name
='dossiers',
537 verbose_name
=u
"Employé")
539 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='dossiers')
540 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
542 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
543 db_column
='organisme_bstg',
545 verbose_name
= u
"Organisme",
546 help_text
="Si détaché (DET) ou \
547 mis à disposition (MAD), \
548 préciser l'organisme.",
549 null
=True, blank
=True)
552 remplacement
= models
.BooleanField(default
=False)
553 remplacement_de
= models
.ForeignKey('self', related_name
='+',
554 null
=True, blank
=True)
555 statut_residence
= models
.CharField(max_length
=10, default
='local',
556 verbose_name
= u
"Statut", null
=True,
557 choices
=STATUT_RESIDENCE_CHOICES
)
560 classement
= models
.ForeignKey('Classement', db_column
='classement',
562 null
=True, blank
=True)
563 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
565 default
=REGIME_TRAVAIL_DEFAULT
,
566 verbose_name
= u
"Régime de travail",
567 help_text
="% du temps complet")
568 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
569 decimal_places
=2, null
=True,
570 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
571 verbose_name
= u
"Nb. heures par semaine")
573 # Occupation du Poste par cet Employe (anciennement "mandat")
574 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
576 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
578 null
=True, blank
=True)
585 ordering
= ['employe__nom', ]
586 verbose_name
= u
"Dossier"
587 verbose_name_plural
= "Dossiers"
589 def salaire_theorique(self
):
590 annee
= date
.today().year
591 coeff
= self
.classement
.coefficient
592 implantation
= self
.poste
.implantation
593 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
595 montant
= coeff
* point
.valeur
596 devise
= point
.devise
597 return {'montant':montant
, 'devise':devise
}
599 def __unicode__(self
):
600 poste
= self
.poste
.nom
601 if self
.employe
.genre
== 'F':
602 poste
= self
.poste
.nom_feminin
603 return u
'%s - %s' % (self
.employe
, poste
)
605 prefix_implantation
= "poste__implantation__region"
606 def get_regions(self
):
607 return [self
.poste
.implantation
.region
]
610 def remunerations(self
):
611 return self
.rh_remuneration_remunerations
.all().order_by('date_debut')
614 class Dossier(Dossier_
):
615 __doc__
= Dossier_
.__doc__
618 class DossierPiece(models
.Model
):
619 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
620 Ex.: Lettre de motivation.
622 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
624 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
625 fichier
= models
.FileField(verbose_name
= u
"Fichier",
626 upload_to
=dossier_piece_dispatch
,
627 storage
=storage_prive
)
632 def __unicode__(self
):
633 return u
'%s' % (self
.nom
)
635 class DossierCommentaire(Commentaire
):
636 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
639 class DossierComparaison(models
.Model
):
641 Photo d'une comparaison salariale au moment de l'embauche.
643 dossier
= models
.ForeignKey('Dossier', related_name
='comparaisons')
644 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
645 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
646 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
647 montant
= models
.IntegerField(null
=True)
648 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
650 def taux_devise(self
):
651 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.dossier
.poste
.implantation
)
652 if len(liste_taux
) == 0:
653 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.dossier
.poste
.implantation
))
655 return liste_taux
[0].taux
657 def montant_euros(self
):
658 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
663 class RemunerationMixin(AUFMetadata
):
665 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
666 related_name
='%(app_label)s_%(class)s_remunerations')
667 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
669 verbose_name
= u
"Type de rémunération")
670 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
671 db_column
='type_revalorisation',
673 verbose_name
= u
"Type de revalorisation",
674 null
=True, blank
=True)
675 montant
= models
.FloatField(null
=True, blank
=True,
677 # Annuel (12 mois, 52 semaines, 364 jours?)
678 devise
= models
.ForeignKey('Devise', to_field
='id',
679 db_column
='devise', related_name
='+',
681 # commentaire = precision
682 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
683 # date_debut = anciennement date_effectif
684 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
685 null
=True, blank
=True)
686 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
687 null
=True, blank
=True)
691 ordering
= ['type__nom', '-date_fin']
693 def __unicode__(self
):
694 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
696 class Remuneration_(RemunerationMixin
):
697 """Structure de rémunération (données budgétaires) en situation normale
698 pour un Dossier. Si un Evenement existe, utiliser la structure de
699 rémunération EvenementRemuneration de cet événement.
702 def montant_mois(self
):
703 return round(self
.montant
/ 12, 2)
705 def taux_devise(self
):
706 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
708 def montant_euro(self
):
709 return round(float(self
.montant
) / float(self
.taux_devise()), 2)
711 def montant_euro_mois(self
):
712 return round(self
.montant_euro() / 12, 2)
714 def __unicode__(self
):
716 devise
= self
.devise
.code
719 return "%s %s" % (self
.montant
, devise
)
723 verbose_name
= u
"Rémunération"
724 verbose_name_plural
= u
"Rémunérations"
727 class Remuneration(Remuneration_
):
728 __doc__
= Remuneration_
.__doc__
733 class ContratManager(NoDeleteManager
):
734 def get_query_set(self
):
735 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
738 class Contrat(AUFMetadata
):
739 """Document juridique qui encadre la relation de travail d'un Employe
740 pour un Poste particulier. Pour un Dossier (qui documente cette
741 relation de travail) plusieurs contrats peuvent être associés.
744 objects
= ContratManager()
746 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
747 related_name
='contrats')
748 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
750 verbose_name
= u
"Type de contrat")
751 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
752 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
753 null
=True, blank
=True)
756 ordering
= ['dossier__employe__nom_affichage']
757 verbose_name
= u
"Contrat"
758 verbose_name_plural
= u
"Contrats"
760 def __unicode__(self
):
761 return u
'%s - %s' % (self
.dossier
, self
.id)
763 # TODO? class ContratPiece(models.Model):
768 class Evenement_(AUFMetadata
):
769 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
770 d'un Dossier qui vient altérer des informations normales liées à un Dossier
771 (ex.: la Remuneration).
773 Ex.: congé de maternité, maladie...
775 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
776 différent et une rémunération en conséquence. On souhaite toutefois
777 conserver le Dossier intact afin d'éviter une re-saisie des données lors
778 du retour à la normale.
780 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
782 nom
= models
.CharField(max_length
=255)
783 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
784 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
785 null
=True, blank
=True)
790 verbose_name
= u
"Évènement"
791 verbose_name_plural
= u
"Évènements"
793 def __unicode__(self
):
794 return u
'%s' % (self
.nom
)
797 class Evenement(Evenement_
):
798 __doc__
= Evenement_
.__doc__
801 class EvenementRemuneration_(RemunerationMixin
):
802 """Structure de rémunération liée à un Evenement qui remplace
803 temporairement la Remuneration normale d'un Dossier, pour toute la durée
806 evenement
= models
.ForeignKey("Evenement", db_column
='evenement',
808 verbose_name
= u
"Évènement")
809 # TODO : le champ dossier hérité de Remuneration doit être dérivé
810 # de l'Evenement associé
814 ordering
= ['evenement', 'type__nom', '-date_fin']
815 verbose_name
= u
"Évènement - rémunération"
816 verbose_name_plural
= u
"Évènements - rémunérations"
819 class EvenementRemuneration(EvenementRemuneration_
):
820 __doc__
= EvenementRemuneration_
.__doc__
826 class EvenementRemuneration(EvenementRemuneration_
):
827 __doc__
= EvenementRemuneration_
.__doc__
832 class FamilleEmploi(AUFMetadata
):
833 """Catégorie utilisée dans la gestion des Postes.
834 Catégorie supérieure à TypePoste.
836 nom
= models
.CharField(max_length
=255)
840 verbose_name
= u
"Famille d'emploi"
841 verbose_name_plural
= u
"Familles d'emploi"
843 def __unicode__(self
):
844 return u
'%s' % (self
.nom
)
846 class TypePoste(AUFMetadata
):
847 """Catégorie de Poste.
849 nom
= models
.CharField(max_length
=255)
850 nom_feminin
= models
.CharField(max_length
=255,
851 verbose_name
= u
"Nom féminin")
853 is_responsable
= models
.BooleanField(default
=False,
854 verbose_name
= u
"Poste de responsabilité")
855 famille_emploi
= models
.ForeignKey('FamilleEmploi',
856 db_column
='famille_emploi',
858 verbose_name
= u
"Famille d'emploi")
862 verbose_name
= u
"Type de poste"
863 verbose_name_plural
= u
"Types de poste"
865 def __unicode__(self
):
866 return u
'%s' % (self
.nom
)
869 TYPE_PAIEMENT_CHOICES
= (
870 ('Régulier', 'Régulier'),
871 ('Ponctuel', 'Ponctuel'),
874 NATURE_REMUNERATION_CHOICES
= (
875 ('Accessoire', 'Accessoire'),
876 ('Charges', 'Charges'),
877 ('Indemnité', 'Indemnité'),
878 ('RAS', 'Rémunération autre source'),
879 ('Traitement', 'Traitement'),
882 class TypeRemuneration(AUFMetadata
):
883 """Catégorie de Remuneration.
885 nom
= models
.CharField(max_length
=255)
886 type_paiement
= models
.CharField(max_length
=30,
887 choices
=TYPE_PAIEMENT_CHOICES
,
888 verbose_name
= u
"Type de paiement")
889 nature_remuneration
= models
.CharField(max_length
=30,
890 choices
=NATURE_REMUNERATION_CHOICES
,
891 verbose_name
= u
"Nature de la rémunération")
895 verbose_name
= u
"Type de rémunération"
896 verbose_name_plural
= u
"Types de rémunération"
898 def __unicode__(self
):
899 return u
'%s' % (self
.nom
)
901 class TypeRevalorisation(AUFMetadata
):
902 """Justification du changement de la Remuneration.
903 (Actuellement utilisé dans aucun traitement informatique.)
905 nom
= models
.CharField(max_length
=255)
909 verbose_name
= u
"Type de revalorisation"
910 verbose_name_plural
= u
"Types de revalorisation"
912 def __unicode__(self
):
913 return u
'%s' % (self
.nom
)
915 class Service(AUFMetadata
):
916 """Unité administrative où les Postes sont rattachés.
918 nom
= models
.CharField(max_length
=255)
922 verbose_name
= u
"Service"
923 verbose_name_plural
= u
"Services"
925 def __unicode__(self
):
926 return u
'%s' % (self
.nom
)
929 TYPE_ORGANISME_CHOICES
= (
930 ('MAD', 'Mise à disposition'),
931 ('DET', 'Détachement'),
934 class OrganismeBstg(AUFMetadata
):
935 """Organisation d'où provient un Employe mis à disposition (MAD) de
936 ou détaché (DET) à l'AUF à titre gratuit.
938 (BSTG = bien et service à titre gratuit.)
940 nom
= models
.CharField(max_length
=255)
941 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
942 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
944 related_name
='organismes_bstg',
945 null
=True, blank
=True)
948 ordering
= ['type', 'nom']
949 verbose_name
= u
"Organisme BSTG"
950 verbose_name_plural
= u
"Organismes BSTG"
952 def __unicode__(self
):
953 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
955 prefix_implantation
= "pays__region"
956 def get_regions(self
):
957 return [self
.pays
.region
]
960 class Statut(AUFMetadata
):
961 """Statut de l'Employe dans le cadre d'un Dossier particulier.
964 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.")
965 nom
= models
.CharField(max_length
=255)
969 verbose_name
= u
"Statut d'employé"
970 verbose_name_plural
= u
"Statuts d'employé"
972 def __unicode__(self
):
973 return u
'%s : %s' % (self
.code
, self
.nom
)
976 TYPE_CLASSEMENT_CHOICES
= (
978 ('T', 'T - Technicien'),
979 ('P', 'P - Professionel'),
981 ('D', 'D - Direction'),
982 ('SO', 'SO - Sans objet [expatriés]'),
983 ('HG', 'HG - Hors grille [direction]'),
987 class Classement_(AUFMetadata
):
988 """Éléments de classement de la
989 "Grille générique de classement hiérarchique".
991 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
992 classement dans la grille. Le classement donne le coefficient utilisé dans:
994 salaire de base = coefficient * valeur du point de l'Implantation du Poste
997 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
998 echelon
= models
.IntegerField(verbose_name
= u
"Échelon")
999 degre
= models
.IntegerField(verbose_name
= u
"Degré")
1000 coefficient
= models
.FloatField(default
=0, verbose_name
= u
"Coéfficient",
1003 # annee # au lieu de date_debut et date_fin
1004 commentaire
= models
.TextField(null
=True, blank
=True)
1008 ordering
= ['type','echelon','degre','coefficient']
1009 verbose_name
= u
"Classement"
1010 verbose_name_plural
= u
"Classements"
1012 def __unicode__(self
):
1013 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1016 class Classement(Classement_
):
1017 __doc__
= Classement_
.__doc__
1020 class TauxChange_(AUFMetadata
):
1021 """Taux de change de la devise vers l'euro (EUR)
1022 pour chaque année budgétaire.
1025 devise
= models
.ForeignKey('Devise', db_column
='devise')
1026 annee
= models
.IntegerField(verbose_name
= u
"Année")
1027 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1031 ordering
= ['-annee', 'devise__code']
1032 verbose_name
= u
"Taux de change"
1033 verbose_name_plural
= u
"Taux de change"
1035 def __unicode__(self
):
1036 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1039 class TauxChange(TauxChange_
):
1040 __doc__
= TauxChange_
.__doc__
1042 class ValeurPointManager(NoDeleteManager
):
1043 def get_query_set(self
):
1044 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1047 class ValeurPoint_(AUFMetadata
):
1048 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1049 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1050 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1052 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1055 objects
= ValeurPointManager()
1057 valeur
= models
.FloatField(null
=True)
1058 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1059 related_name
='+', default
=5)
1060 implantation
= models
.ForeignKey(ref
.Implantation
,
1061 db_column
='implantation',
1062 related_name
='%(app_label)s_valeur_point')
1064 annee
= models
.IntegerField()
1067 ordering
= ['-annee', 'implantation__nom']
1069 verbose_name
= u
"Valeur du point"
1070 verbose_name_plural
= u
"Valeurs du point"
1072 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
1073 def get_tauxchange_courant(self
):
1075 Recherche le taux courant associé à la valeur d'un point.
1076 Tous les taux de l'année courante sont chargés, pour optimiser un
1077 affichage en liste. (On pourrait probablement améliorer le manager pour
1078 lui greffer le taux courant sous forme de JOIN)
1080 for tauxchange
in self
.tauxchange
:
1081 if tauxchange
.implantation_id
== self
.implantation_id
:
1085 def __unicode__(self
):
1086 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1089 class ValeurPoint(ValeurPoint_
):
1090 __doc__
= ValeurPoint_
.__doc__
1093 class Devise(AUFMetadata
):
1094 """Devise monétaire.
1096 code
= models
.CharField(max_length
=10, unique
=True)
1097 nom
= models
.CharField(max_length
=255)
1101 verbose_name
= u
"Devise"
1102 verbose_name_plural
= u
"Devises"
1104 def __unicode__(self
):
1105 return u
'%s - %s' % (self
.code
, self
.nom
)
1107 class TypeContrat(AUFMetadata
):
1110 nom
= models
.CharField(max_length
=255)
1111 nom_long
= models
.CharField(max_length
=255)
1115 verbose_name
= u
"Type de contrat"
1116 verbose_name_plural
= u
"Types de contrat"
1118 def __unicode__(self
):
1119 return u
'%s' % (self
.nom
)
1124 class ResponsableImplantation(AUFMetadata
):
1125 """Le responsable d'une implantation.
1126 Anciennement géré sur le Dossier du responsable.
1128 employe
= models
.ForeignKey('Employe', db_column
='employe',
1130 null
=True, blank
=True)
1131 implantation
= models
.ForeignKey(ref
.Implantation
,
1132 db_column
='implantation', related_name
='+',
1135 def __unicode__(self
):
1136 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1139 ordering
= ['implantation__nom']
1140 verbose_name
= "Responsable d'implantation"
1141 verbose_name_plural
= "Responsables d'implantation"