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
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
.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 poste
= models
.ForeignKey('Poste', db_column
='poste',
223 related_name
='%(app_label)s_financements')
224 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
225 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
226 help_text
="ex.: 33.33 % (décimale avec point)")
227 commentaire
= models
.TextField(
228 help_text
="Spécifiez la source de financement.")
234 def __unicode__(self
):
235 return u
'%s : %s %' % (self
.type, self
.pourcentage
)
238 class PosteFinancement(PosteFinancement_
):
239 __doc__
= PosteFinancement_
.__doc__
242 class PostePiece(models
.Model
):
243 """Documents relatifs au Poste.
244 Ex.: Description de poste
246 poste
= models
.ForeignKey('Poste', db_column
='poste',
247 related_name
='pieces')
248 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
249 fichier
= models
.FileField(verbose_name
= u
"Fichier",
250 upload_to
=poste_piece_dispatch
,
251 storage
=storage_prive
)
256 def __unicode__(self
):
257 return u
'%s' % (self
.nom
)
259 class PosteComparaison(models
.Model
):
261 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
263 poste
= models
.ForeignKey('Poste', related_name
='comparaisons_internes')
264 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
265 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
266 montant
= models
.IntegerField(null
=True)
267 devise
= models
.ForeignKey("Devise", default
=5, related_name
='+', null
=True, blank
=True)
269 def taux_devise(self
):
270 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.implantation
)
271 if len(liste_taux
) == 0:
272 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.implantation
))
274 return liste_taux
[0].taux
276 def montant_euros(self
):
277 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
280 class PosteCommentaire(Commentaire
):
281 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='+')
290 SITUATION_CHOICES
= (
291 ('C', 'Célibataire'),
296 class Employe(AUFMetadata
):
297 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
298 Dossiers qu'il occupe ou a occupé de Postes.
300 Cette classe aurait pu avantageusement s'appeler Personne car la notion
301 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
304 nom
= models
.CharField(max_length
=255)
305 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
306 nom_affichage
= models
.CharField(max_length
=255,
307 verbose_name
= u
"Nom d'affichage",
308 null
=True, blank
=True)
309 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
310 db_column
='nationalite',
311 related_name
='employes_nationalite',
312 verbose_name
= u
"Nationalité")
313 date_naissance
= models
.DateField(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 null
=True, blank
=True)
327 tel_domicile
= models
.CharField(max_length
=255,
328 verbose_name
= u
"Tél. domicile",
329 null
=True, blank
=True)
330 tel_cellulaire
= models
.CharField(max_length
=255,
331 verbose_name
= u
"Tél. cellulaire",
332 null
=True, blank
=True)
333 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
334 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
335 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
336 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
337 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
338 related_name
='employes',
339 null
=True, blank
=True)
342 ordering
= ['nom_affichage','nom','prenom']
343 verbose_name
= u
"Employé"
344 verbose_name_plural
= u
"Employés"
346 def __unicode__(self
):
347 return u
'%s [%s]' % (self
.get_nom(), self
.id)
350 nom_affichage
= self
.nom_affichage
351 if not nom_affichage
:
352 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
357 if self
.genre
.upper() == u
'M':
359 elif self
.genre
.upper() == u
'F':
364 """Retourne l'URL du service retournant la photo de l'Employe.
365 Équivalent reverse url 'rh_photo' avec id en param.
367 from django
.core
.urlresolvers
import reverse
368 return reverse('rh_photo', kwargs
={'id':self
.id})
370 def dossiers_passes(self
):
372 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
373 for d
in dossiers_passes
:
375 return dossiers_passes
377 def dossiers_futurs(self
):
379 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
381 def dossiers_encours(self
):
382 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
383 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
384 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
386 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
387 for d
in dossiers_encours
:
388 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
389 return dossiers_encours
391 def postes_encours(self
):
392 postes_encours
= set()
393 for d
in self
.dossiers_encours():
394 postes_encours
.add(d
.poste
)
395 return postes_encours
397 def poste_principal(self
):
399 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
401 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
403 poste
= Poste
.objects
.none()
405 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
410 prefix_implantation
= "dossiers__poste__implantation__region"
411 def get_regions(self
):
413 for d
in self
.dossiers
.all():
414 regions
.append(d
.poste
.implantation
.region
)
418 class EmployePiece(models
.Model
):
419 """Documents relatifs à un employé.
422 employe
= models
.ForeignKey('Employe', db_column
='employe')
423 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
424 fichier
= models
.FileField(verbose_name
="Fichier",
425 upload_to
=employe_piece_dispatch
,
426 storage
=storage_prive
)
430 verbose_name
= u
"Employé pièce"
431 verbose_name_plural
= u
"Employé pièces"
433 def __unicode__(self
):
434 return u
'%s' % (self
.nom
)
436 class EmployeCommentaire(Commentaire
):
437 employe
= models
.ForeignKey('Employe', db_column
='employe',
441 verbose_name
= u
"Employé commentaire"
442 verbose_name_plural
= u
"Employé commentaires"
445 LIEN_PARENTE_CHOICES
= (
446 ('Conjoint', 'Conjoint'),
447 ('Conjointe', 'Conjointe'),
452 class AyantDroit(AUFMetadata
):
453 """Personne en relation avec un Employe.
456 nom
= models
.CharField(max_length
=255)
457 prenom
= models
.CharField(max_length
=255,
458 verbose_name
= u
"Prénom",)
459 nom_affichage
= models
.CharField(max_length
=255,
460 verbose_name
= u
"Nom d'affichage",
461 null
=True, blank
=True)
462 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
463 db_column
='nationalite',
464 related_name
='ayantdroits_nationalite',
465 verbose_name
= u
"Nationalité")
466 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
467 validators
=[validate_date_passee
],
468 null
=True, blank
=True)
469 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
472 employe
= models
.ForeignKey('Employe', db_column
='employe',
473 related_name
='ayantdroits',
474 verbose_name
= u
"Employé")
475 lien_parente
= models
.CharField(max_length
=10,
476 choices
=LIEN_PARENTE_CHOICES
,
477 verbose_name
= u
"Lien de parenté",
478 null
=True, blank
=True)
481 ordering
= ['nom_affichage']
482 verbose_name
= u
"Ayant droit"
483 verbose_name_plural
= u
"Ayants droit"
485 def __unicode__(self
):
486 return u
'%s' % (self
.get_nom())
489 nom_affichage
= self
.nom_affichage
490 if not nom_affichage
:
491 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
494 prefix_implantation
= "employe__dossiers__poste__implantation__region"
495 def get_regions(self
):
497 for d
in self
.employe
.dossiers
.all():
498 regions
.append(d
.poste
.implantation
.region
)
502 class AyantDroitCommentaire(Commentaire
):
503 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
509 STATUT_RESIDENCE_CHOICES
= (
511 ('expat', 'Expatrié'),
514 COMPTE_COMPTA_CHOICES
= (
520 class Dossier_(AUFMetadata
):
521 """Le Dossier regroupe les informations relatives à l'occupation
522 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
525 Plusieurs Contrats peuvent être associés au Dossier.
526 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
527 lequel aucun Dossier n'existe est un poste vacant.
530 objects
= DossierManager()
533 employe
= models
.ForeignKey('Employe', db_column
='employe',
534 related_name
='dossiers',
535 verbose_name
=u
"Employé")
537 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='dossiers')
538 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
540 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
541 db_column
='organisme_bstg',
543 verbose_name
= u
"Organisme",
544 help_text
="Si détaché (DET) ou \
545 mis à disposition (MAD), \
546 préciser l'organisme.",
547 null
=True, blank
=True)
550 remplacement
= models
.BooleanField(default
=False)
551 remplacement_de
= models
.ForeignKey('self', related_name
='+',
552 null
=True, blank
=True)
553 statut_residence
= models
.CharField(max_length
=10, default
='local',
554 verbose_name
= u
"Statut", null
=True,
555 choices
=STATUT_RESIDENCE_CHOICES
)
558 classement
= models
.ForeignKey('Classement', db_column
='classement',
560 null
=True, blank
=True)
561 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
563 default
=REGIME_TRAVAIL_DEFAULT
,
564 verbose_name
= u
"Régime de travail",
565 help_text
="% du temps complet")
566 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
567 decimal_places
=2, null
=True,
568 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
569 verbose_name
= u
"Nb. heures par semaine")
571 # Occupation du Poste par cet Employe (anciennement "mandat")
572 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
574 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
576 null
=True, blank
=True)
583 ordering
= ['employe__nom', ]
584 verbose_name
= u
"Dossier"
585 verbose_name_plural
= "Dossiers"
587 def salaire_theorique(self
):
588 annee
= date
.today().year
589 coeff
= self
.classement
.coefficient
590 implantation
= self
.poste
.implantation
591 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
593 montant
= coeff
* point
.valeur
594 devise
= point
.devise
595 return {'montant':montant
, 'devise':devise
}
597 def __unicode__(self
):
598 poste
= self
.poste
.nom
599 if self
.employe
.genre
== 'F':
600 poste
= self
.poste
.nom_feminin
601 return u
'%s - %s' % (self
.employe
, poste
)
603 prefix_implantation
= "poste__implantation__region"
604 def get_regions(self
):
605 return [self
.poste
.implantation
.region
]
608 def remunerations(self
):
609 return self
.rh_remuneration_remunerations
.all().order_by('date_debut')
611 def get_salaire(self
):
613 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
617 class Dossier(Dossier_
):
618 __doc__
= Dossier_
.__doc__
621 class DossierPiece(models
.Model
):
622 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
623 Ex.: Lettre de motivation.
625 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
627 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
628 fichier
= models
.FileField(verbose_name
= u
"Fichier",
629 upload_to
=dossier_piece_dispatch
,
630 storage
=storage_prive
)
635 def __unicode__(self
):
636 return u
'%s' % (self
.nom
)
638 class DossierCommentaire(Commentaire
):
639 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
642 class DossierComparaison(models
.Model
):
644 Photo d'une comparaison salariale au moment de l'embauche.
646 dossier
= models
.ForeignKey('Dossier', related_name
='comparaisons')
647 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
648 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
649 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
650 montant
= models
.IntegerField(null
=True)
651 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
653 def taux_devise(self
):
654 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.dossier
.poste
.implantation
)
655 if len(liste_taux
) == 0:
656 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.dossier
.poste
.implantation
))
658 return liste_taux
[0].taux
660 def montant_euros(self
):
661 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
666 class RemunerationMixin(AUFMetadata
):
668 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
669 related_name
='%(app_label)s_%(class)s_remunerations')
670 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
672 verbose_name
= u
"Type de rémunération")
673 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
674 db_column
='type_revalorisation',
676 verbose_name
= u
"Type de revalorisation",
677 null
=True, blank
=True)
678 montant
= models
.FloatField(null
=True, blank
=True,
680 # Annuel (12 mois, 52 semaines, 364 jours?)
681 devise
= models
.ForeignKey('Devise', to_field
='id',
682 db_column
='devise', related_name
='+',
684 # commentaire = precision
685 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
686 # date_debut = anciennement date_effectif
687 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
688 null
=True, blank
=True)
689 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
690 null
=True, blank
=True)
694 ordering
= ['type__nom', '-date_fin']
696 def __unicode__(self
):
697 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
699 class Remuneration_(RemunerationMixin
):
700 """Structure de rémunération (données budgétaires) en situation normale
701 pour un Dossier. Si un Evenement existe, utiliser la structure de
702 rémunération EvenementRemuneration de cet événement.
705 def montant_mois(self
):
706 return round(self
.montant
/ 12, 2)
708 def taux_devise(self
):
709 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
711 def montant_euro(self
):
712 return round(float(self
.montant
) / float(self
.taux_devise()), 2)
714 def montant_euro_mois(self
):
715 return round(self
.montant_euro() / 12, 2)
717 def __unicode__(self
):
719 devise
= self
.devise
.code
722 return "%s %s" % (self
.montant
, devise
)
726 verbose_name
= u
"Rémunération"
727 verbose_name_plural
= u
"Rémunérations"
730 class Remuneration(Remuneration_
):
731 __doc__
= Remuneration_
.__doc__
736 class ContratManager(NoDeleteManager
):
737 def get_query_set(self
):
738 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
741 class Contrat(AUFMetadata
):
742 """Document juridique qui encadre la relation de travail d'un Employe
743 pour un Poste particulier. Pour un Dossier (qui documente cette
744 relation de travail) plusieurs contrats peuvent être associés.
747 objects
= ContratManager()
749 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
750 related_name
='contrats')
751 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
753 verbose_name
= u
"Type de contrat")
754 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
755 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
756 null
=True, blank
=True)
759 ordering
= ['dossier__employe__nom_affichage']
760 verbose_name
= u
"Contrat"
761 verbose_name_plural
= u
"Contrats"
763 def __unicode__(self
):
764 return u
'%s - %s' % (self
.dossier
, self
.id)
766 # TODO? class ContratPiece(models.Model):
771 class Evenement_(AUFMetadata
):
772 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
773 d'un Dossier qui vient altérer des informations normales liées à un Dossier
774 (ex.: la Remuneration).
776 Ex.: congé de maternité, maladie...
778 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
779 différent et une rémunération en conséquence. On souhaite toutefois
780 conserver le Dossier intact afin d'éviter une re-saisie des données lors
781 du retour à la normale.
783 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
785 nom
= models
.CharField(max_length
=255)
786 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
787 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
788 null
=True, blank
=True)
793 verbose_name
= u
"Évènement"
794 verbose_name_plural
= u
"Évènements"
796 def __unicode__(self
):
797 return u
'%s' % (self
.nom
)
800 class Evenement(Evenement_
):
801 __doc__
= Evenement_
.__doc__
804 class EvenementRemuneration_(RemunerationMixin
):
805 """Structure de rémunération liée à un Evenement qui remplace
806 temporairement la Remuneration normale d'un Dossier, pour toute la durée
809 evenement
= models
.ForeignKey("Evenement", db_column
='evenement',
811 verbose_name
= u
"Évènement")
812 # TODO : le champ dossier hérité de Remuneration doit être dérivé
813 # de l'Evenement associé
817 ordering
= ['evenement', 'type__nom', '-date_fin']
818 verbose_name
= u
"Évènement - rémunération"
819 verbose_name_plural
= u
"Évènements - rémunérations"
822 class EvenementRemuneration(EvenementRemuneration_
):
823 __doc__
= EvenementRemuneration_
.__doc__
829 class EvenementRemuneration(EvenementRemuneration_
):
830 __doc__
= EvenementRemuneration_
.__doc__
835 class FamilleEmploi(AUFMetadata
):
836 """Catégorie utilisée dans la gestion des Postes.
837 Catégorie supérieure à TypePoste.
839 nom
= models
.CharField(max_length
=255)
843 verbose_name
= u
"Famille d'emploi"
844 verbose_name_plural
= u
"Familles d'emploi"
846 def __unicode__(self
):
847 return u
'%s' % (self
.nom
)
849 class TypePoste(AUFMetadata
):
850 """Catégorie de Poste.
852 nom
= models
.CharField(max_length
=255)
853 nom_feminin
= models
.CharField(max_length
=255,
854 verbose_name
= u
"Nom féminin")
856 is_responsable
= models
.BooleanField(default
=False,
857 verbose_name
= u
"Poste de responsabilité")
858 famille_emploi
= models
.ForeignKey('FamilleEmploi',
859 db_column
='famille_emploi',
861 verbose_name
= u
"Famille d'emploi")
865 verbose_name
= u
"Type de poste"
866 verbose_name_plural
= u
"Types de poste"
868 def __unicode__(self
):
869 return u
'%s' % (self
.nom
)
872 TYPE_PAIEMENT_CHOICES
= (
873 ('Régulier', 'Régulier'),
874 ('Ponctuel', 'Ponctuel'),
877 NATURE_REMUNERATION_CHOICES
= (
878 ('Accessoire', 'Accessoire'),
879 ('Charges', 'Charges'),
880 ('Indemnité', 'Indemnité'),
881 ('RAS', 'Rémunération autre source'),
882 ('Traitement', 'Traitement'),
885 class TypeRemuneration(AUFMetadata
):
886 """Catégorie de Remuneration.
888 nom
= models
.CharField(max_length
=255)
889 type_paiement
= models
.CharField(max_length
=30,
890 choices
=TYPE_PAIEMENT_CHOICES
,
891 verbose_name
= u
"Type de paiement")
892 nature_remuneration
= models
.CharField(max_length
=30,
893 choices
=NATURE_REMUNERATION_CHOICES
,
894 verbose_name
= u
"Nature de la rémunération")
898 verbose_name
= u
"Type de rémunération"
899 verbose_name_plural
= u
"Types de rémunération"
901 def __unicode__(self
):
902 return u
'%s' % (self
.nom
)
904 class TypeRevalorisation(AUFMetadata
):
905 """Justification du changement de la Remuneration.
906 (Actuellement utilisé dans aucun traitement informatique.)
908 nom
= models
.CharField(max_length
=255)
912 verbose_name
= u
"Type de revalorisation"
913 verbose_name_plural
= u
"Types de revalorisation"
915 def __unicode__(self
):
916 return u
'%s' % (self
.nom
)
918 class Service(AUFMetadata
):
919 """Unité administrative où les Postes sont rattachés.
921 nom
= models
.CharField(max_length
=255)
925 verbose_name
= u
"Service"
926 verbose_name_plural
= u
"Services"
928 def __unicode__(self
):
929 return u
'%s' % (self
.nom
)
932 TYPE_ORGANISME_CHOICES
= (
933 ('MAD', 'Mise à disposition'),
934 ('DET', 'Détachement'),
937 class OrganismeBstg(AUFMetadata
):
938 """Organisation d'où provient un Employe mis à disposition (MAD) de
939 ou détaché (DET) à l'AUF à titre gratuit.
941 (BSTG = bien et service à titre gratuit.)
943 nom
= models
.CharField(max_length
=255)
944 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
945 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
947 related_name
='organismes_bstg',
948 null
=True, blank
=True)
951 ordering
= ['type', 'nom']
952 verbose_name
= u
"Organisme BSTG"
953 verbose_name_plural
= u
"Organismes BSTG"
955 def __unicode__(self
):
956 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
958 prefix_implantation
= "pays__region"
959 def get_regions(self
):
960 return [self
.pays
.region
]
963 class Statut(AUFMetadata
):
964 """Statut de l'Employe dans le cadre d'un Dossier particulier.
967 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.")
968 nom
= models
.CharField(max_length
=255)
972 verbose_name
= u
"Statut d'employé"
973 verbose_name_plural
= u
"Statuts d'employé"
975 def __unicode__(self
):
976 return u
'%s : %s' % (self
.code
, self
.nom
)
979 TYPE_CLASSEMENT_CHOICES
= (
981 ('T', 'T - Technicien'),
982 ('P', 'P - Professionel'),
984 ('D', 'D - Direction'),
985 ('SO', 'SO - Sans objet [expatriés]'),
986 ('HG', 'HG - Hors grille [direction]'),
990 class Classement_(AUFMetadata
):
991 """Éléments de classement de la
992 "Grille générique de classement hiérarchique".
994 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
995 classement dans la grille. Le classement donne le coefficient utilisé dans:
997 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1000 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1001 echelon
= models
.IntegerField(verbose_name
= u
"Échelon")
1002 degre
= models
.IntegerField(verbose_name
= u
"Degré")
1003 coefficient
= models
.FloatField(default
=0, verbose_name
= u
"Coéfficient",
1006 # annee # au lieu de date_debut et date_fin
1007 commentaire
= models
.TextField(null
=True, blank
=True)
1011 ordering
= ['type','echelon','degre','coefficient']
1012 verbose_name
= u
"Classement"
1013 verbose_name_plural
= u
"Classements"
1015 def __unicode__(self
):
1016 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1019 class Classement(Classement_
):
1020 __doc__
= Classement_
.__doc__
1023 class TauxChange_(AUFMetadata
):
1024 """Taux de change de la devise vers l'euro (EUR)
1025 pour chaque année budgétaire.
1028 devise
= models
.ForeignKey('Devise', db_column
='devise')
1029 annee
= models
.IntegerField(verbose_name
= u
"Année")
1030 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1034 ordering
= ['-annee', 'devise__code']
1035 verbose_name
= u
"Taux de change"
1036 verbose_name_plural
= u
"Taux de change"
1038 def __unicode__(self
):
1039 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1042 class TauxChange(TauxChange_
):
1043 __doc__
= TauxChange_
.__doc__
1045 class ValeurPointManager(NoDeleteManager
):
1046 def get_query_set(self
):
1047 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1050 class ValeurPoint_(AUFMetadata
):
1051 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1052 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1053 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1055 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1058 actuelles
= ValeurPointManager()
1060 valeur
= models
.FloatField(null
=True)
1061 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1062 related_name
='+', default
=5)
1063 implantation
= models
.ForeignKey(ref
.Implantation
,
1064 db_column
='implantation',
1065 related_name
='%(app_label)s_valeur_point')
1067 annee
= models
.IntegerField()
1070 ordering
= ['-annee', 'implantation__nom']
1072 verbose_name
= u
"Valeur du point"
1073 verbose_name_plural
= u
"Valeurs du point"
1075 def __unicode__(self
):
1076 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1079 class ValeurPoint(ValeurPoint_
):
1080 __doc__
= ValeurPoint_
.__doc__
1083 class Devise(AUFMetadata
):
1084 """Devise monétaire.
1086 code
= models
.CharField(max_length
=10, unique
=True)
1087 nom
= models
.CharField(max_length
=255)
1091 verbose_name
= u
"Devise"
1092 verbose_name_plural
= u
"Devises"
1094 def __unicode__(self
):
1095 return u
'%s - %s' % (self
.code
, self
.nom
)
1097 class TypeContrat(AUFMetadata
):
1100 nom
= models
.CharField(max_length
=255)
1101 nom_long
= models
.CharField(max_length
=255)
1105 verbose_name
= u
"Type de contrat"
1106 verbose_name_plural
= u
"Types de contrat"
1108 def __unicode__(self
):
1109 return u
'%s' % (self
.nom
)
1114 class ResponsableImplantation(AUFMetadata
):
1115 """Le responsable d'une implantation.
1116 Anciennement géré sur le Dossier du responsable.
1118 employe
= models
.ForeignKey('Employe', db_column
='employe',
1120 null
=True, blank
=True)
1121 implantation
= models
.ForeignKey(ref
.Implantation
,
1122 db_column
='implantation', related_name
='+',
1125 def __unicode__(self
):
1126 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1129 ordering
= ['implantation__nom']
1130 verbose_name
= "Responsable d'implantation"
1131 verbose_name_plural
= "Responsables d'implantation"