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 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 objects
= PosteComparaisonManager()
265 poste
= models
.ForeignKey('Poste', related_name
='comparaisons_internes')
266 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
267 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
268 montant
= models
.IntegerField(null
=True)
269 devise
= models
.ForeignKey("Devise", default
=5, related_name
='+', null
=True, blank
=True)
271 def taux_devise(self
):
272 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.implantation
)
273 if len(liste_taux
) == 0:
274 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.implantation
))
276 return liste_taux
[0].taux
278 def montant_euros(self
):
279 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
282 class PosteCommentaire(Commentaire
):
283 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='+')
292 SITUATION_CHOICES
= (
293 ('C', 'Célibataire'),
298 class Employe(AUFMetadata
):
299 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
300 Dossiers qu'il occupe ou a occupé de Postes.
302 Cette classe aurait pu avantageusement s'appeler Personne car la notion
303 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
306 nom
= models
.CharField(max_length
=255)
307 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
308 nom_affichage
= models
.CharField(max_length
=255,
309 verbose_name
= u
"Nom d'affichage",
310 null
=True, blank
=True)
311 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
312 db_column
='nationalite',
313 related_name
='employes_nationalite',
314 verbose_name
= u
"Nationalité")
315 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
316 validators
=[validate_date_passee
],
317 null
=True, blank
=True)
318 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
321 situation_famille
= models
.CharField(max_length
=1,
322 choices
=SITUATION_CHOICES
,
323 verbose_name
= u
"Situation familiale",
324 null
=True, blank
=True)
325 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
326 null
=True, blank
=True)
329 tel_domicile
= models
.CharField(max_length
=255,
330 verbose_name
= u
"Tél. domicile",
331 null
=True, blank
=True)
332 tel_cellulaire
= models
.CharField(max_length
=255,
333 verbose_name
= u
"Tél. cellulaire",
334 null
=True, blank
=True)
335 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
336 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
337 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
338 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
339 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
340 related_name
='employes',
341 null
=True, blank
=True)
344 ordering
= ['nom_affichage','nom','prenom']
345 verbose_name
= u
"Employé"
346 verbose_name_plural
= u
"Employés"
348 def __unicode__(self
):
349 return u
'%s [%s]' % (self
.get_nom(), self
.id)
352 nom_affichage
= self
.nom_affichage
353 if not nom_affichage
:
354 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
359 if self
.genre
.upper() == u
'M':
361 elif self
.genre
.upper() == u
'F':
366 """Retourne l'URL du service retournant la photo de l'Employe.
367 Équivalent reverse url 'rh_photo' avec id en param.
369 from django
.core
.urlresolvers
import reverse
370 return reverse('rh_photo', kwargs
={'id':self
.id})
372 def dossiers_passes(self
):
374 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
375 for d
in dossiers_passes
:
377 return dossiers_passes
379 def dossiers_futurs(self
):
381 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
383 def dossiers_encours(self
):
384 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
385 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
386 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
388 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
389 for d
in dossiers_encours
:
390 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
391 return dossiers_encours
393 def postes_encours(self
):
394 postes_encours
= set()
395 for d
in self
.dossiers_encours():
396 postes_encours
.add(d
.poste
)
397 return postes_encours
399 def poste_principal(self
):
401 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
403 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
405 poste
= Poste
.objects
.none()
407 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
412 prefix_implantation
= "dossiers__poste__implantation__region"
413 def get_regions(self
):
415 for d
in self
.dossiers
.all():
416 regions
.append(d
.poste
.implantation
.region
)
420 class EmployePiece(models
.Model
):
421 """Documents relatifs à un employé.
424 employe
= models
.ForeignKey('Employe', db_column
='employe')
425 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
426 fichier
= models
.FileField(verbose_name
="Fichier",
427 upload_to
=employe_piece_dispatch
,
428 storage
=storage_prive
)
432 verbose_name
= u
"Employé pièce"
433 verbose_name_plural
= u
"Employé pièces"
435 def __unicode__(self
):
436 return u
'%s' % (self
.nom
)
438 class EmployeCommentaire(Commentaire
):
439 employe
= models
.ForeignKey('Employe', db_column
='employe',
443 verbose_name
= u
"Employé commentaire"
444 verbose_name_plural
= u
"Employé commentaires"
447 LIEN_PARENTE_CHOICES
= (
448 ('Conjoint', 'Conjoint'),
449 ('Conjointe', 'Conjointe'),
454 class AyantDroit(AUFMetadata
):
455 """Personne en relation avec un Employe.
458 nom
= models
.CharField(max_length
=255)
459 prenom
= models
.CharField(max_length
=255,
460 verbose_name
= u
"Prénom",)
461 nom_affichage
= models
.CharField(max_length
=255,
462 verbose_name
= u
"Nom d'affichage",
463 null
=True, blank
=True)
464 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
465 db_column
='nationalite',
466 related_name
='ayantdroits_nationalite',
467 verbose_name
= u
"Nationalité")
468 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
469 validators
=[validate_date_passee
],
470 null
=True, blank
=True)
471 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
474 employe
= models
.ForeignKey('Employe', db_column
='employe',
475 related_name
='ayantdroits',
476 verbose_name
= u
"Employé")
477 lien_parente
= models
.CharField(max_length
=10,
478 choices
=LIEN_PARENTE_CHOICES
,
479 verbose_name
= u
"Lien de parenté",
480 null
=True, blank
=True)
483 ordering
= ['nom_affichage']
484 verbose_name
= u
"Ayant droit"
485 verbose_name_plural
= u
"Ayants droit"
487 def __unicode__(self
):
488 return u
'%s' % (self
.get_nom())
491 nom_affichage
= self
.nom_affichage
492 if not nom_affichage
:
493 nom_affichage
= u
'%s %s' % (self
.nom
.upper(), self
.prenom
)
496 prefix_implantation
= "employe__dossiers__poste__implantation__region"
497 def get_regions(self
):
499 for d
in self
.employe
.dossiers
.all():
500 regions
.append(d
.poste
.implantation
.region
)
504 class AyantDroitCommentaire(Commentaire
):
505 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
511 STATUT_RESIDENCE_CHOICES
= (
513 ('expat', 'Expatrié'),
516 COMPTE_COMPTA_CHOICES
= (
522 class Dossier_(AUFMetadata
):
523 """Le Dossier regroupe les informations relatives à l'occupation
524 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
527 Plusieurs Contrats peuvent être associés au Dossier.
528 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
529 lequel aucun Dossier n'existe est un poste vacant.
532 objects
= DossierManager()
535 statut
= models
.ForeignKey('Statut', related_name
='+', default
=3,
537 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
538 db_column
='organisme_bstg',
540 verbose_name
= u
"Organisme",
541 help_text
="Si détaché (DET) ou \
542 mis à disposition (MAD), \
543 préciser l'organisme.",
544 null
=True, blank
=True)
547 remplacement
= models
.BooleanField(default
=False)
548 remplacement_de
= models
.ForeignKey('self', related_name
='+',
549 null
=True, blank
=True)
550 statut_residence
= models
.CharField(max_length
=10, default
='local',
551 verbose_name
= u
"Statut", null
=True,
552 choices
=STATUT_RESIDENCE_CHOICES
)
555 classement
= models
.ForeignKey('Classement', db_column
='classement',
557 null
=True, blank
=True)
558 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
560 default
=REGIME_TRAVAIL_DEFAULT
,
561 verbose_name
= u
"Régime de travail",
562 help_text
="% du temps complet")
563 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
564 decimal_places
=2, null
=True,
565 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
566 verbose_name
= u
"Nb. heures par semaine")
568 # Occupation du Poste par cet Employe (anciennement "mandat")
569 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
571 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
573 null
=True, blank
=True)
580 ordering
= ['employe__nom', ]
581 verbose_name
= u
"Dossier"
582 verbose_name_plural
= "Dossiers"
584 def salaire_theorique(self
):
585 annee
= date
.today().year
586 coeff
= self
.classement
.coefficient
587 implantation
= self
.poste
.implantation
588 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
590 montant
= coeff
* point
.valeur
591 devise
= point
.devise
592 return {'montant':montant
, 'devise':devise
}
594 def __unicode__(self
):
595 poste
= self
.poste
.nom
596 if self
.employe
.genre
== 'F':
597 poste
= self
.poste
.nom_feminin
598 return u
'%s - %s' % (self
.employe
, poste
)
600 prefix_implantation
= "poste__implantation__region"
601 def get_regions(self
):
602 return [self
.poste
.implantation
.region
]
605 def remunerations(self
):
606 return self
.rh_remuneration_remunerations
.all().order_by('date_debut')
608 def get_salaire(self
):
610 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
614 class Dossier(Dossier_
):
615 __doc__
= Dossier_
.__doc__
616 poste
= models
.ForeignKey('Poste', db_column
='poste', related_name
='%(app_label)s_dossiers')
617 employe
= models
.ForeignKey('Employe', db_column
='employe',
618 related_name
='%(app_label)s_dossiers',
619 verbose_name
=u
"Employé")
622 class DossierPiece(models
.Model
):
623 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
624 Ex.: Lettre de motivation.
626 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
628 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
629 fichier
= models
.FileField(verbose_name
= u
"Fichier",
630 upload_to
=dossier_piece_dispatch
,
631 storage
=storage_prive
)
636 def __unicode__(self
):
637 return u
'%s' % (self
.nom
)
639 class DossierCommentaire(Commentaire
):
640 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
643 class DossierComparaison(models
.Model
):
645 Photo d'une comparaison salariale au moment de l'embauche.
648 objects
= DossierComparaisonManager()
650 dossier
= models
.ForeignKey('Dossier', related_name
='comparaisons')
651 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
652 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
653 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
654 montant
= models
.IntegerField(null
=True)
655 devise
= models
.ForeignKey('Devise', default
=5, related_name
='+', null
=True, blank
=True)
657 def taux_devise(self
):
658 liste_taux
= self
.devise
.tauxchange_set
.order_by('-annee').filter(implantation
=self
.dossier
.poste
.implantation
)
659 if len(liste_taux
) == 0:
660 raise Exception(u
"La devise %s n'a pas de taux pour l'implantation %s" % (self
.devise
, self
.dossier
.poste
.implantation
))
662 return liste_taux
[0].taux
664 def montant_euros(self
):
665 return round(float(self
.montant
) * float(self
.taux_devise()), 2)
670 class RemunerationMixin(AUFMetadata
):
672 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
673 related_name
='%(app_label)s_%(class)s_remunerations')
674 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
676 verbose_name
= u
"Type de rémunération")
677 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
678 db_column
='type_revalorisation',
680 verbose_name
= u
"Type de revalorisation",
681 null
=True, blank
=True)
682 montant
= models
.FloatField(null
=True, blank
=True,
684 # Annuel (12 mois, 52 semaines, 364 jours?)
685 devise
= models
.ForeignKey('Devise', to_field
='id',
686 db_column
='devise', related_name
='+',
688 # commentaire = precision
689 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
690 # date_debut = anciennement date_effectif
691 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
692 null
=True, blank
=True)
693 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
694 null
=True, blank
=True)
698 ordering
= ['type__nom', '-date_fin']
700 def __unicode__(self
):
701 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
703 class Remuneration_(RemunerationMixin
):
704 """Structure de rémunération (données budgétaires) en situation normale
705 pour un Dossier. Si un Evenement existe, utiliser la structure de
706 rémunération EvenementRemuneration de cet événement.
709 def montant_mois(self
):
710 return round(self
.montant
/ 12, 2)
712 def taux_devise(self
):
713 return self
.devise
.tauxchange_set
.order_by('-annee').all()[0].taux
715 def montant_euro(self
):
716 return round(float(self
.montant
) / float(self
.taux_devise()), 2)
718 def montant_euro_mois(self
):
719 return round(self
.montant_euro() / 12, 2)
721 def __unicode__(self
):
723 devise
= self
.devise
.code
726 return "%s %s" % (self
.montant
, devise
)
730 verbose_name
= u
"Rémunération"
731 verbose_name_plural
= u
"Rémunérations"
734 class Remuneration(Remuneration_
):
735 __doc__
= Remuneration_
.__doc__
740 class ContratManager(NoDeleteManager
):
741 def get_query_set(self
):
742 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
745 class Contrat(AUFMetadata
):
746 """Document juridique qui encadre la relation de travail d'un Employe
747 pour un Poste particulier. Pour un Dossier (qui documente cette
748 relation de travail) plusieurs contrats peuvent être associés.
751 objects
= ContratManager()
753 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
754 related_name
='contrats')
755 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
757 verbose_name
= u
"Type de contrat")
758 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
759 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
760 null
=True, blank
=True)
763 ordering
= ['dossier__employe__nom_affichage']
764 verbose_name
= u
"Contrat"
765 verbose_name_plural
= u
"Contrats"
767 def __unicode__(self
):
768 return u
'%s - %s' % (self
.dossier
, self
.id)
770 # TODO? class ContratPiece(models.Model):
775 class Evenement_(AUFMetadata
):
776 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
777 d'un Dossier qui vient altérer des informations normales liées à un Dossier
778 (ex.: la Remuneration).
780 Ex.: congé de maternité, maladie...
782 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
783 différent et une rémunération en conséquence. On souhaite toutefois
784 conserver le Dossier intact afin d'éviter une re-saisie des données lors
785 du retour à la normale.
787 dossier
= models
.ForeignKey('Dossier', db_column
='dossier',
789 nom
= models
.CharField(max_length
=255)
790 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
791 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
792 null
=True, blank
=True)
797 verbose_name
= u
"Évènement"
798 verbose_name_plural
= u
"Évènements"
800 def __unicode__(self
):
801 return u
'%s' % (self
.nom
)
804 class Evenement(Evenement_
):
805 __doc__
= Evenement_
.__doc__
808 class EvenementRemuneration_(RemunerationMixin
):
809 """Structure de rémunération liée à un Evenement qui remplace
810 temporairement la Remuneration normale d'un Dossier, pour toute la durée
813 evenement
= models
.ForeignKey("Evenement", db_column
='evenement',
815 verbose_name
= u
"Évènement")
816 # TODO : le champ dossier hérité de Remuneration doit être dérivé
817 # de l'Evenement associé
821 ordering
= ['evenement', 'type__nom', '-date_fin']
822 verbose_name
= u
"Évènement - rémunération"
823 verbose_name_plural
= u
"Évènements - rémunérations"
826 class EvenementRemuneration(EvenementRemuneration_
):
827 __doc__
= EvenementRemuneration_
.__doc__
833 class EvenementRemuneration(EvenementRemuneration_
):
834 __doc__
= EvenementRemuneration_
.__doc__
839 class FamilleEmploi(AUFMetadata
):
840 """Catégorie utilisée dans la gestion des Postes.
841 Catégorie supérieure à TypePoste.
843 nom
= models
.CharField(max_length
=255)
847 verbose_name
= u
"Famille d'emploi"
848 verbose_name_plural
= u
"Familles d'emploi"
850 def __unicode__(self
):
851 return u
'%s' % (self
.nom
)
853 class TypePoste(AUFMetadata
):
854 """Catégorie de Poste.
856 nom
= models
.CharField(max_length
=255)
857 nom_feminin
= models
.CharField(max_length
=255,
858 verbose_name
= u
"Nom féminin")
860 is_responsable
= models
.BooleanField(default
=False,
861 verbose_name
= u
"Poste de responsabilité")
862 famille_emploi
= models
.ForeignKey('FamilleEmploi',
863 db_column
='famille_emploi',
865 verbose_name
= u
"Famille d'emploi")
869 verbose_name
= u
"Type de poste"
870 verbose_name_plural
= u
"Types de poste"
872 def __unicode__(self
):
873 return u
'%s' % (self
.nom
)
876 TYPE_PAIEMENT_CHOICES
= (
877 ('Régulier', 'Régulier'),
878 ('Ponctuel', 'Ponctuel'),
881 NATURE_REMUNERATION_CHOICES
= (
882 ('Accessoire', 'Accessoire'),
883 ('Charges', 'Charges'),
884 ('Indemnité', 'Indemnité'),
885 ('RAS', 'Rémunération autre source'),
886 ('Traitement', 'Traitement'),
889 class TypeRemuneration(AUFMetadata
):
890 """Catégorie de Remuneration.
892 nom
= models
.CharField(max_length
=255)
893 type_paiement
= models
.CharField(max_length
=30,
894 choices
=TYPE_PAIEMENT_CHOICES
,
895 verbose_name
= u
"Type de paiement")
896 nature_remuneration
= models
.CharField(max_length
=30,
897 choices
=NATURE_REMUNERATION_CHOICES
,
898 verbose_name
= u
"Nature de la rémunération")
902 verbose_name
= u
"Type de rémunération"
903 verbose_name_plural
= u
"Types de rémunération"
905 def __unicode__(self
):
906 return u
'%s' % (self
.nom
)
908 class TypeRevalorisation(AUFMetadata
):
909 """Justification du changement de la Remuneration.
910 (Actuellement utilisé dans aucun traitement informatique.)
912 nom
= models
.CharField(max_length
=255)
916 verbose_name
= u
"Type de revalorisation"
917 verbose_name_plural
= u
"Types de revalorisation"
919 def __unicode__(self
):
920 return u
'%s' % (self
.nom
)
922 class Service(AUFMetadata
):
923 """Unité administrative où les Postes sont rattachés.
925 nom
= models
.CharField(max_length
=255)
929 verbose_name
= u
"Service"
930 verbose_name_plural
= u
"Services"
932 def __unicode__(self
):
933 return u
'%s' % (self
.nom
)
936 TYPE_ORGANISME_CHOICES
= (
937 ('MAD', 'Mise à disposition'),
938 ('DET', 'Détachement'),
941 class OrganismeBstg(AUFMetadata
):
942 """Organisation d'où provient un Employe mis à disposition (MAD) de
943 ou détaché (DET) à l'AUF à titre gratuit.
945 (BSTG = bien et service à titre gratuit.)
947 nom
= models
.CharField(max_length
=255)
948 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
949 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
951 related_name
='organismes_bstg',
952 null
=True, blank
=True)
955 ordering
= ['type', 'nom']
956 verbose_name
= u
"Organisme BSTG"
957 verbose_name_plural
= u
"Organismes BSTG"
959 def __unicode__(self
):
960 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
962 prefix_implantation
= "pays__region"
963 def get_regions(self
):
964 return [self
.pays
.region
]
967 class Statut(AUFMetadata
):
968 """Statut de l'Employe dans le cadre d'un Dossier particulier.
971 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.")
972 nom
= models
.CharField(max_length
=255)
976 verbose_name
= u
"Statut d'employé"
977 verbose_name_plural
= u
"Statuts d'employé"
979 def __unicode__(self
):
980 return u
'%s : %s' % (self
.code
, self
.nom
)
983 TYPE_CLASSEMENT_CHOICES
= (
985 ('T', 'T - Technicien'),
986 ('P', 'P - Professionel'),
988 ('D', 'D - Direction'),
989 ('SO', 'SO - Sans objet [expatriés]'),
990 ('HG', 'HG - Hors grille [direction]'),
994 class Classement_(AUFMetadata
):
995 """Éléments de classement de la
996 "Grille générique de classement hiérarchique".
998 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
999 classement dans la grille. Le classement donne le coefficient utilisé dans:
1001 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1004 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1005 echelon
= models
.IntegerField(verbose_name
= u
"Échelon")
1006 degre
= models
.IntegerField(verbose_name
= u
"Degré")
1007 coefficient
= models
.FloatField(default
=0, verbose_name
= u
"Coéfficient",
1010 # annee # au lieu de date_debut et date_fin
1011 commentaire
= models
.TextField(null
=True, blank
=True)
1015 ordering
= ['type','echelon','degre','coefficient']
1016 verbose_name
= u
"Classement"
1017 verbose_name_plural
= u
"Classements"
1019 def __unicode__(self
):
1020 return u
'%s.%s.%s (%s)' % (self
.type, self
.echelon
, self
.degre
,
1023 class Classement(Classement_
):
1024 __doc__
= Classement_
.__doc__
1027 class TauxChange_(AUFMetadata
):
1028 """Taux de change de la devise vers l'euro (EUR)
1029 pour chaque année budgétaire.
1032 devise
= models
.ForeignKey('Devise', db_column
='devise')
1033 annee
= models
.IntegerField(verbose_name
= u
"Année")
1034 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1038 ordering
= ['-annee', 'devise__code']
1039 verbose_name
= u
"Taux de change"
1040 verbose_name_plural
= u
"Taux de change"
1042 def __unicode__(self
):
1043 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1046 class TauxChange(TauxChange_
):
1047 __doc__
= TauxChange_
.__doc__
1049 class ValeurPointManager(NoDeleteManager
):
1050 def get_query_set(self
):
1051 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1054 class ValeurPoint_(AUFMetadata
):
1055 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1056 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1057 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1059 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1062 actuelles
= ValeurPointManager()
1064 valeur
= models
.FloatField(null
=True)
1065 devise
= models
.ForeignKey('Devise', db_column
='devise', null
=True,
1066 related_name
='+', default
=5)
1067 implantation
= models
.ForeignKey(ref
.Implantation
,
1068 db_column
='implantation',
1069 related_name
='%(app_label)s_valeur_point')
1071 annee
= models
.IntegerField()
1074 ordering
= ['-annee', 'implantation__nom']
1076 verbose_name
= u
"Valeur du point"
1077 verbose_name_plural
= u
"Valeurs du point"
1079 def __unicode__(self
):
1080 return u
'%s %s (%s)' % (self
.valeur
, self
.devise
, self
.annee
)
1083 class ValeurPoint(ValeurPoint_
):
1084 __doc__
= ValeurPoint_
.__doc__
1087 class Devise(AUFMetadata
):
1088 """Devise monétaire.
1090 code
= models
.CharField(max_length
=10, unique
=True)
1091 nom
= models
.CharField(max_length
=255)
1095 verbose_name
= u
"Devise"
1096 verbose_name_plural
= u
"Devises"
1098 def __unicode__(self
):
1099 return u
'%s - %s' % (self
.code
, self
.nom
)
1101 class TypeContrat(AUFMetadata
):
1104 nom
= models
.CharField(max_length
=255)
1105 nom_long
= models
.CharField(max_length
=255)
1109 verbose_name
= u
"Type de contrat"
1110 verbose_name_plural
= u
"Types de contrat"
1112 def __unicode__(self
):
1113 return u
'%s' % (self
.nom
)
1118 class ResponsableImplantation(AUFMetadata
):
1119 """Le responsable d'une implantation.
1120 Anciennement géré sur le Dossier du responsable.
1122 employe
= models
.ForeignKey('Employe', db_column
='employe',
1124 null
=True, blank
=True)
1125 implantation
= models
.ForeignKey(ref
.Implantation
,
1126 db_column
='implantation', related_name
='+',
1129 def __unicode__(self
):
1130 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1133 ordering
= ['implantation__nom']
1134 verbose_name
= "Responsable d'implantation"
1135 verbose_name_plural
= "Responsables d'implantation"