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 auf
.django
.references
.models
as ref
12 from validators
import validate_date_passee
13 from managers
import PosteManager
, DossierManager
, DossierComparaisonManager
, PosteComparaisonManager
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 Poste_(AUFMetadata
):
57 """Un Poste est un emploi (job) à combler dans une implantation.
58 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
59 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
62 objects
= PosteManager()
65 nom
= models
.CharField(max_length
=255,
66 verbose_name
= u
"Titre du poste", )
67 nom_feminin
= models
.CharField(max_length
=255,
68 verbose_name
= u
"Titre du poste (au féminin)",
70 implantation
= models
.ForeignKey(ref
.Implantation
,
71 db_column
='implantation', related_name
='+')
72 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste',
75 service
= models
.ForeignKey('Service', db_column
='service', null
=True,
77 verbose_name
= u
"Direction/Service/Pôle support", )
78 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
79 related_name
='+', null
=True,
80 verbose_name
= u
"Poste du responsable", )
83 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
84 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
85 verbose_name
= u
"Temps de travail",
86 help_text
="% du temps complet")
87 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
88 decimal_places
=2, null
=True,
89 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
90 verbose_name
= u
"Nb. heures par semaine")
93 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
94 null
=True, blank
=True)
95 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
96 null
=True, blank
=True)
97 mise_a_disposition
= models
.NullBooleanField(
98 verbose_name
= u
"Mise à disposition",
99 null
=True, default
=False)
100 appel
= models
.CharField(max_length
=10, null
=True,
101 verbose_name
= u
"Appel à candidature",
102 choices
=POSTE_APPEL_CHOICES
,
106 classement_min
= models
.ForeignKey('Classement',
107 db_column
='classement_min', related_name
='+',
108 null
=True, blank
=True)
109 classement_max
= models
.ForeignKey('Classement',
110 db_column
='classement_max', related_name
='+',
111 null
=True, blank
=True)
112 valeur_point_min
= models
.ForeignKey('ValeurPoint',
113 db_column
='valeur_point_min', related_name
='+',
114 null
=True, blank
=True)
115 valeur_point_max
= models
.ForeignKey('ValeurPoint',
116 db_column
='valeur_point_max', related_name
='+',
117 null
=True, blank
=True)
118 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
119 related_name
='+', default
=5)
120 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
121 related_name
='+', default
=5)
122 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
123 null
=True, default
=0)
124 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
125 null
=True, default
=0)
126 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
127 null
=True, default
=0)
128 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
129 null
=True, default
=0)
130 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
131 null
=True, default
=0)
132 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
133 null
=True, default
=0)
135 # Comparatifs de rémunération
136 devise_comparaison
= models
.ForeignKey('Devise', null
=True,
137 db_column
='devise_comparaison',
140 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
141 null
=True, blank
=True)
142 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
143 null
=True, blank
=True)
144 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
145 null
=True, blank
=True)
146 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
147 null
=True, blank
=True)
148 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
149 null
=True, blank
=True)
150 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
151 null
=True, blank
=True)
152 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
153 null
=True, blank
=True)
154 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
155 null
=True, blank
=True)
156 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
157 null
=True, blank
=True)
158 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
159 null
=True, blank
=True)
162 justification
= models
.TextField(null
=True, blank
=True)
165 date_validation
= models
.DateTimeField(null
=True, blank
=True) # de dae
166 date_debut
= models
.DateField(verbose_name
=u
"Date de début",
167 null
=True, blank
=True)
168 date_fin
= models
.DateField(verbose_name
=u
"Date de fin",
169 null
=True, blank
=True)
173 ordering
= ['implantation__nom', 'nom']
174 verbose_name
= u
"Poste"
175 verbose_name_plural
= u
"Postes"
177 def __unicode__(self
):
178 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
181 representation
= representation
+ u
' (VACANT)'
182 return representation
186 if self
.occupe_par():
190 def occupe_par(self
):
191 """Retourne la liste d'employé occupant ce poste.
192 Généralement, retourne une liste d'un élément.
193 Si poste inoccupé, retourne liste vide.
195 return [d
.employe
for d
in self
.rh_dossiers
.filter(actif
=True, supprime
=False) \
196 .exclude(date_fin__lt
=date
.today())]
198 prefix_implantation
= "implantation__region"
199 def get_regions(self
):
200 return [self
.implantation
.region
]
204 __doc__
= Poste_
.__doc__
208 __doc__
= Poste_
.__doc__
211 POSTE_FINANCEMENT_CHOICES
= (
212 ('A', 'A - Frais de personnel'),
213 ('B', 'B - Projet(s)-Titre(s)'),
218 class PosteFinancement_(models
.Model
):
219 """Pour un Poste, structure d'informations décrivant comment on prévoit
222 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
223 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
224 help_text
="ex.: 33.33 % (décimale avec point)")
225 commentaire
= models
.TextField(
226 help_text
="Spécifiez la source de financement.")
232 def __unicode__(self
):
233 return u
'%s : %s %' % (self
.type, self
.pourcentage
)
236 class PosteFinancement(PosteFinancement_
):
237 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='%(app_label)s_financements')
240 class PostePiece_(models
.Model
):
241 """Documents relatifs au Poste.
242 Ex.: Description de poste
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
)
253 def __unicode__(self
):
254 return u
'%s' % (self
.nom
)
256 class PostePiece(PostePiece_
):
257 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='%(app_label)s_pieces')
259 class PosteComparaison_(models
.Model
):
261 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
263 objects
= PosteComparaisonManager()
265 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
266 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
267 montant
= models
.IntegerField(null
=True)
268 devise
= models
.ForeignKey("Devise", default
=5, related_name
='+', null
=True, blank
=True)
273 def taux_devise(self
):
274 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.implantation
)
275 if len(liste_taux
) == 0:
276 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.implantation
))
278 return liste_taux
[0].taux
280 def montant_euros(self
):
281 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
283 class PosteComparaison(PosteComparaison_
):
284 poste
= models
.ForeignKey('Poste', related_name
='%(app_label)s_comparaisons_internes')
286 class PosteCommentaire_(Commentaire
):
287 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='+')
292 class PosteCommentaire(PosteCommentaire_
):
301 SITUATION_CHOICES
= (
302 ('C', 'Célibataire'),
307 class Employe(AUFMetadata
):
308 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
309 Dossiers qu'il occupe ou a occupé de Postes.
311 Cette classe aurait pu avantageusement s'appeler Personne car la notion
312 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
315 nom
= models
.CharField(max_length
=255)
316 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
317 nom_affichage
= models
.CharField(max_length
=255,
318 verbose_name
= u
"Nom d'affichage",
319 null
=True, blank
=True)
320 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
321 db_column
='nationalite',
322 related_name
='employes_nationalite',
323 verbose_name
= u
"Nationalité")
324 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
325 validators
=[validate_date_passee
],
326 null
=True, blank
=True)
327 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
330 situation_famille
= models
.CharField(max_length
=1,
331 choices
=SITUATION_CHOICES
,
332 verbose_name
= u
"Situation familiale",
333 null
=True, blank
=True)
334 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
335 null
=True, blank
=True)
338 tel_domicile
= models
.CharField(max_length
=255,
339 verbose_name
= u
"Tél. domicile",
340 null
=True, blank
=True)
341 tel_cellulaire
= models
.CharField(max_length
=255,
342 verbose_name
= u
"Tél. cellulaire",
343 null
=True, blank
=True)
344 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
345 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
346 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
347 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
348 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
349 related_name
='employes',
350 null
=True, blank
=True)
353 ordering
= ['nom_affichage','nom','prenom']
354 verbose_name
= u
"Employé"
355 verbose_name_plural
= u
"Employés"
357 def __unicode__(self
):
358 return u
'%s [%s]' % (self
.get_nom(), self
.id)
361 nom_affichage
= self
.nom_affichage
362 if not nom_affichage
:
363 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
368 if self
.genre
.upper() == u
'M':
370 elif self
.genre
.upper() == u
'F':
375 """Retourne l'URL du service retournant la photo de l'Employe.
376 Équivalent reverse url 'rh_photo' avec id en param.
378 from django
.core
.urlresolvers
import reverse
379 return reverse('rh_photo', kwargs
={'id':self
.id})
381 def dossiers_passes(self
):
383 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
384 for d
in dossiers_passes
:
386 return dossiers_passes
388 def dossiers_futurs(self
):
390 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
392 def dossiers_encours(self
):
393 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
394 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
395 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
397 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
398 for d
in dossiers_encours
:
399 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
400 return dossiers_encours
402 def postes_encours(self
):
403 postes_encours
= set()
404 for d
in self
.dossiers_encours():
405 postes_encours
.add(d
.poste
)
406 return postes_encours
408 def poste_principal(self
):
410 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
412 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
414 poste
= Poste
.objects
.none()
416 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
421 prefix_implantation
= "dossiers__poste__implantation__region"
422 def get_regions(self
):
424 for d
in self
.dossiers
.all():
425 regions
.append(d
.poste
.implantation
.region
)
429 class EmployePiece(models
.Model
):
430 """Documents relatifs à un employé.
433 employe
= models
.ForeignKey('Employe', db_column
='employe')
434 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
435 fichier
= models
.FileField(verbose_name
="Fichier",
436 upload_to
=employe_piece_dispatch
,
437 storage
=storage_prive
)
441 verbose_name
= u
"Employé pièce"
442 verbose_name_plural
= u
"Employé pièces"
444 def __unicode__(self
):
445 return u
'%s' % (self
.nom
)
447 class EmployeCommentaire(Commentaire
):
448 employe
= models
.ForeignKey('Employe', db_column
='employe',
452 verbose_name
= u
"Employé commentaire"
453 verbose_name_plural
= u
"Employé commentaires"
456 LIEN_PARENTE_CHOICES
= (
457 ('Conjoint', 'Conjoint'),
458 ('Conjointe', 'Conjointe'),
463 class AyantDroit(AUFMetadata
):
464 """Personne en relation avec un Employe.
467 nom
= models
.CharField(max_length
=255)
468 prenom
= models
.CharField(max_length
=255,
469 verbose_name
= u
"Prénom",)
470 nom_affichage
= models
.CharField(max_length
=255,
471 verbose_name
= u
"Nom d'affichage",
472 null
=True, blank
=True)
473 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
474 db_column
='nationalite',
475 related_name
='ayantdroits_nationalite',
476 verbose_name
= u
"Nationalité")
477 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
478 validators
=[validate_date_passee
],
479 null
=True, blank
=True)
480 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
483 employe
= models
.ForeignKey('Employe', db_column
='employe',
484 related_name
='ayantdroits',
485 verbose_name
= u
"Employé")
486 lien_parente
= models
.CharField(max_length
=10,
487 choices
=LIEN_PARENTE_CHOICES
,
488 verbose_name
= u
"Lien de parenté",
489 null
=True, blank
=True)
492 ordering
= ['nom_affichage']
493 verbose_name
= u
"Ayant droit"
494 verbose_name_plural
= u
"Ayants droit"
496 def __unicode__(self
):
497 return u
'%s' % (self
.get_nom())
500 nom_affichage
= self
.nom_affichage
501 if not nom_affichage
:
502 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
505 prefix_implantation
= "employe__dossiers__poste__implantation__region"
506 def get_regions(self
):
508 for d
in self
.employe
.dossiers
.all():
509 regions
.append(d
.poste
.implantation
.region
)
513 class AyantDroitCommentaire(Commentaire
):
514 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
520 STATUT_RESIDENCE_CHOICES
= (
522 ('expat', 'Expatrié'),
525 COMPTE_COMPTA_CHOICES
= (
531 class Dossier_(AUFMetadata
):
532 """Le Dossier regroupe les informations relatives à l'occupation
533 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
536 Plusieurs Contrats peuvent être associés au Dossier.
537 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
538 lequel aucun Dossier n'existe est un poste vacant.
541 objects
= DossierManager()
544 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
546 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
547 db_column
='organisme_bstg',
549 verbose_name
= u
"Organisme",
550 help_text
="Si détaché (DET) ou \
551 mis à disposition (MAD), \
552 préciser l'organisme.",
553 null
=True, blank
=True)
556 remplacement
= models
.BooleanField(default
=False)
557 remplacement_de
= models
.ForeignKey('self', related_name
='+',
558 null
=True, blank
=True)
559 statut_residence
= models
.CharField(max_length
=10, default
='local',
560 verbose_name
= u
"Statut", null
=True,
561 choices
=STATUT_RESIDENCE_CHOICES
)
564 classement
= models
.ForeignKey('Classement', db_column
='classement',
566 null
=True, blank
=True)
567 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
569 default
=REGIME_TRAVAIL_DEFAULT
,
570 verbose_name
= u
"Régime de travail",
571 help_text
="% du temps complet")
572 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
573 decimal_places
=2, null
=True,
574 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
575 verbose_name
= u
"Nb. heures par semaine")
577 # Occupation du Poste par cet Employe (anciennement "mandat")
578 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
580 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
582 null
=True, blank
=True)
589 ordering
= ['employe__nom', ]
590 verbose_name
= u
"Dossier"
591 verbose_name_plural
= "Dossiers"
593 def salaire_theorique(self
):
594 annee
= date
.today().year
595 coeff
= self
.classement
.coefficient
596 implantation
= self
.poste
.implantation
597 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
599 montant
= coeff
* point
.valeur
600 devise
= point
.devise
601 return {'montant':montant
, 'devise':devise
}
603 def __unicode__(self
):
604 poste
= self
.poste
.nom
605 if self
.employe
.genre
== 'F':
606 poste
= self
.poste
.nom_feminin
607 return u
'%s - %s' % (self
.employe
, poste
)
609 prefix_implantation
= "poste__implantation__region"
610 def get_regions(self
):
611 return [self
.poste
.implantation
.region
]
614 def remunerations(self
):
615 return self
.rh_remuneration_remunerations
.all().order_by('date_debut')
617 def get_salaire(self
):
619 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
623 class Dossier(Dossier_
):
624 __doc__
= Dossier_
.__doc__
625 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='%(app_label)s_dossiers')
626 employe
= models
.ForeignKey('Employe', db_column
='employe',
627 related_name
='%(app_label)s_dossiers',
628 verbose_name
=u
"Employé")
631 class DossierPiece(models
.Model
):
632 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
633 Ex.: Lettre de motivation.
635 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
637 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
638 fichier
= models
.FileField(verbose_name
= u
"Fichier",
639 upload_to
=dossier_piece_dispatch
,
640 storage
=storage_prive
)
645 def __unicode__(self
):
646 return u
'%s' % (self
.nom
)
648 class DossierCommentaire(Commentaire
):
649 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
652 class DossierComparaison(models
.Model
):
654 Photo d'une comparaison salariale au moment de l'embauche.
657 objects
= DossierComparaisonManager()
659 dossier
= models
.ForeignKey('Dossier', related_name
='comparaisons')
660 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
661 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
662 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
663 montant
= models
.IntegerField(null
=True)
664 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
666 def taux_devise(self
):
667 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.dossier
.poste
.implantation
)
668 if len(liste_taux
) == 0:
669 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.dossier
.poste
.implantation
))
671 return liste_taux
[0].taux
673 def montant_euros(self
):
674 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
679 class RemunerationMixin(AUFMetadata
):
681 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
682 related_name
='%(app_label)s_%(class)s_remunerations')
683 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
685 verbose_name
= u
"Type de rémunération")
686 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
687 db_column
='type_revalorisation',
689 verbose_name
= u
"Type de revalorisation",
690 null
=True, blank
=True)
691 montant
= models
.FloatField(null
=True, blank
=True,
693 # Annuel (12 mois, 52 semaines, 364 jours?)
694 devise
= models
.ForeignKey('Devise', to_field
='id',
695 db_column
='devise', related_name
='+',
697 # commentaire = precision
698 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
699 # date_debut = anciennement date_effectif
700 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
701 null
=True, blank
=True)
702 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
703 null
=True, blank
=True)
707 ordering
= ['type__nom', '-date_fin']
709 def __unicode__(self
):
710 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
712 class Remuneration_(RemunerationMixin
):
713 """Structure de rémunération (données budgétaires) en situation normale
714 pour un Dossier. Si un Evenement existe, utiliser la structure de
715 rémunération EvenementRemuneration de cet événement.
718 def montant_mois(self
):
719 return round(self
.montant
/ 12, 2)
721 def taux_devise(self
):
722 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
724 def montant_euro(self
):
725 return round(float(self
.montant
) / float(self
.taux_devise()), 2)
727 def montant_euro_mois(self
):
728 return round(self
.montant_euro() / 12, 2)
730 def __unicode__(self
):
732 devise
= self
.devise
.code
735 return "%s %s" % (self
.montant
, devise
)
739 verbose_name
= u
"Rémunération"
740 verbose_name_plural
= u
"Rémunérations"
743 class Remuneration(Remuneration_
):
744 __doc__
= Remuneration_
.__doc__
749 class ContratManager(NoDeleteManager
):
750 def get_query_set(self
):
751 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
754 class Contrat(AUFMetadata
):
755 """Document juridique qui encadre la relation de travail d'un Employe
756 pour un Poste particulier. Pour un Dossier (qui documente cette
757 relation de travail) plusieurs contrats peuvent être associés.
760 objects
= ContratManager()
762 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
763 related_name
='contrats')
764 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
766 verbose_name
= u
"Type de contrat")
767 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
768 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
769 null
=True, blank
=True)
772 ordering
= ['dossier__employe__nom_affichage']
773 verbose_name
= u
"Contrat"
774 verbose_name_plural
= u
"Contrats"
776 def __unicode__(self
):
777 return u
'%s - %s' % (self
.dossier
, self
.id)
779 # TODO? class ContratPiece(models.Model):
784 class Evenement_(AUFMetadata
):
785 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
786 d'un Dossier qui vient altérer des informations normales liées à un Dossier
787 (ex.: la Remuneration).
789 Ex.: congé de maternité, maladie...
791 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
792 différent et une rémunération en conséquence. On souhaite toutefois
793 conserver le Dossier intact afin d'éviter une re-saisie des données lors
794 du retour à la normale.
796 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
798 nom
= models
.CharField(max_length
=255)
799 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
800 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
801 null
=True, blank
=True)
806 verbose_name
= u
"Évènement"
807 verbose_name_plural
= u
"Évènements"
809 def __unicode__(self
):
810 return u
'%s' % (self
.nom
)
813 class Evenement(Evenement_
):
814 __doc__
= Evenement_
.__doc__
817 class EvenementRemuneration_(RemunerationMixin
):
818 """Structure de rémunération liée à un Evenement qui remplace
819 temporairement la Remuneration normale d'un Dossier, pour toute la durée
822 evenement
= models
.ForeignKey("Evenement", db_column
='evenement',
824 verbose_name
= u
"Évènement")
825 # TODO : le champ dossier hérité de Remuneration doit être dérivé
826 # de l'Evenement associé
830 ordering
= ['evenement', 'type__nom', '-date_fin']
831 verbose_name
= u
"Évènement - rémunération"
832 verbose_name_plural
= u
"Évènements - rémunérations"
835 class EvenementRemuneration(EvenementRemuneration_
):
836 __doc__
= EvenementRemuneration_
.__doc__
842 class EvenementRemuneration(EvenementRemuneration_
):
843 __doc__
= EvenementRemuneration_
.__doc__
848 class FamilleEmploi(AUFMetadata
):
849 """Catégorie utilisée dans la gestion des Postes.
850 Catégorie supérieure à TypePoste.
852 nom
= models
.CharField(max_length
=255)
856 verbose_name
= u
"Famille d'emploi"
857 verbose_name_plural
= u
"Familles d'emploi"
859 def __unicode__(self
):
860 return u
'%s' % (self
.nom
)
862 class TypePoste(AUFMetadata
):
863 """Catégorie de Poste.
865 nom
= models
.CharField(max_length
=255)
866 nom_feminin
= models
.CharField(max_length
=255,
867 verbose_name
= u
"Nom féminin")
869 is_responsable
= models
.BooleanField(default
=False,
870 verbose_name
= u
"Poste de responsabilité")
871 famille_emploi
= models
.ForeignKey('FamilleEmploi',
872 db_column
='famille_emploi',
874 verbose_name
= u
"Famille d'emploi")
878 verbose_name
= u
"Type de poste"
879 verbose_name_plural
= u
"Types de poste"
881 def __unicode__(self
):
882 return u
'%s' % (self
.nom
)
885 TYPE_PAIEMENT_CHOICES
= (
886 ('Régulier', 'Régulier'),
887 ('Ponctuel', 'Ponctuel'),
890 NATURE_REMUNERATION_CHOICES
= (
891 ('Accessoire', 'Accessoire'),
892 ('Charges', 'Charges'),
893 ('Indemnité', 'Indemnité'),
894 ('RAS', 'Rémunération autre source'),
895 ('Traitement', 'Traitement'),
898 class TypeRemuneration(AUFMetadata
):
899 """Catégorie de Remuneration.
901 nom
= models
.CharField(max_length
=255)
902 type_paiement
= models
.CharField(max_length
=30,
903 choices
=TYPE_PAIEMENT_CHOICES
,
904 verbose_name
= u
"Type de paiement")
905 nature_remuneration
= models
.CharField(max_length
=30,
906 choices
=NATURE_REMUNERATION_CHOICES
,
907 verbose_name
= u
"Nature de la rémunération")
911 verbose_name
= u
"Type de rémunération"
912 verbose_name_plural
= u
"Types de rémunération"
914 def __unicode__(self
):
915 return u
'%s' % (self
.nom
)
917 class TypeRevalorisation(AUFMetadata
):
918 """Justification du changement de la Remuneration.
919 (Actuellement utilisé dans aucun traitement informatique.)
921 nom
= models
.CharField(max_length
=255)
925 verbose_name
= u
"Type de revalorisation"
926 verbose_name_plural
= u
"Types de revalorisation"
928 def __unicode__(self
):
929 return u
'%s' % (self
.nom
)
931 class Service(AUFMetadata
):
932 """Unité administrative où les Postes sont rattachés.
934 nom
= models
.CharField(max_length
=255)
938 verbose_name
= u
"Service"
939 verbose_name_plural
= u
"Services"
941 def __unicode__(self
):
942 return u
'%s' % (self
.nom
)
945 TYPE_ORGANISME_CHOICES
= (
946 ('MAD', 'Mise à disposition'),
947 ('DET', 'Détachement'),
950 class OrganismeBstg(AUFMetadata
):
951 """Organisation d'où provient un Employe mis à disposition (MAD) de
952 ou détaché (DET) à l'AUF à titre gratuit.
954 (BSTG = bien et service à titre gratuit.)
956 nom
= models
.CharField(max_length
=255)
957 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
958 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
960 related_name
='organismes_bstg',
961 null
=True, blank
=True)
964 ordering
= ['type', 'nom']
965 verbose_name
= u
"Organisme BSTG"
966 verbose_name_plural
= u
"Organismes BSTG"
968 def __unicode__(self
):
969 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
971 prefix_implantation
= "pays__region"
972 def get_regions(self
):
973 return [self
.pays
.region
]
976 class Statut(AUFMetadata
):
977 """Statut de l'Employe dans le cadre d'un Dossier particulier.
980 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.")
981 nom
= models
.CharField(max_length
=255)
985 verbose_name
= u
"Statut d'employé"
986 verbose_name_plural
= u
"Statuts d'employé"
988 def __unicode__(self
):
989 return u
'%s : %s' % (self
.code
, self
.nom
)
992 TYPE_CLASSEMENT_CHOICES
= (
994 ('T', 'T - Technicien'),
995 ('P', 'P - Professionel'),
997 ('D', 'D - Direction'),
998 ('SO', 'SO - Sans objet [expatriés]'),
999 ('HG', 'HG - Hors grille [direction]'),
1003 class Classement_(AUFMetadata
):
1004 """Éléments de classement de la
1005 "Grille générique de classement hiérarchique".
1007 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1008 classement dans la grille. Le classement donne le coefficient utilisé dans:
1010 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1013 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1014 echelon
= models
.IntegerField(verbose_name
= u
"Échelon")
1015 degre
= models
.IntegerField(verbose_name
= u
"Degré")
1016 coefficient
= models
.FloatField(default
=0, verbose_name
= u
"Coéfficient",
1019 # annee # au lieu de date_debut et date_fin
1020 commentaire
= models
.TextField(null
=True, blank
=True)
1024 ordering
= ['type','echelon','degre','coefficient']
1025 verbose_name
= u
"Classement"
1026 verbose_name_plural
= u
"Classements"
1028 def __unicode__(self
):
1029 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1032 class Classement(Classement_
):
1033 __doc__
= Classement_
.__doc__
1036 class TauxChange_(AUFMetadata
):
1037 """Taux de change de la devise vers l'euro (EUR)
1038 pour chaque année budgétaire.
1041 devise
= models
.ForeignKey('Devise', db_column
='devise')
1042 annee
= models
.IntegerField(verbose_name
= u
"Année")
1043 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1047 ordering
= ['-annee', 'devise__code']
1048 verbose_name
= u
"Taux de change"
1049 verbose_name_plural
= u
"Taux de change"
1051 def __unicode__(self
):
1052 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1055 class TauxChange(TauxChange_
):
1056 __doc__
= TauxChange_
.__doc__
1058 class ValeurPointManager(NoDeleteManager
):
1059 def get_query_set(self
):
1060 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1063 class ValeurPoint_(AUFMetadata
):
1064 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1065 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1066 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1068 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1071 actuelles
= ValeurPointManager()
1073 valeur
= models
.FloatField(null
=True)
1074 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1075 related_name
='+', default
=5)
1076 implantation
= models
.ForeignKey(ref
.Implantation
,
1077 db_column
='implantation',
1078 related_name
='%(app_label)s_valeur_point')
1080 annee
= models
.IntegerField()
1083 ordering
= ['-annee', 'implantation__nom']
1085 verbose_name
= u
"Valeur du point"
1086 verbose_name_plural
= u
"Valeurs du point"
1088 def __unicode__(self
):
1089 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1092 class ValeurPoint(ValeurPoint_
):
1093 __doc__
= ValeurPoint_
.__doc__
1096 class Devise(AUFMetadata
):
1097 """Devise monétaire.
1099 code
= models
.CharField(max_length
=10, unique
=True)
1100 nom
= models
.CharField(max_length
=255)
1104 verbose_name
= u
"Devise"
1105 verbose_name_plural
= u
"Devises"
1107 def __unicode__(self
):
1108 return u
'%s - %s' % (self
.code
, self
.nom
)
1110 class TypeContrat(AUFMetadata
):
1113 nom
= models
.CharField(max_length
=255)
1114 nom_long
= models
.CharField(max_length
=255)
1118 verbose_name
= u
"Type de contrat"
1119 verbose_name_plural
= u
"Types de contrat"
1121 def __unicode__(self
):
1122 return u
'%s' % (self
.nom
)
1127 class ResponsableImplantation(AUFMetadata
):
1128 """Le responsable d'une implantation.
1129 Anciennement géré sur le Dossier du responsable.
1131 employe
= models
.ForeignKey('Employe', db_column
='employe',
1133 null
=True, blank
=True)
1134 implantation
= models
.ForeignKey(ref
.Implantation
,
1135 db_column
='implantation', related_name
='+',
1138 def __unicode__(self
):
1139 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1142 ordering
= ['implantation__nom']
1143 verbose_name
= "Responsable d'implantation"
1144 verbose_name_plural
= "Responsables d'implantation"