1 # -=- encoding: utf-8 -=-
4 from datetime
import date
5 from decimal
import Decimal
7 from django
.db
.models
import signals
8 from django
.core
.files
.storage
import FileSystemStorage
9 from django
.db
import models
10 from django
.conf
import settings
12 from auf
.django
.emploi
.models
import GENRE_CHOICES
, SITUATION_CHOICES
# devrait plutot être dans references
13 from auf
.django
.metadata
.models
import AUFMetadata
14 from auf
.django
.metadata
.managers
import NoDeleteManager
15 import auf
.django
.references
.models
as ref
16 from validators
import validate_date_passee
17 from managers
import PosteManager
, DossierManager
, DossierComparaisonManager
, PosteComparaisonManager
20 # Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
21 # Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
24 models_stack
= [s
[1].split('/')[-2] for s
in inspect
.stack() if s
[1].endswith('models.py')]
25 return models_stack
[-1]
29 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
30 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
31 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
34 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
35 base_url
=settings
.PRIVE_MEDIA_URL
)
37 def poste_piece_dispatch(instance
, filename
):
38 path
= "poste/%s/%s" % (instance
.poste_id
, filename
)
41 def dossier_piece_dispatch(instance
, filename
):
42 path
= "dossier/%s/%s" % (instance
.dossier_id
, filename
)
45 def employe_piece_dispatch(instance
, filename
):
46 path
= "employe/%s/%s" % (instance
.employe_id
, filename
)
49 def contrat_dispatch(instance
, filename
):
50 path
= "contrat/%s/%s" % (instance
.dossier_id
, filename
)
54 class Commentaire(AUFMetadata
):
55 texte
= models
.TextField()
56 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+', verbose_name
=u
"Commentaire de")
60 ordering
= ['-date_creation']
62 def __unicode__(self
):
63 return u
'%s' % (self
.texte
)
68 POSTE_APPEL_CHOICES
= (
69 ('interne', 'Interne'),
70 ('externe', 'Externe'),
73 class Poste_(AUFMetadata
):
74 """Un Poste est un emploi (job) à combler dans une implantation.
75 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
76 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
79 objects
= PosteManager()
82 nom
= models
.CharField(max_length
=255,
83 verbose_name
= u
"Titre du poste", )
84 nom_feminin
= models
.CharField(max_length
=255,
85 verbose_name
= u
"Titre du poste (au féminin)",
87 implantation
= models
.ForeignKey(ref
.Implantation
, help_text
=u
"Taper le nom de l'implantation ou sa région",
88 db_column
='implantation', related_name
='+')
89 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste', help_text
=u
"Taper le nom du type de poste",
92 service
= models
.ForeignKey('Service', db_column
='service',
94 verbose_name
= u
"Direction/Service/Pôle support", )
95 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
96 related_name
='+', null
=True,help_text
=u
"Taper le nom du poste ou du type de poste",
97 verbose_name
= u
"Poste du responsable", )
100 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
101 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
102 verbose_name
= u
"Temps de travail",
103 help_text
="% du temps complet")
104 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
105 decimal_places
=2, null
=True,
106 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
107 verbose_name
= u
"Nb. heures par semaine")
110 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
111 null
=True, blank
=True)
112 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
113 null
=True, blank
=True)
114 mise_a_disposition
= models
.NullBooleanField(
115 verbose_name
= u
"Mise à disposition",
116 null
=True, default
=False)
117 appel
= models
.CharField(max_length
=10, null
=True,
118 verbose_name
= u
"Appel à candidature",
119 choices
=POSTE_APPEL_CHOICES
,
123 classement_min
= models
.ForeignKey('Classement',
124 db_column
='classement_min', related_name
='+',
125 null
=True, blank
=True)
126 classement_max
= models
.ForeignKey('Classement',
127 db_column
='classement_max', related_name
='+',
128 null
=True, blank
=True)
129 valeur_point_min
= models
.ForeignKey('ValeurPoint', help_text
=u
"Taper le code ou le nom de l'implantation",
130 db_column
='valeur_point_min', related_name
='+',
131 null
=True, blank
=True)
132 valeur_point_max
= models
.ForeignKey('ValeurPoint', help_text
=u
"Taper le code ou le nom de l'implantation",
133 db_column
='valeur_point_max', related_name
='+',
134 null
=True, blank
=True)
135 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
137 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
139 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
140 null
=True, default
=0)
141 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
142 null
=True, default
=0)
143 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
144 null
=True, default
=0)
145 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
146 null
=True, default
=0)
147 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
148 null
=True, default
=0)
149 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
150 null
=True, default
=0)
152 # Comparatifs de rémunération
153 devise_comparaison
= models
.ForeignKey('Devise', null
=True,
154 db_column
='devise_comparaison',
156 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
157 null
=True, blank
=True)
158 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
159 null
=True, blank
=True)
160 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
161 null
=True, blank
=True)
162 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
163 null
=True, blank
=True)
164 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
165 null
=True, blank
=True)
166 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
167 null
=True, blank
=True)
168 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
169 null
=True, blank
=True)
170 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
171 null
=True, blank
=True)
172 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
173 null
=True, blank
=True)
174 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
175 null
=True, blank
=True)
178 justification
= models
.TextField(null
=True, blank
=True)
181 date_debut
= models
.DateField(verbose_name
=u
"Date de début",
182 null
=True, blank
=True)
183 date_fin
= models
.DateField(verbose_name
=u
"Date de fin",
184 null
=True, blank
=True)
188 ordering
= ['implantation__nom', 'nom']
189 verbose_name
= u
"Poste"
190 verbose_name_plural
= u
"Postes"
192 def __unicode__(self
):
193 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
195 return representation
198 prefix_implantation
= "implantation__region"
199 def get_regions(self
):
200 return [self
.implantation
.region
]
204 __doc__
= Poste_
.__doc__
206 # meta dématérialisation : pour permettre le filtrage
207 vacant
= models
.NullBooleanField(verbose_name
= u
"Vacant", null
=True, blank
=True)
211 if self
.occupe_par():
215 def occupe_par(self
):
216 """Retourne la liste d'employé occupant ce poste.
217 Généralement, retourne une liste d'un élément.
218 Si poste inoccupé, retourne liste vide.
219 UTILISE pour mettre a jour le flag vacant
221 return [d
.employe
for d
in self
.rh_dossiers
.filter(supprime
=False).exclude(date_fin__lt
=date
.today())]
224 POSTE_FINANCEMENT_CHOICES
= (
225 ('A', 'A - Frais de personnel'),
226 ('B', 'B - Projet(s)-Titre(s)'),
231 class PosteFinancement_(models
.Model
):
232 """Pour un Poste, structure d'informations décrivant comment on prévoit
235 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_financements')
236 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
237 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
238 help_text
="ex.: 33.33 % (décimale avec point)")
239 commentaire
= models
.TextField(
240 help_text
="Spécifiez la source de financement.")
246 def __unicode__(self
):
247 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
250 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
253 class PosteFinancement(PosteFinancement_
):
257 class PostePiece_(models
.Model
):
258 """Documents relatifs au Poste.
259 Ex.: Description de poste
261 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_pieces')
262 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
263 fichier
= models
.FileField(verbose_name
= u
"Fichier",
264 upload_to
=poste_piece_dispatch
,
265 storage
=storage_prive
)
271 def __unicode__(self
):
272 return u
'%s' % (self
.nom
)
274 class PostePiece(PostePiece_
):
277 class PosteComparaison_(AUFMetadata
):
279 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
281 poste
= models
.ForeignKey('%s.Poste' % app_context(), related_name
='%(app_label)s_comparaisons_internes')
282 objects
= PosteComparaisonManager()
284 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
285 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
286 montant
= models
.IntegerField(null
=True)
287 devise
= models
.ForeignKey("Devise", related_name
='+', null
=True, blank
=True)
292 def taux_devise(self
):
293 if self
.devise
.code
== "EUR":
295 annee
= self
.poste
.date_debut
.year
296 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
299 raise Exception(u
"Le taux de la devise %s n'a pas n'existe pas pour %s ou il existe plusieurs taux pour la même année" % (self
.devise
.id, annee
))
303 def montant_euros(self
):
304 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
306 class PosteComparaison(PosteComparaison_
):
309 class PosteCommentaire_(Commentaire
):
310 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='+')
315 class PosteCommentaire(PosteCommentaire_
):
320 class Employe(AUFMetadata
):
321 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
322 Dossiers qu'il occupe ou a occupé de Postes.
324 Cette classe aurait pu avantageusement s'appeler Personne car la notion
325 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
328 nom
= models
.CharField(max_length
=255)
329 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
330 nom_affichage
= models
.CharField(max_length
=255,
331 verbose_name
= u
"Nom d'affichage",
332 null
=True, blank
=True)
333 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
334 db_column
='nationalite',
335 related_name
='employes_nationalite',
336 verbose_name
= u
"Nationalité",
337 blank
=True, null
=True)
338 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
339 help_text
=HELP_TEXT_DATE
,
340 validators
=[validate_date_passee
],
341 null
=True, blank
=True)
342 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
345 situation_famille
= models
.CharField(max_length
=1,
346 choices
=SITUATION_CHOICES
,
347 verbose_name
= u
"Situation familiale",
348 null
=True, blank
=True)
349 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
350 help_text
=HELP_TEXT_DATE
,
351 null
=True, blank
=True)
354 tel_domicile
= models
.CharField(max_length
=255,
355 verbose_name
= u
"Tél. domicile",
356 null
=True, blank
=True)
357 tel_cellulaire
= models
.CharField(max_length
=255,
358 verbose_name
= u
"Tél. cellulaire",
359 null
=True, blank
=True)
360 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
361 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
362 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
363 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
364 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
365 related_name
='employes',
366 null
=True, blank
=True)
368 # meta dématérialisation : pour permettre le filtrage
369 nb_postes
= models
.IntegerField(verbose_name
= u
"Nombre de postes", null
=True, blank
=True)
372 ordering
= ['nom','prenom']
373 verbose_name
= u
"Employé"
374 verbose_name_plural
= u
"Employés"
376 def __unicode__(self
):
377 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
381 if self
.genre
.upper() == u
'M':
383 elif self
.genre
.upper() == u
'F':
388 """Retourne l'URL du service retournant la photo de l'Employe.
389 Équivalent reverse url 'rh_photo' avec id en param.
391 from django
.core
.urlresolvers
import reverse
392 return reverse('rh_photo', kwargs
={'id':self
.id})
394 def dossiers_passes(self
):
396 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
397 for d
in dossiers_passes
:
399 return dossiers_passes
401 def dossiers_futurs(self
):
403 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
405 def dossiers_encours(self
):
406 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
407 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
408 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
410 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
411 for d
in dossiers_encours
:
412 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
413 return dossiers_encours
415 def postes_encours(self
):
416 postes_encours
= set()
417 for d
in self
.dossiers_encours():
418 postes_encours
.add(d
.poste
)
419 return postes_encours
421 def poste_principal(self
):
423 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
425 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
427 poste
= Poste
.objects
.none()
429 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
434 prefix_implantation
= "rh_dossiers__poste__implantation__region"
435 def get_regions(self
):
437 for d
in self
.dossiers
.all():
438 regions
.append(d
.poste
.implantation
.region
)
442 class EmployePiece(models
.Model
):
443 """Documents relatifs à un employé.
446 employe
= models
.ForeignKey('Employe', db_column
='employe')
447 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
448 fichier
= models
.FileField(verbose_name
="Fichier",
449 upload_to
=employe_piece_dispatch
,
450 storage
=storage_prive
)
454 verbose_name
= u
"Employé pièce"
455 verbose_name_plural
= u
"Employé pièces"
457 def __unicode__(self
):
458 return u
'%s' % (self
.nom
)
460 class EmployeCommentaire(Commentaire
):
461 employe
= models
.ForeignKey('Employe', db_column
='employe',
465 verbose_name
= u
"Employé commentaire"
466 verbose_name_plural
= u
"Employé commentaires"
469 LIEN_PARENTE_CHOICES
= (
470 ('Conjoint', 'Conjoint'),
471 ('Conjointe', 'Conjointe'),
476 class AyantDroit(AUFMetadata
):
477 """Personne en relation avec un Employe.
480 nom
= models
.CharField(max_length
=255)
481 prenom
= models
.CharField(max_length
=255,
482 verbose_name
= u
"Prénom",)
483 nom_affichage
= models
.CharField(max_length
=255,
484 verbose_name
= u
"Nom d'affichage",
485 null
=True, blank
=True)
486 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
487 db_column
='nationalite',
488 related_name
='ayantdroits_nationalite',
489 verbose_name
= u
"Nationalité",
490 null
=True, blank
=True)
491 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
492 help_text
=HELP_TEXT_DATE
,
493 validators
=[validate_date_passee
],
494 null
=True, blank
=True)
495 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
498 employe
= models
.ForeignKey('Employe', db_column
='employe',
499 related_name
='ayantdroits',
500 verbose_name
= u
"Employé")
501 lien_parente
= models
.CharField(max_length
=10,
502 choices
=LIEN_PARENTE_CHOICES
,
503 verbose_name
= u
"Lien de parenté",
504 null
=True, blank
=True)
508 verbose_name
= u
"Ayant droit"
509 verbose_name_plural
= u
"Ayants droit"
511 def __unicode__(self
):
512 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
514 prefix_implantation
= "employe__dossiers__poste__implantation__region"
515 def get_regions(self
):
517 for d
in self
.employe
.dossiers
.all():
518 regions
.append(d
.poste
.implantation
.region
)
522 class AyantDroitCommentaire(Commentaire
):
523 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
529 STATUT_RESIDENCE_CHOICES
= (
531 ('expat', 'Expatrié'),
534 COMPTE_COMPTA_CHOICES
= (
540 class Dossier_(AUFMetadata
):
541 """Le Dossier regroupe les informations relatives à l'occupation
542 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
545 Plusieurs Contrats peuvent être associés au Dossier.
546 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
547 lequel aucun Dossier n'existe est un poste vacant.
550 objects
= DossierManager()
553 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
554 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
555 db_column
='organisme_bstg',
557 verbose_name
= u
"Organisme",
558 help_text
="Si détaché (DET) ou \
559 mis à disposition (MAD), \
560 préciser l'organisme.",
561 null
=True, blank
=True)
564 remplacement
= models
.BooleanField(default
=False)
565 remplacement_de
= models
.ForeignKey('self', related_name
='+',
566 help_text
=u
"Taper le nom de l'employé",
567 null
=True, blank
=True)
568 statut_residence
= models
.CharField(max_length
=10, default
='local',
569 verbose_name
= u
"Statut", null
=True,
570 choices
=STATUT_RESIDENCE_CHOICES
)
573 classement
= models
.ForeignKey('Classement', db_column
='classement',
575 null
=True, blank
=True)
576 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
578 default
=REGIME_TRAVAIL_DEFAULT
,
579 verbose_name
= u
"Régime de travail",
580 help_text
="% du temps complet")
581 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
582 decimal_places
=2, null
=True,
583 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
584 verbose_name
= u
"Nb. heures par semaine")
586 # Occupation du Poste par cet Employe (anciennement "mandat")
587 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
589 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
591 null
=True, blank
=True)
598 ordering
= ['employe__nom', ]
599 verbose_name
= u
"Dossier"
600 verbose_name_plural
= "Dossiers"
602 def salaire_theorique(self
):
603 annee
= date
.today().year
604 coeff
= self
.classement
.coefficient
605 implantation
= self
.poste
.implantation
606 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
608 montant
= coeff
* point
.valeur
609 devise
= point
.devise
610 return {'montant':montant
, 'devise':devise
}
612 def __unicode__(self
):
613 poste
= self
.poste
.nom
614 if self
.employe
.genre
== 'F':
615 poste
= self
.poste
.nom_feminin
616 return u
'%s - %s' % (self
.employe
, poste
)
618 prefix_implantation
= "poste__implantation__region"
619 def get_regions(self
):
620 return [self
.poste
.implantation
.region
]
623 def remunerations(self
):
624 return self
.rh_remunerations
.all().order_by('date_debut')
626 def remunerations_en_cours(self
):
627 return self
.rh_remunerations
.all().filter(date_fin__exact
=None).order_by('date_debut')
629 def get_salaire(self
):
631 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
635 class Dossier(Dossier_
):
636 __doc__
= Dossier_
.__doc__
637 poste
= models
.ForeignKey('%s.Poste' % app_context(),
639 related_name
='%(app_label)s_dossiers',
640 help_text
=u
"Taper le nom du poste ou du type de poste",
642 employe
= models
.ForeignKey('Employe', db_column
='employe',
643 help_text
=u
"Taper le nom de l'employé",
644 related_name
='%(app_label)s_dossiers',
645 verbose_name
=u
"Employé")
648 class DossierPiece_(models
.Model
):
649 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
650 Ex.: Lettre de motivation.
652 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
653 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
654 fichier
= models
.FileField(verbose_name
= u
"Fichier",
655 upload_to
=dossier_piece_dispatch
,
656 storage
=storage_prive
)
662 def __unicode__(self
):
663 return u
'%s' % (self
.nom
)
665 class DossierPiece(DossierPiece_
):
668 class DossierCommentaire_(Commentaire
):
669 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
673 class DossierCommentaire(DossierCommentaire_
):
676 class DossierComparaison_(models
.Model
):
678 Photo d'une comparaison salariale au moment de l'embauche.
680 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), related_name
='%(app_label)s_comparaisons')
681 objects
= DossierComparaisonManager()
683 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
684 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
685 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
686 montant
= models
.IntegerField(null
=True)
687 devise
= models
.ForeignKey('Devise', related_name
='+', null
=True, blank
=True)
692 def taux_devise(self
):
693 annee
= self
.dossier
.poste
.date_debut
.year
694 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
697 raise Exception(u
"Le taux de la devise %s n'a pas n'existe pas pour %s ou il existe plusieurs taux pour la même année" % (self
.devise
.id, annee
))
701 def montant_euros(self
):
702 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
704 class DossierComparaison(DossierComparaison_
):
709 class RemunerationMixin(AUFMetadata
):
710 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_remunerations')
712 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
714 verbose_name
= u
"Type de rémunération")
715 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
716 db_column
='type_revalorisation',
718 verbose_name
= u
"Type de revalorisation",
719 null
=True, blank
=True)
720 montant
= models
.FloatField(null
=True, blank
=True,
722 # Annuel (12 mois, 52 semaines, 364 jours?)
723 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
724 # commentaire = precision
725 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
726 # date_debut = anciennement date_effectif
727 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
728 null
=True, blank
=True)
729 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
730 null
=True, blank
=True)
734 ordering
= ['type__nom', '-date_fin']
736 def __unicode__(self
):
737 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
739 class Remuneration_(RemunerationMixin
):
740 """Structure de rémunération (données budgétaires) en situation normale
741 pour un Dossier. Si un Evenement existe, utiliser la structure de
742 rémunération EvenementRemuneration de cet événement.
745 def montant_mois(self
):
746 return round(self
.montant
/ 12, 2)
748 def taux_devise(self
):
749 if self
.devise
.code
== "EUR":
752 annee
= datetime
.datetime
.now().year
753 if self
.date_debut
is not None:
754 annee
= self
.date_debut
.year
755 if self
.dossier
.poste
.date_debut
is not None:
756 annee
= self
.dossier
.poste
.date_debut
.year
758 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise_id
, annee
=annee
)]
761 raise Exception(u
"Le taux de la devise %s n'a pas n'existe pas pour %s ou il existe plusieurs taux pour la même année" % (self
.devise
.id, annee
))
765 def montant_euro(self
):
766 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
768 def montant_euro_mois(self
):
769 return round(self
.montant_euro() / 12, 2)
771 def __unicode__(self
):
773 devise
= self
.devise
.code
776 return "%s %s" % (self
.montant
, devise
)
780 verbose_name
= u
"Rémunération"
781 verbose_name_plural
= u
"Rémunérations"
784 class Remuneration(Remuneration_
):
790 class ContratManager(NoDeleteManager
):
791 def get_query_set(self
):
792 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
795 class Contrat_(AUFMetadata
):
796 """Document juridique qui encadre la relation de travail d'un Employe
797 pour un Poste particulier. Pour un Dossier (qui documente cette
798 relation de travail) plusieurs contrats peuvent être associés.
800 objects
= ContratManager()
801 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_contrats')
802 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
804 verbose_name
= u
"Type de contrat")
805 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
806 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
807 null
=True, blank
=True)
808 fichier
= models
.FileField(verbose_name
= u
"Fichier",
809 upload_to
=contrat_dispatch
,
810 storage
=storage_prive
,
811 null
=True, blank
=True)
815 ordering
= ['dossier__employe__nom']
816 verbose_name
= u
"Contrat"
817 verbose_name_plural
= u
"Contrats"
819 def __unicode__(self
):
820 return u
'%s - %s' % (self
.dossier
, self
.id)
822 class Contrat(Contrat_
):
828 #class Evenement_(AUFMetadata):
829 # """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
830 # d'un Dossier qui vient altérer des informations normales liées à un Dossier
831 # (ex.: la Remuneration).
833 # Ex.: congé de maternité, maladie...
835 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
836 # différent et une rémunération en conséquence. On souhaite toutefois
837 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
838 # du retour à la normale.
840 # dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
842 # nom = models.CharField(max_length=255)
843 # date_debut = models.DateField(verbose_name = u"Date de début")
844 # date_fin = models.DateField(verbose_name = u"Date de fin",
845 # null=True, blank=True)
850 # verbose_name = u"Évènement"
851 # verbose_name_plural = u"Évènements"
853 # def __unicode__(self):
854 # return u'%s' % (self.nom)
857 #class Evenement(Evenement_):
858 # __doc__ = Evenement_.__doc__
861 #class EvenementRemuneration_(RemunerationMixin):
862 # """Structure de rémunération liée à un Evenement qui remplace
863 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
866 # evenement = models.ForeignKey("Evenement", db_column='evenement',
868 # verbose_name = u"Évènement")
869 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
870 # # de l'Evenement associé
874 # ordering = ['evenement', 'type__nom', '-date_fin']
875 # verbose_name = u"Évènement - rémunération"
876 # verbose_name_plural = u"Évènements - rémunérations"
879 #class EvenementRemuneration(EvenementRemuneration_):
880 # __doc__ = EvenementRemuneration_.__doc__
886 #class EvenementRemuneration(EvenementRemuneration_):
887 # __doc__ = EvenementRemuneration_.__doc__
888 # TODO? class ContratPiece(models.Model):
893 class FamilleEmploi(AUFMetadata
):
894 """Catégorie utilisée dans la gestion des Postes.
895 Catégorie supérieure à TypePoste.
897 nom
= models
.CharField(max_length
=255)
901 verbose_name
= u
"Famille d'emploi"
902 verbose_name_plural
= u
"Familles d'emploi"
904 def __unicode__(self
):
905 return u
'%s' % (self
.nom
)
907 class TypePoste(AUFMetadata
):
908 """Catégorie de Poste.
910 nom
= models
.CharField(max_length
=255)
911 nom_feminin
= models
.CharField(max_length
=255,
912 verbose_name
= u
"Nom féminin")
914 is_responsable
= models
.BooleanField(default
=False,
915 verbose_name
= u
"Poste de responsabilité")
916 famille_emploi
= models
.ForeignKey('FamilleEmploi',
917 db_column
='famille_emploi',
919 verbose_name
= u
"Famille d'emploi")
923 verbose_name
= u
"Type de poste"
924 verbose_name_plural
= u
"Types de poste"
926 def __unicode__(self
):
927 return u
'%s' % (self
.nom
)
930 TYPE_PAIEMENT_CHOICES
= (
931 (u
'Régulier', u
'Régulier'),
932 (u
'Ponctuel', u
'Ponctuel'),
935 NATURE_REMUNERATION_CHOICES
= (
936 (u
'Accessoire', u
'Accessoire'),
937 (u
'Charges', u
'Charges'),
938 (u
'Indemnité', u
'Indemnité'),
939 (u
'RAS', u
'Rémunération autre source'),
940 (u
'Traitement', u
'Traitement'),
943 class TypeRemuneration(AUFMetadata
):
944 """Catégorie de Remuneration.
946 nom
= models
.CharField(max_length
=255)
947 type_paiement
= models
.CharField(max_length
=30,
948 choices
=TYPE_PAIEMENT_CHOICES
,
949 verbose_name
= u
"Type de paiement")
950 nature_remuneration
= models
.CharField(max_length
=30,
951 choices
=NATURE_REMUNERATION_CHOICES
,
952 verbose_name
= u
"Nature de la rémunération")
956 verbose_name
= u
"Type de rémunération"
957 verbose_name_plural
= u
"Types de rémunération"
959 def __unicode__(self
):
960 return u
'%s' % (self
.nom
)
962 class TypeRevalorisation(AUFMetadata
):
963 """Justification du changement de la Remuneration.
964 (Actuellement utilisé dans aucun traitement informatique.)
966 nom
= models
.CharField(max_length
=255)
970 verbose_name
= u
"Type de revalorisation"
971 verbose_name_plural
= u
"Types de revalorisation"
973 def __unicode__(self
):
974 return u
'%s' % (self
.nom
)
976 class Service(AUFMetadata
):
977 """Unité administrative où les Postes sont rattachés.
979 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
980 nom
= models
.CharField(max_length
=255)
984 verbose_name
= u
"Service"
985 verbose_name_plural
= u
"Services"
987 def __unicode__(self
):
988 return u
'%s' % (self
.nom
)
991 TYPE_ORGANISME_CHOICES
= (
992 ('MAD', 'Mise à disposition'),
993 ('DET', 'Détachement'),
996 class OrganismeBstg(AUFMetadata
):
997 """Organisation d'où provient un Employe mis à disposition (MAD) de
998 ou détaché (DET) à l'AUF à titre gratuit.
1000 (BSTG = bien et service à titre gratuit.)
1002 nom
= models
.CharField(max_length
=255)
1003 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1004 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1006 related_name
='organismes_bstg',
1007 null
=True, blank
=True)
1010 ordering
= ['type', 'nom']
1011 verbose_name
= u
"Organisme BSTG"
1012 verbose_name_plural
= u
"Organismes BSTG"
1014 def __unicode__(self
):
1015 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1017 prefix_implantation
= "pays__region"
1018 def get_regions(self
):
1019 return [self
.pays
.region
]
1022 class Statut(AUFMetadata
):
1023 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1026 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.")
1027 nom
= models
.CharField(max_length
=255)
1031 verbose_name
= u
"Statut d'employé"
1032 verbose_name_plural
= u
"Statuts d'employé"
1034 def __unicode__(self
):
1035 return u
'%s : %s' % (self
.code
, self
.nom
)
1038 TYPE_CLASSEMENT_CHOICES
= (
1039 ('S', 'S -Soutien'),
1040 ('T', 'T - Technicien'),
1041 ('P', 'P - Professionel'),
1043 ('D', 'D - Direction'),
1044 ('SO', 'SO - Sans objet [expatriés]'),
1045 ('HG', 'HG - Hors grille [direction]'),
1049 class Classement_(AUFMetadata
):
1050 """Éléments de classement de la
1051 "Grille générique de classement hiérarchique".
1053 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1054 classement dans la grille. Le classement donne le coefficient utilisé dans:
1056 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1059 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1060 echelon
= models
.IntegerField(verbose_name
=u
"Échelon", blank
=True, default
=0)
1061 degre
= models
.IntegerField(verbose_name
=u
"Degré", blank
=True, default
=0)
1062 coefficient
= models
.FloatField(default
=0, verbose_name
=u
"Coefficient",
1065 # annee # au lieu de date_debut et date_fin
1066 commentaire
= models
.TextField(null
=True, blank
=True)
1070 ordering
= ['type','echelon','degre','coefficient']
1071 verbose_name
= u
"Classement"
1072 verbose_name_plural
= u
"Classements"
1074 def __unicode__(self
):
1075 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1078 class Classement(Classement_
):
1079 __doc__
= Classement_
.__doc__
1082 class TauxChange_(AUFMetadata
):
1083 """Taux de change de la devise vers l'euro (EUR)
1084 pour chaque année budgétaire.
1087 devise
= models
.ForeignKey('Devise', db_column
='devise')
1088 annee
= models
.IntegerField(verbose_name
= u
"Année")
1089 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1093 ordering
= ['-annee', 'devise__code']
1094 verbose_name
= u
"Taux de change"
1095 verbose_name_plural
= u
"Taux de change"
1097 def __unicode__(self
):
1098 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1101 class TauxChange(TauxChange_
):
1102 __doc__
= TauxChange_
.__doc__
1104 class ValeurPointManager(NoDeleteManager
):
1106 def get_query_set(self
):
1107 now
= datetime
.datetime
.now()
1108 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1111 class ValeurPoint_(AUFMetadata
):
1112 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1113 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1114 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1116 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1119 actuelles
= ValeurPointManager()
1121 valeur
= models
.FloatField(null
=True)
1122 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1123 implantation
= models
.ForeignKey(ref
.Implantation
,
1124 db_column
='implantation',
1125 related_name
='%(app_label)s_valeur_point')
1127 annee
= models
.IntegerField()
1130 ordering
= ['-annee', 'implantation__nom']
1132 verbose_name
= u
"Valeur du point"
1133 verbose_name_plural
= u
"Valeurs du point"
1135 def __unicode__(self
):
1136 return u
'%s %s %s [%s] %s' % (self
.devise
.code
, self
.annee
, self
.valeur
, self
.implantation
.nom_court
, self
.devise
.nom
)
1139 class ValeurPoint(ValeurPoint_
):
1140 __doc__
= ValeurPoint_
.__doc__
1144 class Devise(AUFMetadata
):
1145 """Devise monétaire.
1147 code
= models
.CharField(max_length
=10, unique
=True)
1148 nom
= models
.CharField(max_length
=255)
1152 verbose_name
= u
"Devise"
1153 verbose_name_plural
= u
"Devises"
1155 def __unicode__(self
):
1156 return u
'%s - %s' % (self
.code
, self
.nom
)
1158 class TypeContrat(AUFMetadata
):
1161 nom
= models
.CharField(max_length
=255)
1162 nom_long
= models
.CharField(max_length
=255)
1166 verbose_name
= u
"Type de contrat"
1167 verbose_name_plural
= u
"Types de contrat"
1169 def __unicode__(self
):
1170 return u
'%s' % (self
.nom
)
1175 class ResponsableImplantation(AUFMetadata
):
1176 """Le responsable d'une implantation.
1177 Anciennement géré sur le Dossier du responsable.
1179 employe
= models
.ForeignKey('Employe', db_column
='employe',
1181 null
=True, blank
=True)
1182 implantation
= models
.ForeignKey(ref
.Implantation
,
1183 db_column
='implantation', related_name
='+',
1186 def __unicode__(self
):
1187 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1190 ordering
= ['implantation__nom']
1191 verbose_name
= "Responsable d'implantation"
1192 verbose_name_plural
= "Responsables d'implantation"