1 # -=- encoding: utf-8 -=-
4 from datetime
import date
5 from decimal
import Decimal
7 from django
.core
.files
.storage
import FileSystemStorage
8 from django
.db
import models
9 from django
.conf
import settings
11 from auf
.django
.emploi
.models
import GENRE_CHOICES
, SITUATION_CHOICES
# devrait plutot être dans references
12 from auf
.django
.metadata
.models
import AUFMetadata
13 from auf
.django
.metadata
.managers
import NoDeleteManager
14 import auf
.django
.references
.models
as ref
15 from validators
import validate_date_passee
16 from managers
import PosteManager
, DossierManager
, DossierComparaisonManager
, \
17 PosteComparaisonManager
, DeviseManager
, ServiceManager
, TypeRemunerationManager
18 from change_list
import RechercheTemporelle
, KEY_STATUT
, STATUT_ACTIF
, \
19 STATUT_INACTIF
, STATUT_FUTUR
22 # Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
23 # Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
26 models_stack
= [s
[1].split('/')[-2] for s
in inspect
.stack() if s
[1].endswith('models.py')]
27 return models_stack
[-1]
31 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
32 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
33 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
34 REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
= "Saisir le nombre d'heure de travail à temps complet (100%), sans tenir compte du régime de travail"
37 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
38 base_url
=settings
.PRIVE_MEDIA_URL
)
40 def poste_piece_dispatch(instance
, filename
):
41 path
= "%s/poste/%s/%s" % (instance
._meta
.app_label
, instance
.poste_id
, filename
)
44 def dossier_piece_dispatch(instance
, filename
):
45 path
= "%s/dossier/%s/%s" % (instance
._meta
.app_label
, instance
.dossier_id
, filename
)
48 def employe_piece_dispatch(instance
, filename
):
49 path
= "%s/employe/%s/%s" % (instance
._meta
.app_label
, instance
.employe_id
, filename
)
52 def contrat_dispatch(instance
, filename
):
53 path
= "%s/contrat/%s/%s" % (instance
._meta
.app_label
, instance
.dossier_id
, filename
)
57 class DevisableMixin(object):
59 def get_annee_pour_taux_devise(self
):
60 return datetime
.datetime
.now().year
62 def taux_devise(self
):
63 if self
.devise
is None:
65 if self
.devise
.code
== "EUR":
68 annee
= self
.get_annee_pour_taux_devise()
69 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
73 raise Exception(u
"Pas de taux pour %s en %s" % (self
.devise
.code
, annee
))
76 raise Exception(u
"Il existe plusieurs taux de %s en %s" %
77 (self
.devise
.code
, annee
))
81 def montant_euros(self
):
83 taux
= self
.taux_devise()
88 return int(round(float(self
.montant
) * float(taux
), 2))
91 class Commentaire(AUFMetadata
):
92 texte
= models
.TextField()
93 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+', verbose_name
=u
"Commentaire de")
97 ordering
= ['-date_creation']
99 def __unicode__(self
):
100 return u
'%s' % (self
.texte
)
105 POSTE_APPEL_CHOICES
= (
106 ('interne', 'Interne'),
107 ('externe', 'Externe'),
110 class Poste_(AUFMetadata
):
111 """Un Poste est un emploi (job) à combler dans une implantation.
112 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
113 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
116 objects
= PosteManager()
119 nom
= models
.CharField(max_length
=255,
120 verbose_name
= u
"Titre du poste", )
121 nom_feminin
= models
.CharField(max_length
=255,
122 verbose_name
= u
"Titre du poste (au féminin)",
124 implantation
= models
.ForeignKey(ref
.Implantation
, help_text
=u
"Taper le nom de l'implantation ou sa région",
125 db_column
='implantation', related_name
='+')
126 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste', help_text
=u
"Taper le nom du type de poste",
129 verbose_name
=u
"type de poste")
130 service
= models
.ForeignKey('Service', db_column
='service',
132 verbose_name
= u
"direction/service/pôle support",
134 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
137 help_text
=u
"Taper le nom du poste ou du type de poste",
138 verbose_name
= u
"Poste du responsable", )
141 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
142 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
143 verbose_name
= u
"Temps de travail",
144 help_text
="% du temps complet")
145 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
146 decimal_places
=2, null
=True,
147 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
148 verbose_name
= u
"Nb. heures par semaine",
149 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
)
152 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
153 null
=True, blank
=True)
154 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
155 null
=True, blank
=True)
156 mise_a_disposition
= models
.NullBooleanField(
157 verbose_name
= u
"Mise à disposition",
158 null
=True, default
=False)
159 appel
= models
.CharField(max_length
=10, null
=True,
160 verbose_name
= u
"Appel à candidature",
161 choices
=POSTE_APPEL_CHOICES
,
165 classement_min
= models
.ForeignKey('Classement',
166 db_column
='classement_min', related_name
='+',
167 null
=True, blank
=True)
168 classement_max
= models
.ForeignKey('Classement',
169 db_column
='classement_max', related_name
='+',
170 null
=True, blank
=True)
171 valeur_point_min
= models
.ForeignKey('ValeurPoint', help_text
=u
"Taper le code ou le nom de l'implantation",
172 db_column
='valeur_point_min', related_name
='+',
173 null
=True, blank
=True)
174 valeur_point_max
= models
.ForeignKey('ValeurPoint', help_text
=u
"Taper le code ou le nom de l'implantation",
175 db_column
='valeur_point_max', related_name
='+',
176 null
=True, blank
=True)
177 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
179 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
181 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
182 null
=True, default
=0)
183 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
184 null
=True, default
=0)
185 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
186 null
=True, default
=0)
187 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
188 null
=True, default
=0)
189 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
190 null
=True, default
=0)
191 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
192 null
=True, default
=0)
194 # Comparatifs de rémunération
195 devise_comparaison
= models
.ForeignKey('Devise', null
=True, blank
=True,
196 db_column
='devise_comparaison',
198 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
199 null
=True, blank
=True)
200 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
201 null
=True, blank
=True)
202 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
203 null
=True, blank
=True)
204 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
205 null
=True, blank
=True)
206 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
207 null
=True, blank
=True)
208 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
209 null
=True, blank
=True)
210 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
211 null
=True, blank
=True)
212 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
213 null
=True, blank
=True)
214 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
215 null
=True, blank
=True)
216 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
217 null
=True, blank
=True)
220 justification
= models
.TextField(null
=True, blank
=True)
223 date_debut
= models
.DateField(verbose_name
=u
"Date de début", help_text
=HELP_TEXT_DATE
,
224 null
=True, blank
=True)
225 date_fin
= models
.DateField(verbose_name
=u
"Date de fin", help_text
=HELP_TEXT_DATE
,
226 null
=True, blank
=True)
230 ordering
= ['implantation__nom', 'nom']
231 verbose_name
= u
"Poste"
232 verbose_name_plural
= u
"Postes"
235 def __unicode__(self
):
236 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
238 return representation
241 prefix_implantation
= "implantation__region"
242 def get_regions(self
):
243 return [self
.implantation
.region
]
247 __doc__
= Poste_
.__doc__
249 # meta dématérialisation : pour permettre le filtrage
250 vacant
= models
.NullBooleanField(verbose_name
= u
"vacant", null
=True, blank
=True)
254 if self
.occupe_par():
258 def occupe_par(self
):
259 """Retourne la liste d'employé occupant ce poste.
260 Généralement, retourne une liste d'un élément.
261 Si poste inoccupé, retourne liste vide.
262 UTILISE pour mettre a jour le flag vacant
264 return [d
.employe
for d
in self
.rh_dossiers
.filter(supprime
=False).exclude(date_fin__lt
=date
.today())]
267 POSTE_FINANCEMENT_CHOICES
= (
268 ('A', 'A - Frais de personnel'),
269 ('B', 'B - Projet(s)-Titre(s)'),
274 class PosteFinancement_(models
.Model
):
275 """Pour un Poste, structure d'informations décrivant comment on prévoit
278 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_financements')
279 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
280 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
281 help_text
="ex.: 33.33 % (décimale avec point)")
282 commentaire
= models
.TextField(
283 help_text
="Spécifiez la source de financement.")
289 def __unicode__(self
):
290 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
293 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
296 class PosteFinancement(PosteFinancement_
):
300 class PostePiece_(models
.Model
):
301 """Documents relatifs au Poste.
302 Ex.: Description de poste
304 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_pieces')
305 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
306 fichier
= models
.FileField(verbose_name
= u
"Fichier",
307 upload_to
=poste_piece_dispatch
,
308 storage
=storage_prive
)
314 def __unicode__(self
):
315 return u
'%s' % (self
.nom
)
317 class PostePiece(PostePiece_
):
320 class PosteComparaison_(AUFMetadata
, DevisableMixin
):
322 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
324 poste
= models
.ForeignKey('%s.Poste' % app_context(), related_name
='%(app_label)s_comparaisons_internes')
325 objects
= PosteComparaisonManager()
327 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
328 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
329 montant
= models
.IntegerField(null
=True)
330 devise
= models
.ForeignKey("Devise", related_name
='+', null
=True, blank
=True)
336 def __unicode__(self
):
339 class PosteComparaison(PosteComparaison_
):
342 class PosteCommentaire_(Commentaire
):
343 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='+')
348 class PosteCommentaire(PosteCommentaire_
):
353 class Employe(AUFMetadata
):
354 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
355 Dossiers qu'il occupe ou a occupé de Postes.
357 Cette classe aurait pu avantageusement s'appeler Personne car la notion
358 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
361 nom
= models
.CharField(max_length
=255)
362 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
363 nom_affichage
= models
.CharField(max_length
=255,
364 verbose_name
= u
"Nom d'affichage",
365 null
=True, blank
=True)
366 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
367 db_column
='nationalite',
368 related_name
='employes_nationalite',
369 verbose_name
= u
"Nationalité",
370 blank
=True, null
=True)
371 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
372 help_text
=HELP_TEXT_DATE
,
373 validators
=[validate_date_passee
],
374 null
=True, blank
=True)
375 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
378 situation_famille
= models
.CharField(max_length
=1,
379 choices
=SITUATION_CHOICES
,
380 verbose_name
= u
"Situation familiale",
381 null
=True, blank
=True)
382 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
383 help_text
=HELP_TEXT_DATE
,
384 null
=True, blank
=True)
387 tel_domicile
= models
.CharField(max_length
=255,
388 verbose_name
= u
"Tél. domicile",
389 null
=True, blank
=True)
390 tel_cellulaire
= models
.CharField(max_length
=255,
391 verbose_name
= u
"Tél. cellulaire",
392 null
=True, blank
=True)
393 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
394 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
395 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
396 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
397 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
398 related_name
='employes',
399 null
=True, blank
=True)
401 # meta dématérialisation : pour permettre le filtrage
402 nb_postes
= models
.IntegerField(verbose_name
= u
"nombre de postes", null
=True, blank
=True)
405 ordering
= ['nom','prenom']
406 verbose_name
= u
"Employé"
407 verbose_name_plural
= u
"Employés"
409 def __unicode__(self
):
410 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
414 if self
.genre
.upper() == u
'M':
416 elif self
.genre
.upper() == u
'F':
421 """Retourne l'URL du service retournant la photo de l'Employe.
422 Équivalent reverse url 'rh_photo' avec id en param.
424 from django
.core
.urlresolvers
import reverse
425 return reverse('rh_photo', kwargs
={'id':self
.id})
427 def dossiers_passes(self
):
428 params
= {KEY_STATUT
: STATUT_INACTIF
, }
429 search
= RechercheTemporelle(params
, self
.__class__
)
430 search
.purge_params(params
)
431 q
= search
.get_q_temporel(self
.rh_dossiers
)
432 return self
.rh_dossiers
.filter(q
)
434 def dossiers_futurs(self
):
435 params
= {KEY_STATUT
: STATUT_FUTUR
, }
436 search
= RechercheTemporelle(params
, self
.__class__
)
437 search
.purge_params(params
)
438 q
= search
.get_q_temporel(self
.rh_dossiers
)
439 return self
.rh_dossiers
.filter(q
)
441 def dossiers_encours(self
):
442 params
= {KEY_STATUT
: STATUT_ACTIF
, }
443 search
= RechercheTemporelle(params
, self
.__class__
)
444 search
.purge_params(params
)
445 q
= search
.get_q_temporel(self
.rh_dossiers
)
446 return self
.rh_dossiers
.filter(q
)
448 def postes_encours(self
):
449 postes_encours
= set()
450 for d
in self
.dossiers_encours():
451 postes_encours
.add(d
.poste
)
452 return postes_encours
454 def poste_principal(self
):
456 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
458 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
460 poste
= Poste
.objects
.none()
462 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
467 prefix_implantation
= "rh_dossiers__poste__implantation__region"
468 def get_regions(self
):
470 for d
in self
.dossiers
.all():
471 regions
.append(d
.poste
.implantation
.region
)
475 class EmployePiece(models
.Model
):
476 """Documents relatifs à un employé.
479 employe
= models
.ForeignKey('Employe', db_column
='employe')
480 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
481 fichier
= models
.FileField(verbose_name
="Fichier",
482 upload_to
=employe_piece_dispatch
,
483 storage
=storage_prive
)
487 verbose_name
= u
"Employé pièce"
488 verbose_name_plural
= u
"Employé pièces"
490 def __unicode__(self
):
491 return u
'%s' % (self
.nom
)
493 class EmployeCommentaire(Commentaire
):
494 employe
= models
.ForeignKey('Employe', db_column
='employe',
498 verbose_name
= u
"Employé commentaire"
499 verbose_name_plural
= u
"Employé commentaires"
502 LIEN_PARENTE_CHOICES
= (
503 ('Conjoint', 'Conjoint'),
504 ('Conjointe', 'Conjointe'),
509 class AyantDroit(AUFMetadata
):
510 """Personne en relation avec un Employe.
513 nom
= models
.CharField(max_length
=255)
514 prenom
= models
.CharField(max_length
=255,
515 verbose_name
= u
"Prénom",)
516 nom_affichage
= models
.CharField(max_length
=255,
517 verbose_name
= u
"Nom d'affichage",
518 null
=True, blank
=True)
519 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
520 db_column
='nationalite',
521 related_name
='ayantdroits_nationalite',
522 verbose_name
= u
"Nationalité",
523 null
=True, blank
=True)
524 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
525 help_text
=HELP_TEXT_DATE
,
526 validators
=[validate_date_passee
],
527 null
=True, blank
=True)
528 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
531 employe
= models
.ForeignKey('Employe', db_column
='employe',
532 related_name
='ayantdroits',
533 verbose_name
= u
"Employé")
534 lien_parente
= models
.CharField(max_length
=10,
535 choices
=LIEN_PARENTE_CHOICES
,
536 verbose_name
= u
"Lien de parenté",
537 null
=True, blank
=True)
541 verbose_name
= u
"Ayant droit"
542 verbose_name_plural
= u
"Ayants droit"
544 def __unicode__(self
):
545 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
547 prefix_implantation
= "employe__dossiers__poste__implantation__region"
548 def get_regions(self
):
550 for d
in self
.employe
.dossiers
.all():
551 regions
.append(d
.poste
.implantation
.region
)
555 class AyantDroitCommentaire(Commentaire
):
556 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
562 STATUT_RESIDENCE_CHOICES
= (
564 ('expat', 'Expatrié'),
567 COMPTE_COMPTA_CHOICES
= (
573 class Dossier_(AUFMetadata
, DevisableMixin
):
574 """Le Dossier regroupe les informations relatives à l'occupation
575 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
578 Plusieurs Contrats peuvent être associés au Dossier.
579 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
580 lequel aucun Dossier n'existe est un poste vacant.
583 objects
= DossierManager()
586 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
587 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
588 db_column
='organisme_bstg',
590 verbose_name
= u
"Organisme",
591 help_text
="Si détaché (DET) ou \
592 mis à disposition (MAD), \
593 préciser l'organisme.",
594 null
=True, blank
=True)
597 remplacement
= models
.BooleanField(default
=False)
598 remplacement_de
= models
.ForeignKey('self', related_name
='+',
599 help_text
=u
"Taper le nom de l'employé",
600 null
=True, blank
=True)
601 statut_residence
= models
.CharField(max_length
=10, default
='local',
602 verbose_name
= u
"Statut", null
=True,
603 choices
=STATUT_RESIDENCE_CHOICES
)
606 classement
= models
.ForeignKey('Classement', db_column
='classement',
608 null
=True, blank
=True)
609 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
611 default
=REGIME_TRAVAIL_DEFAULT
,
612 verbose_name
= u
"Régime de travail",
613 help_text
="% du temps complet")
614 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
615 decimal_places
=2, null
=True,
616 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
617 verbose_name
=u
"Nb. heures par semaine",
618 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
)
620 # Occupation du Poste par cet Employe (anciennement "mandat")
621 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
623 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
625 null
=True, blank
=True)
632 ordering
= ['employe__nom', ]
633 verbose_name
= u
"Dossier"
634 verbose_name_plural
= "Dossiers"
636 def salaire_theorique(self
):
637 annee
= date
.today().year
638 coeff
= self
.classement
.coefficient
639 implantation
= self
.poste
.implantation
640 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
642 montant
= coeff
* point
.valeur
643 devise
= point
.devise
644 return {'montant':montant
, 'devise':devise
}
646 def __unicode__(self
):
647 poste
= self
.poste
.nom
648 if self
.employe
.genre
== 'F':
649 poste
= self
.poste
.nom_feminin
650 return u
'%s - %s' % (self
.employe
, poste
)
652 prefix_implantation
= "poste__implantation__region"
653 def get_regions(self
):
654 return [self
.poste
.implantation
.region
]
657 def remunerations(self
):
658 return self
.rh_remunerations
.all().order_by('date_debut')
660 def remunerations_en_cours(self
):
661 return self
.rh_remunerations
.all().filter(date_fin__exact
=None).order_by('date_debut')
663 def get_salaire(self
):
665 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
670 class Dossier(Dossier_
):
671 __doc__
= Dossier_
.__doc__
672 poste
= models
.ForeignKey('%s.Poste' % app_context(),
674 related_name
='%(app_label)s_dossiers',
675 help_text
=u
"Taper le nom du poste ou du type de poste",
677 employe
= models
.ForeignKey('Employe', db_column
='employe',
678 help_text
=u
"Taper le nom de l'employé",
679 related_name
='%(app_label)s_dossiers',
680 verbose_name
=u
"Employé")
681 principal
= models
.BooleanField(verbose_name
=u
"Principal?", default
=True,
682 help_text
=u
"Ce Dossier est pour le principal Poste occupé par l'Employé")
685 class DossierPiece_(models
.Model
):
686 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
687 Ex.: Lettre de motivation.
689 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_dossierpieces')
690 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
691 fichier
= models
.FileField(verbose_name
= u
"Fichier",
692 upload_to
=dossier_piece_dispatch
,
693 storage
=storage_prive
)
699 def __unicode__(self
):
700 return u
'%s' % (self
.nom
)
702 class DossierPiece(DossierPiece_
):
705 class DossierCommentaire_(Commentaire
):
706 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
710 class DossierCommentaire(DossierCommentaire_
):
713 class DossierComparaison_(models
.Model
, DevisableMixin
):
715 Photo d'une comparaison salariale au moment de l'embauche.
717 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), related_name
='%(app_label)s_comparaisons')
718 objects
= DossierComparaisonManager()
720 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
721 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
722 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
723 montant
= models
.IntegerField(null
=True)
724 devise
= models
.ForeignKey('Devise', related_name
='+', null
=True, blank
=True)
730 class DossierComparaison(DossierComparaison_
):
735 class RemunerationMixin(AUFMetadata
):
736 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_remunerations')
738 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
740 verbose_name
= u
"Type de rémunération")
741 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
742 db_column
='type_revalorisation',
744 verbose_name
= u
"Type de revalorisation",
745 null
=True, blank
=True)
746 montant
= models
.DecimalField(null
=True, blank
=True,
747 default
=0, max_digits
=12, decimal_places
=2)
748 # Annuel (12 mois, 52 semaines, 364 jours?)
749 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
750 # commentaire = precision
751 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
752 # date_debut = anciennement date_effectif
753 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
754 null
=True, blank
=True)
755 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
756 null
=True, blank
=True)
760 ordering
= ['type__nom', '-date_fin']
762 def __unicode__(self
):
763 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
765 class Remuneration_(RemunerationMixin
, DevisableMixin
):
766 """Structure de rémunération (données budgétaires) en situation normale
767 pour un Dossier. Si un Evenement existe, utiliser la structure de
768 rémunération EvenementRemuneration de cet événement.
771 def montant_mois(self
):
772 return round(self
.montant
/ 12, 2)
774 def montant_avec_regime(self
):
775 return round(self
.montant
* (self
.dossier
.regime_travail
/100), 2)
777 def montant_euro_mois(self
):
778 return round(self
.montant_euros() / 12, 2)
780 def __unicode__(self
):
782 devise
= self
.devise
.code
785 return "%s %s" % (self
.montant
, devise
)
789 verbose_name
= u
"Rémunération"
790 verbose_name_plural
= u
"Rémunérations"
793 class Remuneration(Remuneration_
):
799 class ContratManager(NoDeleteManager
):
800 def get_query_set(self
):
801 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
804 class Contrat_(AUFMetadata
):
805 """Document juridique qui encadre la relation de travail d'un Employe
806 pour un Poste particulier. Pour un Dossier (qui documente cette
807 relation de travail) plusieurs contrats peuvent être associés.
809 objects
= ContratManager()
810 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_contrats')
811 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
813 verbose_name
= u
"type de contrat")
814 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
815 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
816 null
=True, blank
=True)
817 fichier
= models
.FileField(verbose_name
= u
"Fichier",
818 upload_to
=contrat_dispatch
,
819 storage
=storage_prive
,
820 null
=True, blank
=True)
824 ordering
= ['dossier__employe__nom']
825 verbose_name
= u
"Contrat"
826 verbose_name_plural
= u
"Contrats"
828 def __unicode__(self
):
829 return u
'%s - %s' % (self
.dossier
, self
.id)
831 class Contrat(Contrat_
):
837 #class Evenement_(AUFMetadata):
838 # """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
839 # d'un Dossier qui vient altérer des informations normales liées à un Dossier
840 # (ex.: la Remuneration).
842 # Ex.: congé de maternité, maladie...
844 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
845 # différent et une rémunération en conséquence. On souhaite toutefois
846 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
847 # du retour à la normale.
849 # dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
851 # nom = models.CharField(max_length=255)
852 # date_debut = models.DateField(verbose_name = u"Date de début")
853 # date_fin = models.DateField(verbose_name = u"Date de fin",
854 # null=True, blank=True)
859 # verbose_name = u"Évènement"
860 # verbose_name_plural = u"Évènements"
862 # def __unicode__(self):
863 # return u'%s' % (self.nom)
866 #class Evenement(Evenement_):
867 # __doc__ = Evenement_.__doc__
870 #class EvenementRemuneration_(RemunerationMixin):
871 # """Structure de rémunération liée à un Evenement qui remplace
872 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
875 # evenement = models.ForeignKey("Evenement", db_column='evenement',
877 # verbose_name = u"Évènement")
878 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
879 # # de l'Evenement associé
883 # ordering = ['evenement', 'type__nom', '-date_fin']
884 # verbose_name = u"Évènement - rémunération"
885 # verbose_name_plural = u"Évènements - rémunérations"
888 #class EvenementRemuneration(EvenementRemuneration_):
889 # __doc__ = EvenementRemuneration_.__doc__
895 #class EvenementRemuneration(EvenementRemuneration_):
896 # __doc__ = EvenementRemuneration_.__doc__
897 # TODO? class ContratPiece(models.Model):
902 class FamilleEmploi(AUFMetadata
):
903 """Catégorie utilisée dans la gestion des Postes.
904 Catégorie supérieure à TypePoste.
906 nom
= models
.CharField(max_length
=255)
910 verbose_name
= u
"Famille d'emploi"
911 verbose_name_plural
= u
"Familles d'emploi"
913 def __unicode__(self
):
914 return u
'%s' % (self
.nom
)
916 class TypePoste(AUFMetadata
):
917 """Catégorie de Poste.
919 nom
= models
.CharField(max_length
=255)
920 nom_feminin
= models
.CharField(max_length
=255,
921 verbose_name
= u
"Nom féminin")
923 is_responsable
= models
.BooleanField(default
=False,
924 verbose_name
= u
"Poste de responsabilité")
925 famille_emploi
= models
.ForeignKey('FamilleEmploi',
926 db_column
='famille_emploi',
928 verbose_name
= u
"famille d'emploi")
932 verbose_name
= u
"Type de poste"
933 verbose_name_plural
= u
"Types de poste"
935 def __unicode__(self
):
936 return u
'%s' % (self
.nom
)
939 TYPE_PAIEMENT_CHOICES
= (
940 (u
'Régulier', u
'Régulier'),
941 (u
'Ponctuel', u
'Ponctuel'),
944 NATURE_REMUNERATION_CHOICES
= (
945 (u
'Accessoire', u
'Accessoire'),
946 (u
'Charges', u
'Charges'),
947 (u
'Indemnité', u
'Indemnité'),
948 (u
'RAS', u
'Rémunération autre source'),
949 (u
'Traitement', u
'Traitement'),
952 class TypeRemuneration(AUFMetadata
):
953 """Catégorie de Remuneration.
955 objects
= TypeRemunerationManager()
957 nom
= models
.CharField(max_length
=255)
958 type_paiement
= models
.CharField(max_length
=30,
959 choices
=TYPE_PAIEMENT_CHOICES
,
960 verbose_name
= u
"Type de paiement")
961 nature_remuneration
= models
.CharField(max_length
=30,
962 choices
=NATURE_REMUNERATION_CHOICES
,
963 verbose_name
= u
"Nature de la rémunération")
964 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
968 verbose_name
= u
"Type de rémunération"
969 verbose_name_plural
= u
"Types de rémunération"
971 def __unicode__(self
):
973 archive
= u
"(archivé)"
976 return u
'%s %s' % (self
.nom
, archive
)
978 class TypeRevalorisation(AUFMetadata
):
979 """Justification du changement de la Remuneration.
980 (Actuellement utilisé dans aucun traitement informatique.)
982 nom
= models
.CharField(max_length
=255)
986 verbose_name
= u
"Type de revalorisation"
987 verbose_name_plural
= u
"Types de revalorisation"
989 def __unicode__(self
):
990 return u
'%s' % (self
.nom
)
992 class Service(AUFMetadata
):
993 """Unité administrative où les Postes sont rattachés.
995 objects
= ServiceManager()
997 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
998 nom
= models
.CharField(max_length
=255)
1002 verbose_name
= u
"Service"
1003 verbose_name_plural
= u
"Services"
1005 def __unicode__(self
):
1007 archive
= u
"(archivé)"
1010 return u
'%s %s' % (self
.nom
, archive
)
1013 TYPE_ORGANISME_CHOICES
= (
1014 ('MAD', 'Mise à disposition'),
1015 ('DET', 'Détachement'),
1018 class OrganismeBstg(AUFMetadata
):
1019 """Organisation d'où provient un Employe mis à disposition (MAD) de
1020 ou détaché (DET) à l'AUF à titre gratuit.
1022 (BSTG = bien et service à titre gratuit.)
1024 nom
= models
.CharField(max_length
=255)
1025 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1026 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1028 related_name
='organismes_bstg',
1029 null
=True, blank
=True)
1032 ordering
= ['type', 'nom']
1033 verbose_name
= u
"Organisme BSTG"
1034 verbose_name_plural
= u
"Organismes BSTG"
1036 def __unicode__(self
):
1037 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1039 prefix_implantation
= "pays__region"
1040 def get_regions(self
):
1041 return [self
.pays
.region
]
1044 class Statut(AUFMetadata
):
1045 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1048 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.")
1049 nom
= models
.CharField(max_length
=255)
1053 verbose_name
= u
"Statut d'employé"
1054 verbose_name_plural
= u
"Statuts d'employé"
1056 def __unicode__(self
):
1057 return u
'%s : %s' % (self
.code
, self
.nom
)
1060 TYPE_CLASSEMENT_CHOICES
= (
1061 ('S', 'S -Soutien'),
1062 ('T', 'T - Technicien'),
1063 ('P', 'P - Professionel'),
1065 ('D', 'D - Direction'),
1066 ('SO', 'SO - Sans objet [expatriés]'),
1067 ('HG', 'HG - Hors grille [direction]'),
1070 class ClassementManager(models
.Manager
):
1072 Ordonner les spcéfiquement les classements.
1074 def get_query_set(self
):
1075 qs
= super(self
.__class__
, self
).get_query_set()
1076 qs
= qs
.extra(select
={'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'})
1077 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1081 class Classement_(AUFMetadata
):
1082 """Éléments de classement de la
1083 "Grille générique de classement hiérarchique".
1085 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1086 classement dans la grille. Le classement donne le coefficient utilisé dans:
1088 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1090 objects
= ClassementManager()
1093 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1094 echelon
= models
.IntegerField(verbose_name
=u
"Échelon", blank
=True, default
=0)
1095 degre
= models
.IntegerField(verbose_name
=u
"Degré", blank
=True, default
=0)
1096 coefficient
= models
.FloatField(default
=0, verbose_name
=u
"Coefficient",
1099 # annee # au lieu de date_debut et date_fin
1100 commentaire
= models
.TextField(null
=True, blank
=True)
1104 ordering
= ['type','echelon','degre','coefficient']
1105 verbose_name
= u
"Classement"
1106 verbose_name_plural
= u
"Classements"
1108 def __unicode__(self
):
1109 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1111 class Classement(Classement_
):
1112 __doc__
= Classement_
.__doc__
1115 class TauxChange_(AUFMetadata
):
1116 """Taux de change de la devise vers l'euro (EUR)
1117 pour chaque année budgétaire.
1120 devise
= models
.ForeignKey('Devise', db_column
='devise')
1121 annee
= models
.IntegerField(verbose_name
= u
"Année")
1122 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1126 ordering
= ['-annee', 'devise__code']
1127 verbose_name
= u
"Taux de change"
1128 verbose_name_plural
= u
"Taux de change"
1130 def __unicode__(self
):
1131 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1134 class TauxChange(TauxChange_
):
1135 __doc__
= TauxChange_
.__doc__
1137 class ValeurPointManager(NoDeleteManager
):
1139 def get_query_set(self
):
1140 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1143 class ValeurPoint_(AUFMetadata
):
1144 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1145 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1146 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1148 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1151 actuelles
= ValeurPointManager()
1153 valeur
= models
.FloatField(null
=True)
1154 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1155 implantation
= models
.ForeignKey(ref
.Implantation
,
1156 db_column
='implantation',
1157 related_name
='%(app_label)s_valeur_point')
1159 annee
= models
.IntegerField()
1162 ordering
= ['-annee', 'implantation__nom']
1164 verbose_name
= u
"Valeur du point"
1165 verbose_name_plural
= u
"Valeurs du point"
1167 def __unicode__(self
):
1168 return u
'%s %s %s [%s] %s' % (self
.devise
.code
, self
.annee
, self
.valeur
, self
.implantation
.nom_court
, self
.devise
.nom
)
1171 class ValeurPoint(ValeurPoint_
):
1172 __doc__
= ValeurPoint_
.__doc__
1176 class Devise(AUFMetadata
):
1177 """Devise monétaire.
1180 objects
= DeviseManager()
1182 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1183 code
= models
.CharField(max_length
=10, unique
=True)
1184 nom
= models
.CharField(max_length
=255)
1188 verbose_name
= u
"Devise"
1189 verbose_name_plural
= u
"Devises"
1191 def __unicode__(self
):
1192 return u
'%s - %s' % (self
.code
, self
.nom
)
1194 class TypeContrat(AUFMetadata
):
1197 nom
= models
.CharField(max_length
=255)
1198 nom_long
= models
.CharField(max_length
=255)
1202 verbose_name
= u
"Type de contrat"
1203 verbose_name_plural
= u
"Types de contrat"
1205 def __unicode__(self
):
1206 return u
'%s' % (self
.nom
)
1211 class ResponsableImplantation(AUFMetadata
):
1212 """Le responsable d'une implantation.
1213 Anciennement géré sur le Dossier du responsable.
1215 employe
= models
.ForeignKey('Employe', db_column
='employe',
1217 null
=True, blank
=True)
1218 implantation
= models
.ForeignKey(ref
.Implantation
,
1219 db_column
='implantation', related_name
='+',
1222 def __unicode__(self
):
1223 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1226 ordering
= ['implantation__nom']
1227 verbose_name
= "Responsable d'implantation"
1228 verbose_name_plural
= "Responsables d'implantation"