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
, PosteComparaisonManager
, DeviseManager
19 # Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
20 # Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
23 models_stack
= [s
[1].split('/')[-2] for s
in inspect
.stack() if s
[1].endswith('models.py')]
24 return models_stack
[-1]
28 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
29 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
30 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
33 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
34 base_url
=settings
.PRIVE_MEDIA_URL
)
36 def poste_piece_dispatch(instance
, filename
):
37 path
= "%s/poste/%s/%s" % (instance
._meta
.app_label
, instance
.poste_id
, filename
)
40 def dossier_piece_dispatch(instance
, filename
):
41 path
= "%s/dossier/%s/%s" % (instance
._meta
.app_label
, instance
.dossier_id
, filename
)
44 def employe_piece_dispatch(instance
, filename
):
45 path
= "%s/employe/%s/%s" % (instance
._meta
.app_label
, instance
.employe_id
, filename
)
48 def contrat_dispatch(instance
, filename
):
49 path
= "%s/contrat/%s/%s" % (instance
._meta
.app_label
, instance
.dossier_id
, filename
)
53 class DevisableMixin(object):
55 def get_annee_pour_taux_devise(self
):
56 raise NotImplementedError
58 def taux_devise(self
):
59 if self
.devise
is None:
61 if self
.devise
.code
== "EUR":
64 annee
= self
.get_annee_pour_taux_devise()
65 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
69 raise Exception(u
"Pas de taux pour %s en %s" % (self
.devise
.code
, annee
))
72 raise Exception(u
"Il existe plusieurs taux de %s en %s" %
73 (self
.devise
.code
, annee
))
77 def montant_euros(self
):
79 taux
= self
.taux_devise()
84 return int(round(float(self
.montant
) * float(taux
), 2))
87 class Commentaire(AUFMetadata
):
88 texte
= models
.TextField()
89 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+', verbose_name
=u
"Commentaire de")
93 ordering
= ['-date_creation']
95 def __unicode__(self
):
96 return u
'%s' % (self
.texte
)
101 POSTE_APPEL_CHOICES
= (
102 ('interne', 'Interne'),
103 ('externe', 'Externe'),
106 class Poste_(AUFMetadata
):
107 """Un Poste est un emploi (job) à combler dans une implantation.
108 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
109 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
112 objects
= PosteManager()
115 nom
= models
.CharField(max_length
=255,
116 verbose_name
= u
"Titre du poste", )
117 nom_feminin
= models
.CharField(max_length
=255,
118 verbose_name
= u
"Titre du poste (au féminin)",
120 implantation
= models
.ForeignKey(ref
.Implantation
, help_text
=u
"Taper le nom de l'implantation ou sa région",
121 db_column
='implantation', related_name
='+')
122 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste', help_text
=u
"Taper le nom du type de poste",
125 verbose_name
=u
"type de poste")
126 service
= models
.ForeignKey('Service', db_column
='service',
128 verbose_name
= u
"direction/service/pôle support",
130 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
133 help_text
=u
"Taper le nom du poste ou du type de poste",
134 verbose_name
= u
"Poste du responsable", )
137 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
138 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
139 verbose_name
= u
"Temps de travail",
140 help_text
="% du temps complet")
141 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
142 decimal_places
=2, null
=True,
143 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
144 verbose_name
= u
"Nb. heures par semaine")
147 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
148 null
=True, blank
=True)
149 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
150 null
=True, blank
=True)
151 mise_a_disposition
= models
.NullBooleanField(
152 verbose_name
= u
"Mise à disposition",
153 null
=True, default
=False)
154 appel
= models
.CharField(max_length
=10, null
=True,
155 verbose_name
= u
"Appel à candidature",
156 choices
=POSTE_APPEL_CHOICES
,
160 classement_min
= models
.ForeignKey('Classement',
161 db_column
='classement_min', related_name
='+',
162 null
=True, blank
=True)
163 classement_max
= models
.ForeignKey('Classement',
164 db_column
='classement_max', related_name
='+',
165 null
=True, blank
=True)
166 valeur_point_min
= models
.ForeignKey('ValeurPoint', help_text
=u
"Taper le code ou le nom de l'implantation",
167 db_column
='valeur_point_min', related_name
='+',
168 null
=True, blank
=True)
169 valeur_point_max
= models
.ForeignKey('ValeurPoint', help_text
=u
"Taper le code ou le nom de l'implantation",
170 db_column
='valeur_point_max', related_name
='+',
171 null
=True, blank
=True)
172 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
174 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
176 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
177 null
=True, default
=0)
178 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
179 null
=True, default
=0)
180 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
181 null
=True, default
=0)
182 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
183 null
=True, default
=0)
184 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
185 null
=True, default
=0)
186 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
187 null
=True, default
=0)
189 # Comparatifs de rémunération
190 devise_comparaison
= models
.ForeignKey('Devise', null
=True, blank
=True,
191 db_column
='devise_comparaison',
193 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
194 null
=True, blank
=True)
195 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
196 null
=True, blank
=True)
197 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
198 null
=True, blank
=True)
199 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
200 null
=True, blank
=True)
201 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
202 null
=True, blank
=True)
203 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
204 null
=True, blank
=True)
205 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
206 null
=True, blank
=True)
207 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
208 null
=True, blank
=True)
209 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
210 null
=True, blank
=True)
211 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
212 null
=True, blank
=True)
215 justification
= models
.TextField(null
=True, blank
=True)
218 date_debut
= models
.DateField(verbose_name
=u
"Date de début", help_text
=HELP_TEXT_DATE
,
219 null
=True, blank
=True)
220 date_fin
= models
.DateField(verbose_name
=u
"Date de fin", help_text
=HELP_TEXT_DATE
,
221 null
=True, blank
=True)
225 ordering
= ['implantation__nom', 'nom']
226 verbose_name
= u
"Poste"
227 verbose_name_plural
= u
"Postes"
230 def __unicode__(self
):
231 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
233 return representation
236 prefix_implantation
= "implantation__region"
237 def get_regions(self
):
238 return [self
.implantation
.region
]
242 __doc__
= Poste_
.__doc__
244 # meta dématérialisation : pour permettre le filtrage
245 vacant
= models
.NullBooleanField(verbose_name
= u
"vacant", null
=True, blank
=True)
249 if self
.occupe_par():
253 def occupe_par(self
):
254 """Retourne la liste d'employé occupant ce poste.
255 Généralement, retourne une liste d'un élément.
256 Si poste inoccupé, retourne liste vide.
257 UTILISE pour mettre a jour le flag vacant
259 return [d
.employe
for d
in self
.rh_dossiers
.filter(supprime
=False).exclude(date_fin__lt
=date
.today())]
262 POSTE_FINANCEMENT_CHOICES
= (
263 ('A', 'A - Frais de personnel'),
264 ('B', 'B - Projet(s)-Titre(s)'),
269 class PosteFinancement_(models
.Model
):
270 """Pour un Poste, structure d'informations décrivant comment on prévoit
273 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_financements')
274 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
275 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
276 help_text
="ex.: 33.33 % (décimale avec point)")
277 commentaire
= models
.TextField(
278 help_text
="Spécifiez la source de financement.")
284 def __unicode__(self
):
285 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
288 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
291 class PosteFinancement(PosteFinancement_
):
295 class PostePiece_(models
.Model
):
296 """Documents relatifs au Poste.
297 Ex.: Description de poste
299 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_pieces')
300 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
301 fichier
= models
.FileField(verbose_name
= u
"Fichier",
302 upload_to
=poste_piece_dispatch
,
303 storage
=storage_prive
)
309 def __unicode__(self
):
310 return u
'%s' % (self
.nom
)
312 class PostePiece(PostePiece_
):
315 class PosteComparaison_(AUFMetadata
, DevisableMixin
):
317 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
319 poste
= models
.ForeignKey('%s.Poste' % app_context(), related_name
='%(app_label)s_comparaisons_internes')
320 objects
= PosteComparaisonManager()
322 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
323 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
324 montant
= models
.IntegerField(null
=True)
325 devise
= models
.ForeignKey("Devise", related_name
='+', null
=True, blank
=True)
331 def get_annee_pour_taux_devise(self
):
332 return self
.poste
.date_debut
.year
334 def __unicode__(self
):
337 class PosteComparaison(PosteComparaison_
):
340 class PosteCommentaire_(Commentaire
):
341 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='+')
346 class PosteCommentaire(PosteCommentaire_
):
351 class Employe(AUFMetadata
):
352 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
353 Dossiers qu'il occupe ou a occupé de Postes.
355 Cette classe aurait pu avantageusement s'appeler Personne car la notion
356 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
359 nom
= models
.CharField(max_length
=255)
360 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
361 nom_affichage
= models
.CharField(max_length
=255,
362 verbose_name
= u
"Nom d'affichage",
363 null
=True, blank
=True)
364 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
365 db_column
='nationalite',
366 related_name
='employes_nationalite',
367 verbose_name
= u
"Nationalité",
368 blank
=True, null
=True)
369 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
370 help_text
=HELP_TEXT_DATE
,
371 validators
=[validate_date_passee
],
372 null
=True, blank
=True)
373 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
376 situation_famille
= models
.CharField(max_length
=1,
377 choices
=SITUATION_CHOICES
,
378 verbose_name
= u
"Situation familiale",
379 null
=True, blank
=True)
380 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
381 help_text
=HELP_TEXT_DATE
,
382 null
=True, blank
=True)
385 tel_domicile
= models
.CharField(max_length
=255,
386 verbose_name
= u
"Tél. domicile",
387 null
=True, blank
=True)
388 tel_cellulaire
= models
.CharField(max_length
=255,
389 verbose_name
= u
"Tél. cellulaire",
390 null
=True, blank
=True)
391 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
392 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
393 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
394 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
395 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
396 related_name
='employes',
397 null
=True, blank
=True)
399 # meta dématérialisation : pour permettre le filtrage
400 nb_postes
= models
.IntegerField(verbose_name
= u
"nombre de postes", null
=True, blank
=True)
403 ordering
= ['nom','prenom']
404 verbose_name
= u
"Employé"
405 verbose_name_plural
= u
"Employés"
407 def __unicode__(self
):
408 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
412 if self
.genre
.upper() == u
'M':
414 elif self
.genre
.upper() == u
'F':
419 """Retourne l'URL du service retournant la photo de l'Employe.
420 Équivalent reverse url 'rh_photo' avec id en param.
422 from django
.core
.urlresolvers
import reverse
423 return reverse('rh_photo', kwargs
={'id':self
.id})
425 def dossiers_passes(self
):
427 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
428 for d
in dossiers_passes
:
430 return dossiers_passes
432 def dossiers_futurs(self
):
434 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
436 def dossiers_encours(self
):
437 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
438 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
439 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
441 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
442 for d
in dossiers_encours
:
443 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
444 return dossiers_encours
446 def postes_encours(self
):
447 postes_encours
= set()
448 for d
in self
.dossiers_encours():
449 postes_encours
.add(d
.poste
)
450 return postes_encours
452 def poste_principal(self
):
454 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
456 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
458 poste
= Poste
.objects
.none()
460 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
465 prefix_implantation
= "rh_dossiers__poste__implantation__region"
466 def get_regions(self
):
468 for d
in self
.dossiers
.all():
469 regions
.append(d
.poste
.implantation
.region
)
473 class EmployePiece(models
.Model
):
474 """Documents relatifs à un employé.
477 employe
= models
.ForeignKey('Employe', db_column
='employe')
478 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
479 fichier
= models
.FileField(verbose_name
="Fichier",
480 upload_to
=employe_piece_dispatch
,
481 storage
=storage_prive
)
485 verbose_name
= u
"Employé pièce"
486 verbose_name_plural
= u
"Employé pièces"
488 def __unicode__(self
):
489 return u
'%s' % (self
.nom
)
491 class EmployeCommentaire(Commentaire
):
492 employe
= models
.ForeignKey('Employe', db_column
='employe',
496 verbose_name
= u
"Employé commentaire"
497 verbose_name_plural
= u
"Employé commentaires"
500 LIEN_PARENTE_CHOICES
= (
501 ('Conjoint', 'Conjoint'),
502 ('Conjointe', 'Conjointe'),
507 class AyantDroit(AUFMetadata
):
508 """Personne en relation avec un Employe.
511 nom
= models
.CharField(max_length
=255)
512 prenom
= models
.CharField(max_length
=255,
513 verbose_name
= u
"Prénom",)
514 nom_affichage
= models
.CharField(max_length
=255,
515 verbose_name
= u
"Nom d'affichage",
516 null
=True, blank
=True)
517 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
518 db_column
='nationalite',
519 related_name
='ayantdroits_nationalite',
520 verbose_name
= u
"Nationalité",
521 null
=True, blank
=True)
522 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
523 help_text
=HELP_TEXT_DATE
,
524 validators
=[validate_date_passee
],
525 null
=True, blank
=True)
526 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
529 employe
= models
.ForeignKey('Employe', db_column
='employe',
530 related_name
='ayantdroits',
531 verbose_name
= u
"Employé")
532 lien_parente
= models
.CharField(max_length
=10,
533 choices
=LIEN_PARENTE_CHOICES
,
534 verbose_name
= u
"Lien de parenté",
535 null
=True, blank
=True)
539 verbose_name
= u
"Ayant droit"
540 verbose_name_plural
= u
"Ayants droit"
542 def __unicode__(self
):
543 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
545 prefix_implantation
= "employe__dossiers__poste__implantation__region"
546 def get_regions(self
):
548 for d
in self
.employe
.dossiers
.all():
549 regions
.append(d
.poste
.implantation
.region
)
553 class AyantDroitCommentaire(Commentaire
):
554 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
560 STATUT_RESIDENCE_CHOICES
= (
562 ('expat', 'Expatrié'),
565 COMPTE_COMPTA_CHOICES
= (
571 class Dossier_(AUFMetadata
):
572 """Le Dossier regroupe les informations relatives à l'occupation
573 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
576 Plusieurs Contrats peuvent être associés au Dossier.
577 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
578 lequel aucun Dossier n'existe est un poste vacant.
581 objects
= DossierManager()
584 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
585 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
586 db_column
='organisme_bstg',
588 verbose_name
= u
"Organisme",
589 help_text
="Si détaché (DET) ou \
590 mis à disposition (MAD), \
591 préciser l'organisme.",
592 null
=True, blank
=True)
595 remplacement
= models
.BooleanField(default
=False)
596 remplacement_de
= models
.ForeignKey('self', related_name
='+',
597 help_text
=u
"Taper le nom de l'employé",
598 null
=True, blank
=True)
599 statut_residence
= models
.CharField(max_length
=10, default
='local',
600 verbose_name
= u
"Statut", null
=True,
601 choices
=STATUT_RESIDENCE_CHOICES
)
604 classement
= models
.ForeignKey('Classement', db_column
='classement',
606 null
=True, blank
=True)
607 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
609 default
=REGIME_TRAVAIL_DEFAULT
,
610 verbose_name
= u
"Régime de travail",
611 help_text
="% du temps complet")
612 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
613 decimal_places
=2, null
=True,
614 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
615 verbose_name
= u
"Nb. heures par semaine")
617 # Occupation du Poste par cet Employe (anciennement "mandat")
618 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
620 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
622 null
=True, blank
=True)
629 ordering
= ['employe__nom', ]
630 verbose_name
= u
"Dossier"
631 verbose_name_plural
= "Dossiers"
633 def salaire_theorique(self
):
634 annee
= date
.today().year
635 coeff
= self
.classement
.coefficient
636 implantation
= self
.poste
.implantation
637 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
639 montant
= coeff
* point
.valeur
640 devise
= point
.devise
641 return {'montant':montant
, 'devise':devise
}
643 def __unicode__(self
):
644 poste
= self
.poste
.nom
645 if self
.employe
.genre
== 'F':
646 poste
= self
.poste
.nom_feminin
647 return u
'%s - %s' % (self
.employe
, poste
)
649 prefix_implantation
= "poste__implantation__region"
650 def get_regions(self
):
651 return [self
.poste
.implantation
.region
]
654 def remunerations(self
):
655 return self
.rh_remunerations
.all().order_by('date_debut')
657 def remunerations_en_cours(self
):
658 return self
.rh_remunerations
.all().filter(date_fin__exact
=None).order_by('date_debut')
660 def get_salaire(self
):
662 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
667 class Dossier(Dossier_
):
668 __doc__
= Dossier_
.__doc__
669 poste
= models
.ForeignKey('%s.Poste' % app_context(),
671 related_name
='%(app_label)s_dossiers',
672 help_text
=u
"Taper le nom du poste ou du type de poste",
674 employe
= models
.ForeignKey('Employe', db_column
='employe',
675 help_text
=u
"Taper le nom de l'employé",
676 related_name
='%(app_label)s_dossiers',
677 verbose_name
=u
"Employé")
680 class DossierPiece_(models
.Model
):
681 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
682 Ex.: Lettre de motivation.
684 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_dossierpieces')
685 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
686 fichier
= models
.FileField(verbose_name
= u
"Fichier",
687 upload_to
=dossier_piece_dispatch
,
688 storage
=storage_prive
)
694 def __unicode__(self
):
695 return u
'%s' % (self
.nom
)
697 class DossierPiece(DossierPiece_
):
700 class DossierCommentaire_(Commentaire
):
701 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
705 class DossierCommentaire(DossierCommentaire_
):
708 class DossierComparaison_(models
.Model
, DevisableMixin
):
710 Photo d'une comparaison salariale au moment de l'embauche.
712 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), related_name
='%(app_label)s_comparaisons')
713 objects
= DossierComparaisonManager()
715 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
716 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
717 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
718 montant
= models
.IntegerField(null
=True)
719 devise
= models
.ForeignKey('Devise', related_name
='+', null
=True, blank
=True)
724 def get_annee_pour_taux_devise(self
):
725 return self
.dossier
.contrat_date_debut
.year
728 class DossierComparaison(DossierComparaison_
):
733 class RemunerationMixin(AUFMetadata
):
734 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_remunerations')
736 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
738 verbose_name
= u
"Type de rémunération")
739 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
740 db_column
='type_revalorisation',
742 verbose_name
= u
"Type de revalorisation",
743 null
=True, blank
=True)
744 montant
= models
.DecimalField(null
=True, blank
=True,
745 default
=0, max_digits
=12, decimal_places
=2)
746 # Annuel (12 mois, 52 semaines, 364 jours?)
747 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
748 # commentaire = precision
749 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
750 # date_debut = anciennement date_effectif
751 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
752 null
=True, blank
=True)
753 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
754 null
=True, blank
=True)
758 ordering
= ['type__nom', '-date_fin']
760 def __unicode__(self
):
761 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
763 class Remuneration_(RemunerationMixin
, DevisableMixin
):
764 """Structure de rémunération (données budgétaires) en situation normale
765 pour un Dossier. Si un Evenement existe, utiliser la structure de
766 rémunération EvenementRemuneration de cet événement.
769 def montant_mois(self
):
770 return round(self
.montant
/ 12, 2)
772 def montant_avec_regime(self
):
773 return round(self
.montant
* (self
.dossier
.regime_travail
/100), 2)
775 def get_annee_pour_taux_devise(self
):
776 annee
= datetime
.datetime
.now().year
777 if self
.dossier
.poste
.date_debut
is not None:
778 annee
= self
.dossier
.poste
.date_debut
.year
779 if self
.dossier
.date_debut
is not None:
780 annee
= self
.dossier
.date_debut
.year
781 if self
.date_debut
is not None:
782 annee
= self
.date_debut
.year
785 def montant_euro_mois(self
):
786 return round(self
.montant_euros() / 12, 2)
788 def __unicode__(self
):
790 devise
= self
.devise
.code
793 return "%s %s" % (self
.montant
, devise
)
797 verbose_name
= u
"Rémunération"
798 verbose_name_plural
= u
"Rémunérations"
801 class Remuneration(Remuneration_
):
807 class ContratManager(NoDeleteManager
):
808 def get_query_set(self
):
809 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
812 class Contrat_(AUFMetadata
):
813 """Document juridique qui encadre la relation de travail d'un Employe
814 pour un Poste particulier. Pour un Dossier (qui documente cette
815 relation de travail) plusieurs contrats peuvent être associés.
817 objects
= ContratManager()
818 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_contrats')
819 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
821 verbose_name
= u
"type de contrat")
822 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
823 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
824 null
=True, blank
=True)
825 fichier
= models
.FileField(verbose_name
= u
"Fichier",
826 upload_to
=contrat_dispatch
,
827 storage
=storage_prive
,
828 null
=True, blank
=True)
832 ordering
= ['dossier__employe__nom']
833 verbose_name
= u
"Contrat"
834 verbose_name_plural
= u
"Contrats"
836 def __unicode__(self
):
837 return u
'%s - %s' % (self
.dossier
, self
.id)
839 class Contrat(Contrat_
):
845 #class Evenement_(AUFMetadata):
846 # """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
847 # d'un Dossier qui vient altérer des informations normales liées à un Dossier
848 # (ex.: la Remuneration).
850 # Ex.: congé de maternité, maladie...
852 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
853 # différent et une rémunération en conséquence. On souhaite toutefois
854 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
855 # du retour à la normale.
857 # dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
859 # nom = models.CharField(max_length=255)
860 # date_debut = models.DateField(verbose_name = u"Date de début")
861 # date_fin = models.DateField(verbose_name = u"Date de fin",
862 # null=True, blank=True)
867 # verbose_name = u"Évènement"
868 # verbose_name_plural = u"Évènements"
870 # def __unicode__(self):
871 # return u'%s' % (self.nom)
874 #class Evenement(Evenement_):
875 # __doc__ = Evenement_.__doc__
878 #class EvenementRemuneration_(RemunerationMixin):
879 # """Structure de rémunération liée à un Evenement qui remplace
880 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
883 # evenement = models.ForeignKey("Evenement", db_column='evenement',
885 # verbose_name = u"Évènement")
886 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
887 # # de l'Evenement associé
891 # ordering = ['evenement', 'type__nom', '-date_fin']
892 # verbose_name = u"Évènement - rémunération"
893 # verbose_name_plural = u"Évènements - rémunérations"
896 #class EvenementRemuneration(EvenementRemuneration_):
897 # __doc__ = EvenementRemuneration_.__doc__
903 #class EvenementRemuneration(EvenementRemuneration_):
904 # __doc__ = EvenementRemuneration_.__doc__
905 # TODO? class ContratPiece(models.Model):
910 class FamilleEmploi(AUFMetadata
):
911 """Catégorie utilisée dans la gestion des Postes.
912 Catégorie supérieure à TypePoste.
914 nom
= models
.CharField(max_length
=255)
918 verbose_name
= u
"Famille d'emploi"
919 verbose_name_plural
= u
"Familles d'emploi"
921 def __unicode__(self
):
922 return u
'%s' % (self
.nom
)
924 class TypePoste(AUFMetadata
):
925 """Catégorie de Poste.
927 nom
= models
.CharField(max_length
=255)
928 nom_feminin
= models
.CharField(max_length
=255,
929 verbose_name
= u
"Nom féminin")
931 is_responsable
= models
.BooleanField(default
=False,
932 verbose_name
= u
"Poste de responsabilité")
933 famille_emploi
= models
.ForeignKey('FamilleEmploi',
934 db_column
='famille_emploi',
936 verbose_name
= u
"famille d'emploi")
940 verbose_name
= u
"Type de poste"
941 verbose_name_plural
= u
"Types de poste"
943 def __unicode__(self
):
944 return u
'%s' % (self
.nom
)
947 TYPE_PAIEMENT_CHOICES
= (
948 (u
'Régulier', u
'Régulier'),
949 (u
'Ponctuel', u
'Ponctuel'),
952 NATURE_REMUNERATION_CHOICES
= (
953 (u
'Accessoire', u
'Accessoire'),
954 (u
'Charges', u
'Charges'),
955 (u
'Indemnité', u
'Indemnité'),
956 (u
'RAS', u
'Rémunération autre source'),
957 (u
'Traitement', u
'Traitement'),
960 class TypeRemuneration(AUFMetadata
):
961 """Catégorie de Remuneration.
963 nom
= models
.CharField(max_length
=255)
964 type_paiement
= models
.CharField(max_length
=30,
965 choices
=TYPE_PAIEMENT_CHOICES
,
966 verbose_name
= u
"Type de paiement")
967 nature_remuneration
= models
.CharField(max_length
=30,
968 choices
=NATURE_REMUNERATION_CHOICES
,
969 verbose_name
= u
"Nature de la rémunération")
973 verbose_name
= u
"Type de rémunération"
974 verbose_name_plural
= u
"Types de rémunération"
976 def __unicode__(self
):
977 return u
'%s' % (self
.nom
)
979 class TypeRevalorisation(AUFMetadata
):
980 """Justification du changement de la Remuneration.
981 (Actuellement utilisé dans aucun traitement informatique.)
983 nom
= models
.CharField(max_length
=255)
987 verbose_name
= u
"Type de revalorisation"
988 verbose_name_plural
= u
"Types de revalorisation"
990 def __unicode__(self
):
991 return u
'%s' % (self
.nom
)
993 class Service(AUFMetadata
):
994 """Unité administrative où les Postes sont rattachés.
996 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
997 nom
= models
.CharField(max_length
=255)
1001 verbose_name
= u
"Service"
1002 verbose_name_plural
= u
"Services"
1004 def __unicode__(self
):
1006 archive
= u
"(archivé)"
1009 return u
'%s %s' % (self
.nom
, archive
)
1012 TYPE_ORGANISME_CHOICES
= (
1013 ('MAD', 'Mise à disposition'),
1014 ('DET', 'Détachement'),
1017 class OrganismeBstg(AUFMetadata
):
1018 """Organisation d'où provient un Employe mis à disposition (MAD) de
1019 ou détaché (DET) à l'AUF à titre gratuit.
1021 (BSTG = bien et service à titre gratuit.)
1023 nom
= models
.CharField(max_length
=255)
1024 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1025 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1027 related_name
='organismes_bstg',
1028 null
=True, blank
=True)
1031 ordering
= ['type', 'nom']
1032 verbose_name
= u
"Organisme BSTG"
1033 verbose_name_plural
= u
"Organismes BSTG"
1035 def __unicode__(self
):
1036 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1038 prefix_implantation
= "pays__region"
1039 def get_regions(self
):
1040 return [self
.pays
.region
]
1043 class Statut(AUFMetadata
):
1044 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1047 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.")
1048 nom
= models
.CharField(max_length
=255)
1052 verbose_name
= u
"Statut d'employé"
1053 verbose_name_plural
= u
"Statuts d'employé"
1055 def __unicode__(self
):
1056 return u
'%s : %s' % (self
.code
, self
.nom
)
1059 TYPE_CLASSEMENT_CHOICES
= (
1060 ('S', 'S -Soutien'),
1061 ('T', 'T - Technicien'),
1062 ('P', 'P - Professionel'),
1064 ('D', 'D - Direction'),
1065 ('SO', 'SO - Sans objet [expatriés]'),
1066 ('HG', 'HG - Hors grille [direction]'),
1069 class ClassementManager(models
.Manager
):
1071 Ordonner les spcéfiquement les classements.
1073 def get_query_set(self
):
1074 qs
= super(self
.__class__
, self
).get_query_set()
1075 qs
= qs
.extra(select
={'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'})
1076 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1080 class Classement_(AUFMetadata
):
1081 """Éléments de classement de la
1082 "Grille générique de classement hiérarchique".
1084 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1085 classement dans la grille. Le classement donne le coefficient utilisé dans:
1087 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1089 objects
= ClassementManager()
1092 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1093 echelon
= models
.IntegerField(verbose_name
=u
"Échelon", blank
=True, default
=0)
1094 degre
= models
.IntegerField(verbose_name
=u
"Degré", blank
=True, default
=0)
1095 coefficient
= models
.FloatField(default
=0, verbose_name
=u
"Coefficient",
1098 # annee # au lieu de date_debut et date_fin
1099 commentaire
= models
.TextField(null
=True, blank
=True)
1103 ordering
= ['type','echelon','degre','coefficient']
1104 verbose_name
= u
"Classement"
1105 verbose_name_plural
= u
"Classements"
1107 def __unicode__(self
):
1108 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1110 class Classement(Classement_
):
1111 __doc__
= Classement_
.__doc__
1114 class TauxChange_(AUFMetadata
):
1115 """Taux de change de la devise vers l'euro (EUR)
1116 pour chaque année budgétaire.
1119 devise
= models
.ForeignKey('Devise', db_column
='devise')
1120 annee
= models
.IntegerField(verbose_name
= u
"Année")
1121 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1125 ordering
= ['-annee', 'devise__code']
1126 verbose_name
= u
"Taux de change"
1127 verbose_name_plural
= u
"Taux de change"
1129 def __unicode__(self
):
1130 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1133 class TauxChange(TauxChange_
):
1134 __doc__
= TauxChange_
.__doc__
1136 class ValeurPointManager(NoDeleteManager
):
1138 def get_query_set(self
):
1139 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1142 class ValeurPoint_(AUFMetadata
):
1143 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1144 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1145 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1147 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1150 actuelles
= ValeurPointManager()
1152 valeur
= models
.FloatField(null
=True)
1153 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1154 implantation
= models
.ForeignKey(ref
.Implantation
,
1155 db_column
='implantation',
1156 related_name
='%(app_label)s_valeur_point')
1158 annee
= models
.IntegerField()
1161 ordering
= ['-annee', 'implantation__nom']
1163 verbose_name
= u
"Valeur du point"
1164 verbose_name_plural
= u
"Valeurs du point"
1166 def __unicode__(self
):
1167 return u
'%s %s %s [%s] %s' % (self
.devise
.code
, self
.annee
, self
.valeur
, self
.implantation
.nom_court
, self
.devise
.nom
)
1170 class ValeurPoint(ValeurPoint_
):
1171 __doc__
= ValeurPoint_
.__doc__
1175 class Devise(AUFMetadata
):
1176 """Devise monétaire.
1179 objects
= DeviseManager()
1181 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1182 code
= models
.CharField(max_length
=10, unique
=True)
1183 nom
= models
.CharField(max_length
=255)
1187 verbose_name
= u
"Devise"
1188 verbose_name_plural
= u
"Devises"
1190 def __unicode__(self
):
1191 return u
'%s - %s' % (self
.code
, self
.nom
)
1193 class TypeContrat(AUFMetadata
):
1196 nom
= models
.CharField(max_length
=255)
1197 nom_long
= models
.CharField(max_length
=255)
1201 verbose_name
= u
"Type de contrat"
1202 verbose_name_plural
= u
"Types de contrat"
1204 def __unicode__(self
):
1205 return u
'%s' % (self
.nom
)
1210 class ResponsableImplantation(AUFMetadata
):
1211 """Le responsable d'une implantation.
1212 Anciennement géré sur le Dossier du responsable.
1214 employe
= models
.ForeignKey('Employe', db_column
='employe',
1216 null
=True, blank
=True)
1217 implantation
= models
.ForeignKey(ref
.Implantation
,
1218 db_column
='implantation', related_name
='+',
1221 def __unicode__(self
):
1222 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1225 ordering
= ['implantation__nom']
1226 verbose_name
= "Responsable d'implantation"
1227 verbose_name_plural
= "Responsables d'implantation"