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 HELP_TEXT_DATE
= "format: aaaa-mm-jj"
16 REGIME_TRAVAIL_DEFAULT
= 100.00
17 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= 35.00
21 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
22 base_url
=settings
.PRIVE_MEDIA_URL
)
24 def poste_piece_dispatch(instance
, filename
):
25 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
28 def dossier_piece_dispatch(instance
, filename
):
29 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
32 def employe_piece_dispatch(instance
, filename
):
33 path
= "employe/%s/%s" % (instance
.employe_id
, filename
)
37 class Commentaire(AUFMetadata
):
38 texte
= models
.TextField()
39 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+')
43 ordering
= ['-date_creation']
45 def __unicode__(self
):
46 return u
'%s' % (self
.texte
)
51 POSTE_APPEL_CHOICES
= (
52 ('interne', 'Interne'),
53 ('externe', 'Externe'),
56 class PosteManager(NoDeleteManager
):
57 def get_query_set(self
):
58 return super(PosteManager
, self
).get_query_set().select_related('implantation')
60 class Poste_(AUFMetadata
):
61 """Un Poste est un emploi (job) à combler dans une implantation.
62 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
63 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
66 objects
= PosteManager()
69 nom
= models
.CharField(max_length
=255,
70 verbose_name
= u
"Titre du poste", )
71 nom_feminin
= models
.CharField(max_length
=255,
72 verbose_name
= u
"Titre du poste (au féminin)",
74 implantation
= models
.ForeignKey(ref
.Implantation
,
75 db_column
='implantation', related_name
='+')
76 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste',
79 service
= models
.ForeignKey('Service', db_column
='service', null
=True,
81 verbose_name
= u
"Direction/Service/Pôle support",
82 default
=1) # default = Rectorat
83 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
84 related_name
='+', null
=True,
85 verbose_name
= u
"Poste du responsable",
86 default
=149) # default = Recteur
89 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
90 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
91 verbose_name
= u
"Temps de travail",
92 help_text
="% du temps complet")
93 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
94 decimal_places
=2, null
=True,
95 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
96 verbose_name
= u
"Nb. heures par semaine")
99 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
100 null
=True, blank
=True)
101 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
102 null
=True, blank
=True)
103 mise_a_disposition
= models
.NullBooleanField(
104 verbose_name
= u
"Mise à disposition",
105 null
=True, default
=False)
106 appel
= models
.CharField(max_length
=10, null
=True,
107 verbose_name
= u
"Appel à candidature",
108 choices
=POSTE_APPEL_CHOICES
,
112 classement_min
= models
.ForeignKey('Classement',
113 db_column
='classement_min', related_name
='+',
114 null
=True, blank
=True)
115 classement_max
= models
.ForeignKey('Classement',
116 db_column
='classement_max', related_name
='+',
117 null
=True, blank
=True)
118 valeur_point_min
= models
.ForeignKey('ValeurPoint',
119 db_column
='valeur_point_min', related_name
='+',
120 null
=True, blank
=True)
121 valeur_point_max
= models
.ForeignKey('ValeurPoint',
122 db_column
='valeur_point_max', related_name
='+',
123 null
=True, blank
=True)
124 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
125 related_name
='+', default
=5)
126 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
127 related_name
='+', default
=5)
128 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
129 null
=True, default
=0)
130 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
131 null
=True, default
=0)
132 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
133 null
=True, default
=0)
134 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
135 null
=True, default
=0)
136 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
137 null
=True, default
=0)
138 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
139 null
=True, default
=0)
141 # Comparatifs de rémunération
142 devise_comparaison
= models
.ForeignKey('Devise', null
=True,
143 db_column
='devise_comparaison',
146 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
147 null
=True, blank
=True)
148 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
149 null
=True, blank
=True)
150 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
151 null
=True, blank
=True)
152 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
153 null
=True, blank
=True)
154 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
155 null
=True, blank
=True)
156 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
157 null
=True, blank
=True)
158 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
159 null
=True, blank
=True)
160 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
161 null
=True, blank
=True)
162 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
163 null
=True, blank
=True)
164 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
165 null
=True, blank
=True)
168 justification
= models
.TextField(null
=True, blank
=True)
171 date_validation
= models
.DateTimeField(null
=True, blank
=True) # de dae
172 date_debut
= models
.DateField(verbose_name
=u
"Date de début",
173 help_text
=HELP_TEXT_DATE
,
174 null
=True, blank
=True)
175 date_fin
= models
.DateField(verbose_name
=u
"Date de fin",
176 help_text
=HELP_TEXT_DATE
,
177 null
=True, blank
=True)
181 ordering
= ['implantation__nom', 'nom']
182 verbose_name
= u
"Poste"
183 verbose_name_plural
= u
"Postes"
185 def __unicode__(self
):
186 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
189 representation
= representation
+ u
' (vacant)'
190 return representation
193 # TODO : si existe un dossier actif pour ce poste, return False
194 # self.dossier_set.all() fonctionne pas
197 prefix_implantation
= "implantation__region"
198 def get_regions(self
):
199 return [self
.implantation
.region
]
203 __doc__
= Poste_
.__doc__
207 __doc__
= Poste_
.__doc__
210 POSTE_FINANCEMENT_CHOICES
= (
211 ('A', 'A - Frais de personnel'),
212 ('B', 'B - Projet(s)-Titre(s)'),
217 class PosteFinancement_(models
.Model
):
218 """Pour un Poste, structure d'informations décrivant comment on prévoit
221 poste
= models
.ForeignKey('Poste', db_column
='poste',
222 related_name
='%(app_label)s_financements')
223 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
224 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
225 help_text
="ex.: 33.33 % (décimale avec point)")
226 commentaire
= models
.TextField(
227 help_text
="Spécifiez la source de financement.")
233 def __unicode__(self
):
234 return u
'%s : %s %' % (self
.type, self
.pourcentage
)
237 class PosteFinancement(PosteFinancement_
):
238 __doc__
= PosteFinancement_
.__doc__
241 class PostePiece(models
.Model
):
242 """Documents relatifs au Poste.
243 Ex.: Description de poste
245 poste
= models
.ForeignKey('Poste', db_column
='poste',
246 related_name
='pieces')
247 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
248 fichier
= models
.FileField(verbose_name
= u
"Fichier",
249 upload_to
=poste_piece_dispatch
,
250 storage
=storage_prive
)
255 def __unicode__(self
):
256 return u
'%s' % (self
.nom
)
258 class PosteComparaison(models
.Model
):
260 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
262 poste
= models
.ForeignKey('Poste', related_name
='comparaisons_internes')
263 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
264 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
265 montant
= models
.IntegerField(null
=True)
266 devise
= models
.ForeignKey("Devise", default
=5, related_name
='+', null
=True, blank
=True)
268 def taux_devise(self
):
269 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.implantation
)
270 if len(liste_taux
) == 0:
271 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.implantation
))
273 return liste_taux
[0].taux
275 def montant_euros(self
):
276 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
279 class PosteCommentaire(Commentaire
):
280 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='+')
289 SITUATION_CHOICES
= (
290 ('C', 'Célibataire'),
295 class Employe(AUFMetadata
):
296 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
297 Dossiers qu'il occupe ou a occupé de Postes.
299 Cette classe aurait pu avantageusement s'appeler Personne car la notion
300 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
303 nom
= models
.CharField(max_length
=255)
304 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
305 nom_affichage
= models
.CharField(max_length
=255,
306 verbose_name
= u
"Nom d'affichage",
307 null
=True, blank
=True)
308 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
309 db_column
='nationalite',
310 related_name
='employes_nationalite',
311 verbose_name
= u
"Nationalité")
312 date_naissance
= models
.DateField(help_text
=HELP_TEXT_DATE
,
313 verbose_name
= u
"Date de naissance",
314 validators
=[validate_date_passee
],
315 null
=True, blank
=True)
316 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
319 situation_famille
= models
.CharField(max_length
=1,
320 choices
=SITUATION_CHOICES
,
321 verbose_name
= u
"Situation familiale",
322 null
=True, blank
=True)
323 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
324 help_text
=HELP_TEXT_DATE
,
325 null
=True, blank
=True)
328 tel_domicile
= models
.CharField(max_length
=255,
329 verbose_name
= u
"Tél. domicile",
330 null
=True, blank
=True)
331 tel_cellulaire
= models
.CharField(max_length
=255,
332 verbose_name
= u
"Tél. cellulaire",
333 null
=True, blank
=True)
334 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
335 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
336 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
337 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
338 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
339 related_name
='employes',
340 null
=True, blank
=True)
343 ordering
= ['nom_affichage','nom','prenom']
344 verbose_name
= u
"Employé"
345 verbose_name_plural
= u
"Employés"
347 def __unicode__(self
):
348 return u
'%s [%s]' % (self
.get_nom(), self
.id)
351 nom_affichage
= self
.nom_affichage
352 if not nom_affichage
:
353 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
358 if self
.genre
.upper() == u
'M':
360 elif self
.genre
.upper() == u
'F':
365 """Retourne l'URL du service retournant la photo de l'Employe.
366 Équivalent reverse url 'rh_photo' avec id en param.
368 from django
.core
.urlresolvers
import reverse
369 return reverse('rh_photo', kwargs
={'id':self
.id})
371 def dossiers_passes(self
):
373 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
374 for d
in dossiers_passes
:
376 return dossiers_passes
378 def dossiers_futurs(self
):
380 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
382 def dossiers_encours(self
):
383 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
384 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
385 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
387 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
388 for d
in dossiers_encours
:
389 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
390 return dossiers_encours
392 def postes_encours(self
):
393 postes_encours
= set()
394 for d
in self
.dossiers_encours():
395 postes_encours
.add(d
.poste
)
396 return postes_encours
398 def poste_principal(self
):
400 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
402 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
404 poste
= Poste
.objects
.none()
406 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
411 prefix_implantation
= "dossiers__poste__implantation__region"
412 def get_regions(self
):
414 for d
in self
.dossiers
.all():
415 regions
.append(d
.poste
.implantation
.region
)
419 class EmployeInactif(Employe
):
422 ordering
= ['nom_affichage','nom','prenom']
423 verbose_name
= u
"Employé inactif"
424 verbose_name_plural
= u
"Employés inactifs"
427 class EmployePiece(models
.Model
):
428 """Documents relatifs à un employé.
431 employe
= models
.ForeignKey('Employe', db_column
='employe')
432 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
433 fichier
= models
.FileField(verbose_name
="Fichier",
434 upload_to
=employe_piece_dispatch
,
435 storage
=storage_prive
)
439 verbose_name
= u
"Employé pièce"
440 verbose_name_plural
= u
"Employé pièces"
442 def __unicode__(self
):
443 return u
'%s' % (self
.nom
)
445 class EmployeCommentaire(Commentaire
):
446 employe
= models
.ForeignKey('Employe', db_column
='employe',
450 verbose_name
= u
"Employé commentaire"
451 verbose_name_plural
= u
"Employé commentaires"
454 LIEN_PARENTE_CHOICES
= (
455 ('Conjoint', 'Conjoint'),
456 ('Conjointe', 'Conjointe'),
461 class AyantDroit(AUFMetadata
):
462 """Personne en relation avec un Employe.
465 nom
= models
.CharField(max_length
=255)
466 prenom
= models
.CharField(max_length
=255,
467 verbose_name
= u
"Prénom",)
468 nom_affichage
= models
.CharField(max_length
=255,
469 verbose_name
= u
"Nom d'affichage",
470 null
=True, blank
=True)
471 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
472 db_column
='nationalite',
473 related_name
='ayantdroits_nationalite',
474 verbose_name
= u
"Nationalité")
475 date_naissance
= models
.DateField(help_text
=HELP_TEXT_DATE
,
476 verbose_name
= u
"Date de naissance",
477 validators
=[validate_date_passee
],
478 null
=True, blank
=True)
479 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
482 employe
= models
.ForeignKey('Employe', db_column
='employe',
483 related_name
='ayantdroits',
484 verbose_name
= u
"Employé")
485 lien_parente
= models
.CharField(max_length
=10,
486 choices
=LIEN_PARENTE_CHOICES
,
487 verbose_name
= u
"Lien de parenté",
488 null
=True, blank
=True)
491 ordering
= ['nom_affichage']
492 verbose_name
= u
"Ayant droit"
493 verbose_name_plural
= u
"Ayants droit"
495 def __unicode__(self
):
496 return u
'%s' % (self
.get_nom())
499 nom_affichage
= self
.nom_affichage
500 if not nom_affichage
:
501 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
504 prefix_implantation
= "employe__dossiers__poste__implantation__region"
505 def get_regions(self
):
507 for d
in self
.employe
.dossiers
.all():
508 regions
.append(d
.poste
.implantation
.region
)
512 class AyantDroitCommentaire(Commentaire
):
513 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
519 STATUT_RESIDENCE_CHOICES
= (
521 ('expat', 'Expatrié'),
524 COMPTE_COMPTA_CHOICES
= (
530 class Dossier_(AUFMetadata
):
531 """Le Dossier regroupe les informations relatives à l'occupation
532 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
535 Plusieurs Contrats peuvent être associés au Dossier.
536 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
537 lequel aucun Dossier n'existe est un poste vacant.
540 employe
= models
.ForeignKey('Employe', db_column
='employe',
541 related_name
='dossiers',
542 verbose_name
=u
"Employé")
544 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='dossiers')
545 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
547 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
548 db_column
='organisme_bstg',
550 verbose_name
= u
"Organisme",
551 help_text
="Si détaché (DET) ou \
552 mis à disposition (MAD), \
553 préciser l'organisme.",
554 null
=True, blank
=True)
557 remplacement
= models
.BooleanField(default
=False)
558 remplacement_de
= models
.ForeignKey('self', related_name
='+',
559 null
=True, blank
=True)
560 statut_residence
= models
.CharField(max_length
=10, default
='local',
561 verbose_name
= u
"Statut", null
=True,
562 choices
=STATUT_RESIDENCE_CHOICES
)
565 classement
= models
.ForeignKey('Classement', db_column
='classement',
567 null
=True, blank
=True)
568 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
570 default
=REGIME_TRAVAIL_DEFAULT
,
571 verbose_name
= u
"Régime de travail",
572 help_text
="% du temps complet")
573 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
574 decimal_places
=2, null
=True,
575 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
576 verbose_name
= u
"Nb. heures par semaine")
578 # Occupation du Poste par cet Employe (anciennement "mandat")
579 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
581 help_text
=HELP_TEXT_DATE
)
582 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
584 help_text
=HELP_TEXT_DATE
,
585 null
=True, blank
=True)
592 ordering
= ['employe__nom', ]
593 verbose_name
= u
"Dossier"
594 verbose_name_plural
= "Dossiers"
596 def salaire_theorique(self
):
597 annee
= date
.today().year
598 coeff
= self
.classement
.coefficient
599 implantation
= self
.poste
.implantation
600 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
602 montant
= coeff
* point
.valeur
603 devise
= point
.devise
604 return {'montant':montant
, 'devise':devise
}
606 def __unicode__(self
):
607 poste
= self
.poste
.nom
608 if self
.employe
.genre
== 'F':
609 poste
= self
.poste
.nom_feminin
610 return u
'%s - %s' % (self
.employe
, poste
)
612 prefix_implantation
= "poste__implantation__region"
613 def get_regions(self
):
614 return [self
.poste
.implantation
.region
]
617 class Dossier(Dossier_
):
618 __doc__
= Dossier_
.__doc__
621 class DossierInactif(Dossier
):
624 ordering
= ['employe__nom', ]
625 verbose_name
= u
"Dossier inactif"
626 verbose_name_plural
= u
"Dossiers inactifs"
629 class DossierPiece(models
.Model
):
630 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
631 Ex.: Lettre de motivation.
633 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
635 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
636 fichier
= models
.FileField(verbose_name
= u
"Fichier",
637 upload_to
=dossier_piece_dispatch
,
638 storage
=storage_prive
)
643 def __unicode__(self
):
644 return u
'%s' % (self
.nom
)
646 class DossierCommentaire(Commentaire
):
647 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
650 class DossierComparaison(models
.Model
):
652 Photo d'une comparaison salariale au moment de l'embauche.
654 dossier
= models
.ForeignKey('Dossier', related_name
='comparaisons')
655 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
656 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
657 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
658 montant
= models
.IntegerField(null
=True)
659 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
661 def taux_devise(self
):
662 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.dossier
.poste
.implantation
)
663 if len(liste_taux
) == 0:
664 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.dossier
.poste
.implantation
))
666 return liste_taux
[0].taux
668 def montant_euros(self
):
669 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
674 class RemunerationMixin(AUFMetadata
):
676 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
677 related_name
='%(app_label)s_%(class)s_remunerations')
678 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
680 verbose_name
= u
"Type de rémunération")
681 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
682 db_column
='type_revalorisation',
684 verbose_name
= u
"Type de revalorisation",
685 null
=True, blank
=True)
686 montant
= models
.FloatField(null
=True, blank
=True,
688 # Annuel (12 mois, 52 semaines, 364 jours?)
689 devise
= models
.ForeignKey('Devise', to_field
='id',
690 db_column
='devise', related_name
='+',
692 # commentaire = precision
693 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
694 # date_debut = anciennement date_effectif
695 date_debut
= models
.DateField(help_text
=HELP_TEXT_DATE
,
696 verbose_name
= u
"Date de début",
697 null
=True, blank
=True)
698 date_fin
= models
.DateField(help_text
=HELP_TEXT_DATE
,
699 verbose_name
= u
"Date de fin",
700 null
=True, blank
=True)
704 ordering
= ['type__nom', '-date_fin']
706 def __unicode__(self
):
707 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
709 class Remuneration_(RemunerationMixin
):
710 """Structure de rémunération (données budgétaires) en situation normale
711 pour un Dossier. Si un Evenement existe, utiliser la structure de
712 rémunération EvenementRemuneration de cet événement.
715 def montant_mois(self
):
716 return round(self
.montant
/ 12, 2)
718 def taux_devise(self
):
719 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
721 def montant_euro(self
):
722 return round(float(self
.montant
) / float(self
.taux_devise()), 2)
724 def montant_euro_mois(self
):
725 return round(self
.montant_euro() / 12, 2)
727 def __unicode__(self
):
729 devise
= self
.devise
.code
732 return "%s %s" % (self
.montant
, devise
)
736 verbose_name
= u
"Rémunération"
737 verbose_name_plural
= u
"Rémunérations"
740 class Remuneration(Remuneration_
):
741 __doc__
= Remuneration_
.__doc__
746 class ContratManager(NoDeleteManager
):
747 def get_query_set(self
):
748 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
751 class Contrat(AUFMetadata
):
752 """Document juridique qui encadre la relation de travail d'un Employe
753 pour un Poste particulier. Pour un Dossier (qui documente cette
754 relation de travail) plusieurs contrats peuvent être associés.
757 objects
= ContratManager()
759 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
760 related_name
='contrats')
761 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
763 verbose_name
= u
"Type de contrat")
764 date_debut
= models
.DateField(help_text
=HELP_TEXT_DATE
,
765 verbose_name
= u
"Date de début")
766 date_fin
= models
.DateField(help_text
=HELP_TEXT_DATE
,
767 verbose_name
= u
"Date de fin",
768 null
=True, blank
=True)
771 ordering
= ['dossier__employe__nom_affichage']
772 verbose_name
= u
"Contrat"
773 verbose_name_plural
= u
"Contrats"
775 def __unicode__(self
):
776 return u
'%s - %s' % (self
.dossier
, self
.id)
778 # TODO? class ContratPiece(models.Model):
783 class Evenement_(AUFMetadata
):
784 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
785 d'un Dossier qui vient altérer des informations normales liées à un Dossier
786 (ex.: la Remuneration).
788 Ex.: congé de maternité, maladie...
790 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
791 différent et une rémunération en conséquence. On souhaite toutefois
792 conserver le Dossier intact afin d'éviter une re-saisie des données lors
793 du retour à la normale.
795 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
797 nom
= models
.CharField(max_length
=255)
798 date_debut
= models
.DateField(help_text
=HELP_TEXT_DATE
,
799 verbose_name
= u
"Date de début")
800 date_fin
= models
.DateField(help_text
=HELP_TEXT_DATE
,
801 verbose_name
= u
"Date de fin",
802 null
=True, blank
=True)
807 verbose_name
= u
"Évènement"
808 verbose_name_plural
= u
"Évènements"
810 def __unicode__(self
):
811 return u
'%s' % (self
.nom
)
814 class Evenement(Evenement_
):
815 __doc__
= Evenement_
.__doc__
818 class EvenementRemuneration_(RemunerationMixin
):
819 """Structure de rémunération liée à un Evenement qui remplace
820 temporairement la Remuneration normale d'un Dossier, pour toute la durée
823 evenement
= models
.ForeignKey("Evenement", db_column
='evenement',
825 verbose_name
= u
"Évènement")
826 # TODO : le champ dossier hérité de Remuneration doit être dérivé
827 # de l'Evenement associé
831 ordering
= ['evenement', 'type__nom', '-date_fin']
832 verbose_name
= u
"Évènement - rémunération"
833 verbose_name_plural
= u
"Évènements - rémunérations"
836 class EvenementRemuneration(EvenementRemuneration_
):
837 __doc__
= EvenementRemuneration_
.__doc__
843 class EvenementRemuneration(EvenementRemuneration_
):
844 __doc__
= EvenementRemuneration_
.__doc__
849 class FamilleEmploi(AUFMetadata
):
850 """Catégorie utilisée dans la gestion des Postes.
851 Catégorie supérieure à TypePoste.
853 nom
= models
.CharField(max_length
=255)
857 verbose_name
= u
"Famille d'emploi"
858 verbose_name_plural
= u
"Familles d'emploi"
860 def __unicode__(self
):
861 return u
'%s' % (self
.nom
)
863 class TypePoste(AUFMetadata
):
864 """Catégorie de Poste.
866 nom
= models
.CharField(max_length
=255)
867 nom_feminin
= models
.CharField(max_length
=255,
868 verbose_name
= u
"Nom féminin")
870 is_responsable
= models
.BooleanField(default
=False,
871 verbose_name
= u
"Poste de responsabilité")
872 famille_emploi
= models
.ForeignKey('FamilleEmploi',
873 db_column
='famille_emploi',
875 verbose_name
= u
"Famille d'emploi")
879 verbose_name
= u
"Type de poste"
880 verbose_name_plural
= u
"Types de poste"
882 def __unicode__(self
):
883 return u
'%s' % (self
.nom
)
886 TYPE_PAIEMENT_CHOICES
= (
887 ('Régulier', 'Régulier'),
888 ('Ponctuel', 'Ponctuel'),
891 NATURE_REMUNERATION_CHOICES
= (
892 ('Accessoire', 'Accessoire'),
893 ('Charges', 'Charges'),
894 ('Indemnité', 'Indemnité'),
895 ('RAS', 'Rémunération autre source'),
896 ('Traitement', 'Traitement'),
899 class TypeRemuneration(AUFMetadata
):
900 """Catégorie de Remuneration.
902 nom
= models
.CharField(max_length
=255)
903 type_paiement
= models
.CharField(max_length
=30,
904 choices
=TYPE_PAIEMENT_CHOICES
,
905 verbose_name
= u
"Type de paiement")
906 nature_remuneration
= models
.CharField(max_length
=30,
907 choices
=NATURE_REMUNERATION_CHOICES
,
908 verbose_name
= u
"Nature de la rémunération")
912 verbose_name
= u
"Type de rémunération"
913 verbose_name_plural
= u
"Types de rémunération"
915 def __unicode__(self
):
916 return u
'%s' % (self
.nom
)
918 class TypeRevalorisation(AUFMetadata
):
919 """Justification du changement de la Remuneration.
920 (Actuellement utilisé dans aucun traitement informatique.)
922 nom
= models
.CharField(max_length
=255)
926 verbose_name
= u
"Type de revalorisation"
927 verbose_name_plural
= u
"Types de revalorisation"
929 def __unicode__(self
):
930 return u
'%s' % (self
.nom
)
932 class Service(AUFMetadata
):
933 """Unité administrative où les Postes sont rattachés.
935 nom
= models
.CharField(max_length
=255)
939 verbose_name
= u
"Service"
940 verbose_name_plural
= u
"Services"
942 def __unicode__(self
):
943 return u
'%s' % (self
.nom
)
946 TYPE_ORGANISME_CHOICES
= (
947 ('MAD', 'Mise à disposition'),
948 ('DET', 'Détachement'),
951 class OrganismeBstg(AUFMetadata
):
952 """Organisation d'où provient un Employe mis à disposition (MAD) de
953 ou détaché (DET) à l'AUF à titre gratuit.
955 (BSTG = bien et service à titre gratuit.)
957 nom
= models
.CharField(max_length
=255)
958 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
959 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
961 related_name
='organismes_bstg',
962 null
=True, blank
=True)
965 ordering
= ['type', 'nom']
966 verbose_name
= u
"Organisme BSTG"
967 verbose_name_plural
= u
"Organismes BSTG"
969 def __unicode__(self
):
970 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
972 prefix_implantation
= "pays__region"
973 def get_regions(self
):
974 return [self
.pays
.region
]
977 class Statut(AUFMetadata
):
978 """Statut de l'Employe dans le cadre d'un Dossier particulier.
981 code
= models
.CharField(max_length
=25, unique
=True)
982 nom
= models
.CharField(max_length
=255)
986 verbose_name
= u
"Statut d'employé"
987 verbose_name_plural
= u
"Statuts d'employé"
989 def __unicode__(self
):
990 return u
'%s : %s' % (self
.code
, self
.nom
)
993 TYPE_CLASSEMENT_CHOICES
= (
995 ('T', 'T - Technicien'),
996 ('P', 'P - Professionel'),
998 ('D', 'D - Direction'),
999 ('SO', 'SO - Sans objet [expatriés]'),
1000 ('HG', 'HG - Hors grille [direction]'),
1004 class Classement_(AUFMetadata
):
1005 """Éléments de classement de la
1006 "Grille générique de classement hiérarchique".
1008 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1009 classement dans la grille. Le classement donne le coefficient utilisé dans:
1011 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1014 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1015 echelon
= models
.IntegerField(verbose_name
= u
"Échelon")
1016 degre
= models
.IntegerField(verbose_name
= u
"Degré")
1017 coefficient
= models
.FloatField(default
=0, verbose_name
= u
"Coéfficient",
1020 # annee # au lieu de date_debut et date_fin
1021 commentaire
= models
.TextField(null
=True, blank
=True)
1025 ordering
= ['type','echelon','degre','coefficient']
1026 verbose_name
= u
"Classement"
1027 verbose_name_plural
= u
"Classements"
1029 def __unicode__(self
):
1030 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1033 class Classement(Classement_
):
1034 __doc__
= Classement_
.__doc__
1037 class TauxChange_(AUFMetadata
):
1038 """Taux de change de la devise vers l'euro (EUR)
1039 pour chaque année budgétaire.
1042 devise
= models
.ForeignKey('Devise', db_column
='devise')
1043 annee
= models
.IntegerField(verbose_name
= u
"Année")
1044 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1048 ordering
= ['-annee', 'devise__code']
1049 verbose_name
= u
"Taux de change"
1050 verbose_name_plural
= u
"Taux de change"
1052 def __unicode__(self
):
1053 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1056 class TauxChange(TauxChange_
):
1057 __doc__
= TauxChange_
.__doc__
1059 class ValeurPointManager(NoDeleteManager
):
1060 def get_query_set(self
):
1061 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1064 class ValeurPoint_(AUFMetadata
):
1065 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1066 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1067 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1069 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1072 objects
= ValeurPointManager()
1074 valeur
= models
.FloatField(null
=True)
1075 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1076 related_name
='+', default
=5)
1077 implantation
= models
.ForeignKey(ref
.Implantation
,
1078 db_column
='implantation',
1079 related_name
='%(app_label)s_valeur_point')
1081 annee
= models
.IntegerField()
1084 ordering
= ['-annee', 'implantation__nom']
1086 verbose_name
= u
"Valeur du point"
1087 verbose_name_plural
= u
"Valeurs du point"
1089 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
1090 def get_tauxchange_courant(self
):
1092 Recherche le taux courant associé à la valeur d'un point.
1093 Tous les taux de l'année courante sont chargés, pour optimiser un
1094 affichage en liste. (On pourrait probablement améliorer le manager pour
1095 lui greffer le taux courant sous forme de JOIN)
1097 for tauxchange
in self
.tauxchange
:
1098 if tauxchange
.implantation_id
== self
.implantation_id
:
1102 def __unicode__(self
):
1103 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1106 class ValeurPoint(ValeurPoint_
):
1107 __doc__
= ValeurPoint_
.__doc__
1110 class Devise(AUFMetadata
):
1111 """Devise monétaire.
1113 code
= models
.CharField(max_length
=10, unique
=True)
1114 nom
= models
.CharField(max_length
=255)
1118 verbose_name
= u
"Devise"
1119 verbose_name_plural
= u
"Devises"
1121 def __unicode__(self
):
1122 return u
'%s - %s' % (self
.code
, self
.nom
)
1124 class TypeContrat(AUFMetadata
):
1127 nom
= models
.CharField(max_length
=255)
1128 nom_long
= models
.CharField(max_length
=255)
1132 verbose_name
= u
"Type de contrat"
1133 verbose_name_plural
= u
"Types de contrat"
1135 def __unicode__(self
):
1136 return u
'%s' % (self
.nom
)
1141 class ResponsableImplantation(AUFMetadata
):
1142 """Le responsable d'une implantation.
1143 Anciennement géré sur le Dossier du responsable.
1145 employe
= models
.ForeignKey('Employe', db_column
='employe',
1147 null
=True, blank
=True)
1148 implantation
= models
.ForeignKey(ref
.Implantation
,
1149 db_column
='implantation', related_name
='+',
1152 def __unicode__(self
):
1153 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1156 ordering
= ['implantation__nom']
1157 verbose_name
= "Responsable d'implantation"
1158 verbose_name_plural
= "Responsables d'implantation"