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
63 def taux_devise(self
, devise
=None):
69 if devise
.code
== "EUR":
72 annee
= self
.get_annee_pour_taux_devise()
73 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=devise
, annee
=annee
)]
77 raise Exception(u
"Pas de taux pour %s en %s" % (devise
.code
, annee
))
80 raise Exception(u
"Il existe plusieurs taux de %s en %s" %
85 def montant_euros(self
):
87 taux
= self
.taux_devise()
92 return int(round(float(self
.montant
) * float(taux
), 2))
95 class Commentaire(AUFMetadata
):
96 texte
= models
.TextField()
97 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+', verbose_name
=u
"Commentaire de")
101 ordering
= ['-date_creation']
103 def __unicode__(self
):
104 return u
'%s' % (self
.texte
)
109 POSTE_APPEL_CHOICES
= (
110 ('interne', 'Interne'),
111 ('externe', 'Externe'),
114 class Poste_(AUFMetadata
):
115 """Un Poste est un emploi (job) à combler dans une implantation.
116 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
117 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
120 objects
= PosteManager()
123 nom
= models
.CharField(max_length
=255,
124 verbose_name
= u
"Titre du poste", )
125 nom_feminin
= models
.CharField(max_length
=255,
126 verbose_name
= u
"Titre du poste (au féminin)",
128 implantation
= models
.ForeignKey(ref
.Implantation
, help_text
=u
"Taper le nom de l'implantation ou sa région",
129 db_column
='implantation', related_name
='+')
130 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste', help_text
=u
"Taper le nom du type de poste",
133 verbose_name
=u
"type de poste")
134 service
= models
.ForeignKey('Service', db_column
='service',
136 verbose_name
= u
"direction/service/pôle support",
138 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
141 help_text
=u
"Taper le nom du poste ou du type de poste",
142 verbose_name
= u
"Poste du responsable", )
145 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
146 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
147 verbose_name
= u
"Temps de travail",
148 help_text
="% du temps complet")
149 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
150 decimal_places
=2, null
=True,
151 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
152 verbose_name
= u
"Nb. heures par semaine",
153 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
)
156 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
157 null
=True, blank
=True)
158 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
159 null
=True, blank
=True)
160 mise_a_disposition
= models
.NullBooleanField(
161 verbose_name
= u
"Mise à disposition",
162 null
=True, default
=False)
163 appel
= models
.CharField(max_length
=10, null
=True,
164 verbose_name
= u
"Appel à candidature",
165 choices
=POSTE_APPEL_CHOICES
,
169 classement_min
= models
.ForeignKey('Classement',
170 db_column
='classement_min', related_name
='+',
171 null
=True, blank
=True)
172 classement_max
= models
.ForeignKey('Classement',
173 db_column
='classement_max', related_name
='+',
174 null
=True, blank
=True)
175 valeur_point_min
= models
.ForeignKey('ValeurPoint', help_text
=u
"Taper le code ou le nom de l'implantation",
176 db_column
='valeur_point_min', related_name
='+',
177 null
=True, blank
=True)
178 valeur_point_max
= models
.ForeignKey('ValeurPoint', help_text
=u
"Taper le code ou le nom de l'implantation",
179 db_column
='valeur_point_max', related_name
='+',
180 null
=True, blank
=True)
181 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
183 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
185 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
186 null
=True, default
=0)
187 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
188 null
=True, default
=0)
189 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
190 null
=True, default
=0)
191 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
192 null
=True, default
=0)
193 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
194 null
=True, default
=0)
195 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
196 null
=True, default
=0)
198 # Comparatifs de rémunération
199 devise_comparaison
= models
.ForeignKey('Devise', null
=True, blank
=True,
200 db_column
='devise_comparaison',
202 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
203 null
=True, blank
=True)
204 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
205 null
=True, blank
=True)
206 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
207 null
=True, blank
=True)
208 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
209 null
=True, blank
=True)
210 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
211 null
=True, blank
=True)
212 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
213 null
=True, blank
=True)
214 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
215 null
=True, blank
=True)
216 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
217 null
=True, blank
=True)
218 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
219 null
=True, blank
=True)
220 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
221 null
=True, blank
=True)
224 justification
= models
.TextField(null
=True, blank
=True)
227 date_debut
= models
.DateField(verbose_name
=u
"Date de début", help_text
=HELP_TEXT_DATE
,
228 null
=True, blank
=True)
229 date_fin
= models
.DateField(verbose_name
=u
"Date de fin", help_text
=HELP_TEXT_DATE
,
230 null
=True, blank
=True)
234 ordering
= ['implantation__nom', 'nom']
235 verbose_name
= u
"Poste"
236 verbose_name_plural
= u
"Postes"
239 def __unicode__(self
):
240 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
242 return representation
245 prefix_implantation
= "implantation__region"
246 def get_regions(self
):
247 return [self
.implantation
.region
]
251 __doc__
= Poste_
.__doc__
253 # meta dématérialisation : pour permettre le filtrage
254 vacant
= models
.NullBooleanField(verbose_name
= u
"vacant", null
=True, blank
=True)
258 if self
.occupe_par():
262 def occupe_par(self
):
263 """Retourne la liste d'employé occupant ce poste.
264 Généralement, retourne une liste d'un élément.
265 Si poste inoccupé, retourne liste vide.
266 UTILISE pour mettre a jour le flag vacant
268 return [d
.employe
for d
in self
.rh_dossiers
.filter(supprime
=False).exclude(date_fin__lt
=date
.today())]
271 POSTE_FINANCEMENT_CHOICES
= (
272 ('A', 'A - Frais de personnel'),
273 ('B', 'B - Projet(s)-Titre(s)'),
278 class PosteFinancement_(models
.Model
):
279 """Pour un Poste, structure d'informations décrivant comment on prévoit
282 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_financements')
283 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
284 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
285 help_text
="ex.: 33.33 % (décimale avec point)")
286 commentaire
= models
.TextField(
287 help_text
="Spécifiez la source de financement.")
293 def __unicode__(self
):
294 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
297 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
300 class PosteFinancement(PosteFinancement_
):
304 class PostePiece_(models
.Model
):
305 """Documents relatifs au Poste.
306 Ex.: Description de poste
308 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_pieces')
309 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
310 fichier
= models
.FileField(verbose_name
= u
"Fichier",
311 upload_to
=poste_piece_dispatch
,
312 storage
=storage_prive
)
318 def __unicode__(self
):
319 return u
'%s' % (self
.nom
)
321 class PostePiece(PostePiece_
):
324 class PosteComparaison_(AUFMetadata
, DevisableMixin
):
326 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
328 poste
= models
.ForeignKey('%s.Poste' % app_context(), related_name
='%(app_label)s_comparaisons_internes')
329 objects
= PosteComparaisonManager()
331 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
332 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
333 montant
= models
.IntegerField(null
=True)
334 devise
= models
.ForeignKey("Devise", related_name
='+', null
=True, blank
=True)
340 def __unicode__(self
):
343 class PosteComparaison(PosteComparaison_
):
346 class PosteCommentaire_(Commentaire
):
347 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='+')
352 class PosteCommentaire(PosteCommentaire_
):
357 class Employe(AUFMetadata
):
358 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
359 Dossiers qu'il occupe ou a occupé de Postes.
361 Cette classe aurait pu avantageusement s'appeler Personne car la notion
362 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
365 nom
= models
.CharField(max_length
=255)
366 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
367 nom_affichage
= models
.CharField(max_length
=255,
368 verbose_name
= u
"Nom d'affichage",
369 null
=True, blank
=True)
370 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
371 db_column
='nationalite',
372 related_name
='employes_nationalite',
373 verbose_name
= u
"Nationalité",
374 blank
=True, null
=True)
375 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
376 help_text
=HELP_TEXT_DATE
,
377 validators
=[validate_date_passee
],
378 null
=True, blank
=True)
379 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
382 situation_famille
= models
.CharField(max_length
=1,
383 choices
=SITUATION_CHOICES
,
384 verbose_name
= u
"Situation familiale",
385 null
=True, blank
=True)
386 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
387 help_text
=HELP_TEXT_DATE
,
388 null
=True, blank
=True)
391 tel_domicile
= models
.CharField(max_length
=255,
392 verbose_name
= u
"Tél. domicile",
393 null
=True, blank
=True)
394 tel_cellulaire
= models
.CharField(max_length
=255,
395 verbose_name
= u
"Tél. cellulaire",
396 null
=True, blank
=True)
397 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
398 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
399 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
400 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
401 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
402 related_name
='employes',
403 null
=True, blank
=True)
405 # meta dématérialisation : pour permettre le filtrage
406 nb_postes
= models
.IntegerField(verbose_name
= u
"nombre de postes", null
=True, blank
=True)
409 ordering
= ['nom','prenom']
410 verbose_name
= u
"Employé"
411 verbose_name_plural
= u
"Employés"
413 def __unicode__(self
):
414 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
418 if self
.genre
.upper() == u
'M':
420 elif self
.genre
.upper() == u
'F':
425 """Retourne l'URL du service retournant la photo de l'Employe.
426 Équivalent reverse url 'rh_photo' avec id en param.
428 from django
.core
.urlresolvers
import reverse
429 return reverse('rh_photo', kwargs
={'id':self
.id})
431 def dossiers_passes(self
):
432 params
= {KEY_STATUT
: STATUT_INACTIF
, }
433 search
= RechercheTemporelle(params
, self
.__class__
)
434 search
.purge_params(params
)
435 q
= search
.get_q_temporel(self
.rh_dossiers
)
436 return self
.rh_dossiers
.filter(q
)
438 def dossiers_futurs(self
):
439 params
= {KEY_STATUT
: STATUT_FUTUR
, }
440 search
= RechercheTemporelle(params
, self
.__class__
)
441 search
.purge_params(params
)
442 q
= search
.get_q_temporel(self
.rh_dossiers
)
443 return self
.rh_dossiers
.filter(q
)
445 def dossiers_encours(self
):
446 params
= {KEY_STATUT
: STATUT_ACTIF
, }
447 search
= RechercheTemporelle(params
, self
.__class__
)
448 search
.purge_params(params
)
449 q
= search
.get_q_temporel(self
.rh_dossiers
)
450 return self
.rh_dossiers
.filter(q
)
452 def postes_encours(self
):
453 postes_encours
= set()
454 for d
in self
.dossiers_encours():
455 postes_encours
.add(d
.poste
)
456 return postes_encours
458 def poste_principal(self
):
460 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
462 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
464 poste
= Poste
.objects
.none()
466 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
471 prefix_implantation
= "rh_dossiers__poste__implantation__region"
472 def get_regions(self
):
474 for d
in self
.dossiers
.all():
475 regions
.append(d
.poste
.implantation
.region
)
479 class EmployePiece(models
.Model
):
480 """Documents relatifs à un employé.
483 employe
= models
.ForeignKey('Employe', db_column
='employe')
484 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
485 fichier
= models
.FileField(verbose_name
="Fichier",
486 upload_to
=employe_piece_dispatch
,
487 storage
=storage_prive
)
491 verbose_name
= u
"Employé pièce"
492 verbose_name_plural
= u
"Employé pièces"
494 def __unicode__(self
):
495 return u
'%s' % (self
.nom
)
497 class EmployeCommentaire(Commentaire
):
498 employe
= models
.ForeignKey('Employe', db_column
='employe',
502 verbose_name
= u
"Employé commentaire"
503 verbose_name_plural
= u
"Employé commentaires"
506 LIEN_PARENTE_CHOICES
= (
507 ('Conjoint', 'Conjoint'),
508 ('Conjointe', 'Conjointe'),
513 class AyantDroit(AUFMetadata
):
514 """Personne en relation avec un Employe.
517 nom
= models
.CharField(max_length
=255)
518 prenom
= models
.CharField(max_length
=255,
519 verbose_name
= u
"Prénom",)
520 nom_affichage
= models
.CharField(max_length
=255,
521 verbose_name
= u
"Nom d'affichage",
522 null
=True, blank
=True)
523 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
524 db_column
='nationalite',
525 related_name
='ayantdroits_nationalite',
526 verbose_name
= u
"Nationalité",
527 null
=True, blank
=True)
528 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
529 help_text
=HELP_TEXT_DATE
,
530 validators
=[validate_date_passee
],
531 null
=True, blank
=True)
532 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
535 employe
= models
.ForeignKey('Employe', db_column
='employe',
536 related_name
='ayantdroits',
537 verbose_name
= u
"Employé")
538 lien_parente
= models
.CharField(max_length
=10,
539 choices
=LIEN_PARENTE_CHOICES
,
540 verbose_name
= u
"Lien de parenté",
541 null
=True, blank
=True)
545 verbose_name
= u
"Ayant droit"
546 verbose_name_plural
= u
"Ayants droit"
548 def __unicode__(self
):
549 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
551 prefix_implantation
= "employe__dossiers__poste__implantation__region"
552 def get_regions(self
):
554 for d
in self
.employe
.dossiers
.all():
555 regions
.append(d
.poste
.implantation
.region
)
559 class AyantDroitCommentaire(Commentaire
):
560 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
566 STATUT_RESIDENCE_CHOICES
= (
568 ('expat', 'Expatrié'),
571 COMPTE_COMPTA_CHOICES
= (
577 class Dossier_(AUFMetadata
, DevisableMixin
):
578 """Le Dossier regroupe les informations relatives à l'occupation
579 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
582 Plusieurs Contrats peuvent être associés au Dossier.
583 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
584 lequel aucun Dossier n'existe est un poste vacant.
587 objects
= DossierManager()
590 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
591 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
592 db_column
='organisme_bstg',
594 verbose_name
= u
"Organisme",
595 help_text
="Si détaché (DET) ou \
596 mis à disposition (MAD), \
597 préciser l'organisme.",
598 null
=True, blank
=True)
601 remplacement
= models
.BooleanField(default
=False)
602 remplacement_de
= models
.ForeignKey('self', related_name
='+',
603 help_text
=u
"Taper le nom de l'employé",
604 null
=True, blank
=True)
605 statut_residence
= models
.CharField(max_length
=10, default
='local',
606 verbose_name
= u
"Statut", null
=True,
607 choices
=STATUT_RESIDENCE_CHOICES
)
610 classement
= models
.ForeignKey('Classement', db_column
='classement',
612 null
=True, blank
=True)
613 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
615 default
=REGIME_TRAVAIL_DEFAULT
,
616 verbose_name
= u
"Régime de travail",
617 help_text
="% du temps complet")
618 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
619 decimal_places
=2, null
=True,
620 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
621 verbose_name
=u
"Nb. heures par semaine",
622 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
)
624 # Occupation du Poste par cet Employe (anciennement "mandat")
625 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
627 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
629 null
=True, blank
=True)
636 ordering
= ['employe__nom', ]
637 verbose_name
= u
"Dossier"
638 verbose_name_plural
= "Dossiers"
640 def salaire_theorique(self
):
641 annee
= date
.today().year
642 coeff
= self
.classement
.coefficient
643 implantation
= self
.poste
.implantation
644 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
646 montant
= coeff
* point
.valeur
647 devise
= point
.devise
648 return {'montant':montant
, 'devise':devise
}
650 def __unicode__(self
):
651 poste
= self
.poste
.nom
652 if self
.employe
.genre
== 'F':
653 poste
= self
.poste
.nom_feminin
654 return u
'%s - %s' % (self
.employe
, poste
)
656 prefix_implantation
= "poste__implantation__region"
657 def get_regions(self
):
658 return [self
.poste
.implantation
.region
]
661 def remunerations(self
):
662 return self
.rh_remunerations
.all().order_by('date_debut')
664 def remunerations_en_cours(self
):
665 return self
.rh_remunerations
.all().filter(date_fin__exact
=None).order_by('date_debut')
667 def get_salaire(self
):
669 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
674 class Dossier(Dossier_
):
675 __doc__
= Dossier_
.__doc__
676 poste
= models
.ForeignKey('%s.Poste' % app_context(),
678 related_name
='%(app_label)s_dossiers',
679 help_text
=u
"Taper le nom du poste ou du type de poste",
681 employe
= models
.ForeignKey('Employe', db_column
='employe',
682 help_text
=u
"Taper le nom de l'employé",
683 related_name
='%(app_label)s_dossiers',
684 verbose_name
=u
"Employé")
685 principal
= models
.BooleanField(verbose_name
=u
"Principal?", default
=True,
686 help_text
=u
"Ce Dossier est pour le principal Poste occupé par l'Employé")
689 class DossierPiece_(models
.Model
):
690 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
691 Ex.: Lettre de motivation.
693 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_dossierpieces')
694 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
695 fichier
= models
.FileField(verbose_name
= u
"Fichier",
696 upload_to
=dossier_piece_dispatch
,
697 storage
=storage_prive
)
703 def __unicode__(self
):
704 return u
'%s' % (self
.nom
)
706 class DossierPiece(DossierPiece_
):
709 class DossierCommentaire_(Commentaire
):
710 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
714 class DossierCommentaire(DossierCommentaire_
):
717 class DossierComparaison_(models
.Model
, DevisableMixin
):
719 Photo d'une comparaison salariale au moment de l'embauche.
721 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), related_name
='%(app_label)s_comparaisons')
722 objects
= DossierComparaisonManager()
724 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
725 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
726 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
727 montant
= models
.IntegerField(null
=True)
728 devise
= models
.ForeignKey('Devise', related_name
='+', null
=True, blank
=True)
734 class DossierComparaison(DossierComparaison_
):
739 class RemunerationMixin(AUFMetadata
):
740 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_remunerations')
742 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
744 verbose_name
= u
"Type de rémunération")
745 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
746 db_column
='type_revalorisation',
748 verbose_name
= u
"Type de revalorisation",
749 null
=True, blank
=True)
750 montant
= models
.DecimalField(null
=True, blank
=True,
751 default
=0, max_digits
=12, decimal_places
=2)
752 # Annuel (12 mois, 52 semaines, 364 jours?)
753 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
754 # commentaire = precision
755 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
756 # date_debut = anciennement date_effectif
757 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
758 null
=True, blank
=True)
759 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
760 null
=True, blank
=True)
764 ordering
= ['type__nom', '-date_fin']
766 def __unicode__(self
):
767 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
769 class Remuneration_(RemunerationMixin
, DevisableMixin
):
770 """Structure de rémunération (données budgétaires) en situation normale
771 pour un Dossier. Si un Evenement existe, utiliser la structure de
772 rémunération EvenementRemuneration de cet événement.
775 def montant_mois(self
):
776 return round(self
.montant
/ 12, 2)
778 def montant_avec_regime(self
):
779 return round(self
.montant
* (self
.dossier
.regime_travail
/100), 2)
781 def montant_euro_mois(self
):
782 return round(self
.montant_euros() / 12, 2)
784 def __unicode__(self
):
786 devise
= self
.devise
.code
789 return "%s %s" % (self
.montant
, devise
)
793 verbose_name
= u
"Rémunération"
794 verbose_name_plural
= u
"Rémunérations"
797 class Remuneration(Remuneration_
):
803 class ContratManager(NoDeleteManager
):
804 def get_query_set(self
):
805 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
808 class Contrat_(AUFMetadata
):
809 """Document juridique qui encadre la relation de travail d'un Employe
810 pour un Poste particulier. Pour un Dossier (qui documente cette
811 relation de travail) plusieurs contrats peuvent être associés.
813 objects
= ContratManager()
814 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_contrats')
815 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
817 verbose_name
= u
"type de contrat")
818 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
819 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
820 null
=True, blank
=True)
821 fichier
= models
.FileField(verbose_name
= u
"Fichier",
822 upload_to
=contrat_dispatch
,
823 storage
=storage_prive
,
824 null
=True, blank
=True)
828 ordering
= ['dossier__employe__nom']
829 verbose_name
= u
"Contrat"
830 verbose_name_plural
= u
"Contrats"
832 def __unicode__(self
):
833 return u
'%s - %s' % (self
.dossier
, self
.id)
835 class Contrat(Contrat_
):
841 #class Evenement_(AUFMetadata):
842 # """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
843 # d'un Dossier qui vient altérer des informations normales liées à un Dossier
844 # (ex.: la Remuneration).
846 # Ex.: congé de maternité, maladie...
848 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
849 # différent et une rémunération en conséquence. On souhaite toutefois
850 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
851 # du retour à la normale.
853 # dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
855 # nom = models.CharField(max_length=255)
856 # date_debut = models.DateField(verbose_name = u"Date de début")
857 # date_fin = models.DateField(verbose_name = u"Date de fin",
858 # null=True, blank=True)
863 # verbose_name = u"Évènement"
864 # verbose_name_plural = u"Évènements"
866 # def __unicode__(self):
867 # return u'%s' % (self.nom)
870 #class Evenement(Evenement_):
871 # __doc__ = Evenement_.__doc__
874 #class EvenementRemuneration_(RemunerationMixin):
875 # """Structure de rémunération liée à un Evenement qui remplace
876 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
879 # evenement = models.ForeignKey("Evenement", db_column='evenement',
881 # verbose_name = u"Évènement")
882 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
883 # # de l'Evenement associé
887 # ordering = ['evenement', 'type__nom', '-date_fin']
888 # verbose_name = u"Évènement - rémunération"
889 # verbose_name_plural = u"Évènements - rémunérations"
892 #class EvenementRemuneration(EvenementRemuneration_):
893 # __doc__ = EvenementRemuneration_.__doc__
899 #class EvenementRemuneration(EvenementRemuneration_):
900 # __doc__ = EvenementRemuneration_.__doc__
901 # TODO? class ContratPiece(models.Model):
906 class FamilleEmploi(AUFMetadata
):
907 """Catégorie utilisée dans la gestion des Postes.
908 Catégorie supérieure à TypePoste.
910 nom
= models
.CharField(max_length
=255)
914 verbose_name
= u
"Famille d'emploi"
915 verbose_name_plural
= u
"Familles d'emploi"
917 def __unicode__(self
):
918 return u
'%s' % (self
.nom
)
920 class TypePoste(AUFMetadata
):
921 """Catégorie de Poste.
923 nom
= models
.CharField(max_length
=255)
924 nom_feminin
= models
.CharField(max_length
=255,
925 verbose_name
= u
"Nom féminin")
927 is_responsable
= models
.BooleanField(default
=False,
928 verbose_name
= u
"Poste de responsabilité")
929 famille_emploi
= models
.ForeignKey('FamilleEmploi',
930 db_column
='famille_emploi',
932 verbose_name
= u
"famille d'emploi")
936 verbose_name
= u
"Type de poste"
937 verbose_name_plural
= u
"Types de poste"
939 def __unicode__(self
):
940 return u
'%s' % (self
.nom
)
943 TYPE_PAIEMENT_CHOICES
= (
944 (u
'Régulier', u
'Régulier'),
945 (u
'Ponctuel', u
'Ponctuel'),
948 NATURE_REMUNERATION_CHOICES
= (
949 (u
'Accessoire', u
'Accessoire'),
950 (u
'Charges', u
'Charges'),
951 (u
'Indemnité', u
'Indemnité'),
952 (u
'RAS', u
'Rémunération autre source'),
953 (u
'Traitement', u
'Traitement'),
956 class TypeRemuneration(AUFMetadata
):
957 """Catégorie de Remuneration.
959 objects
= TypeRemunerationManager()
961 nom
= models
.CharField(max_length
=255)
962 type_paiement
= models
.CharField(max_length
=30,
963 choices
=TYPE_PAIEMENT_CHOICES
,
964 verbose_name
= u
"Type de paiement")
965 nature_remuneration
= models
.CharField(max_length
=30,
966 choices
=NATURE_REMUNERATION_CHOICES
,
967 verbose_name
= u
"Nature de la rémunération")
968 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
972 verbose_name
= u
"Type de rémunération"
973 verbose_name_plural
= u
"Types de rémunération"
975 def __unicode__(self
):
977 archive
= u
"(archivé)"
980 return u
'%s %s' % (self
.nom
, archive
)
982 class TypeRevalorisation(AUFMetadata
):
983 """Justification du changement de la Remuneration.
984 (Actuellement utilisé dans aucun traitement informatique.)
986 nom
= models
.CharField(max_length
=255)
990 verbose_name
= u
"Type de revalorisation"
991 verbose_name_plural
= u
"Types de revalorisation"
993 def __unicode__(self
):
994 return u
'%s' % (self
.nom
)
996 class Service(AUFMetadata
):
997 """Unité administrative où les Postes sont rattachés.
999 objects
= ServiceManager()
1001 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1002 nom
= models
.CharField(max_length
=255)
1006 verbose_name
= u
"Service"
1007 verbose_name_plural
= u
"Services"
1009 def __unicode__(self
):
1011 archive
= u
"(archivé)"
1014 return u
'%s %s' % (self
.nom
, archive
)
1017 TYPE_ORGANISME_CHOICES
= (
1018 ('MAD', 'Mise à disposition'),
1019 ('DET', 'Détachement'),
1022 class OrganismeBstg(AUFMetadata
):
1023 """Organisation d'où provient un Employe mis à disposition (MAD) de
1024 ou détaché (DET) à l'AUF à titre gratuit.
1026 (BSTG = bien et service à titre gratuit.)
1028 nom
= models
.CharField(max_length
=255)
1029 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1030 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1032 related_name
='organismes_bstg',
1033 null
=True, blank
=True)
1036 ordering
= ['type', 'nom']
1037 verbose_name
= u
"Organisme BSTG"
1038 verbose_name_plural
= u
"Organismes BSTG"
1040 def __unicode__(self
):
1041 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1043 prefix_implantation
= "pays__region"
1044 def get_regions(self
):
1045 return [self
.pays
.region
]
1048 class Statut(AUFMetadata
):
1049 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1052 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.")
1053 nom
= models
.CharField(max_length
=255)
1057 verbose_name
= u
"Statut d'employé"
1058 verbose_name_plural
= u
"Statuts d'employé"
1060 def __unicode__(self
):
1061 return u
'%s : %s' % (self
.code
, self
.nom
)
1064 TYPE_CLASSEMENT_CHOICES
= (
1065 ('S', 'S -Soutien'),
1066 ('T', 'T - Technicien'),
1067 ('P', 'P - Professionel'),
1069 ('D', 'D - Direction'),
1070 ('SO', 'SO - Sans objet [expatriés]'),
1071 ('HG', 'HG - Hors grille [direction]'),
1074 class ClassementManager(models
.Manager
):
1076 Ordonner les spcéfiquement les classements.
1078 def get_query_set(self
):
1079 qs
= super(self
.__class__
, self
).get_query_set()
1080 qs
= qs
.extra(select
={'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'})
1081 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1085 class Classement_(AUFMetadata
):
1086 """Éléments de classement de la
1087 "Grille générique de classement hiérarchique".
1089 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1090 classement dans la grille. Le classement donne le coefficient utilisé dans:
1092 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1094 objects
= ClassementManager()
1097 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1098 echelon
= models
.IntegerField(verbose_name
=u
"Échelon", blank
=True, default
=0)
1099 degre
= models
.IntegerField(verbose_name
=u
"Degré", blank
=True, default
=0)
1100 coefficient
= models
.FloatField(default
=0, verbose_name
=u
"Coefficient",
1103 # annee # au lieu de date_debut et date_fin
1104 commentaire
= models
.TextField(null
=True, blank
=True)
1108 ordering
= ['type','echelon','degre','coefficient']
1109 verbose_name
= u
"Classement"
1110 verbose_name_plural
= u
"Classements"
1112 def __unicode__(self
):
1113 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1115 class Classement(Classement_
):
1116 __doc__
= Classement_
.__doc__
1119 class TauxChange_(AUFMetadata
):
1120 """Taux de change de la devise vers l'euro (EUR)
1121 pour chaque année budgétaire.
1124 devise
= models
.ForeignKey('Devise', db_column
='devise')
1125 annee
= models
.IntegerField(verbose_name
= u
"Année")
1126 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1130 ordering
= ['-annee', 'devise__code']
1131 verbose_name
= u
"Taux de change"
1132 verbose_name_plural
= u
"Taux de change"
1134 def __unicode__(self
):
1135 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1138 class TauxChange(TauxChange_
):
1139 __doc__
= TauxChange_
.__doc__
1141 class ValeurPointManager(NoDeleteManager
):
1143 def get_query_set(self
):
1144 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1147 class ValeurPoint_(AUFMetadata
):
1148 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1149 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1150 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1152 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1155 actuelles
= ValeurPointManager()
1157 valeur
= models
.FloatField(null
=True)
1158 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1159 implantation
= models
.ForeignKey(ref
.Implantation
,
1160 db_column
='implantation',
1161 related_name
='%(app_label)s_valeur_point')
1163 annee
= models
.IntegerField()
1166 ordering
= ['-annee', 'implantation__nom']
1168 verbose_name
= u
"Valeur du point"
1169 verbose_name_plural
= u
"Valeurs du point"
1171 def __unicode__(self
):
1172 return u
'%s %s %s [%s] %s' % (self
.devise
.code
, self
.annee
, self
.valeur
, self
.implantation
.nom_court
, self
.devise
.nom
)
1175 class ValeurPoint(ValeurPoint_
):
1176 __doc__
= ValeurPoint_
.__doc__
1180 class Devise(AUFMetadata
):
1181 """Devise monétaire.
1184 objects
= DeviseManager()
1186 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1187 code
= models
.CharField(max_length
=10, unique
=True)
1188 nom
= models
.CharField(max_length
=255)
1192 verbose_name
= u
"Devise"
1193 verbose_name_plural
= u
"Devises"
1195 def __unicode__(self
):
1196 return u
'%s - %s' % (self
.code
, self
.nom
)
1198 class TypeContrat(AUFMetadata
):
1201 nom
= models
.CharField(max_length
=255)
1202 nom_long
= models
.CharField(max_length
=255)
1206 verbose_name
= u
"Type de contrat"
1207 verbose_name_plural
= u
"Types de contrat"
1209 def __unicode__(self
):
1210 return u
'%s' % (self
.nom
)
1215 class ResponsableImplantation(AUFMetadata
):
1216 """Le responsable d'une implantation.
1217 Anciennement géré sur le Dossier du responsable.
1219 employe
= models
.ForeignKey('Employe', db_column
='employe',
1221 null
=True, blank
=True)
1222 implantation
= models
.ForeignKey(ref
.Implantation
,
1223 db_column
='implantation', related_name
='+',
1226 def __unicode__(self
):
1227 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1230 ordering
= ['implantation__nom']
1231 verbose_name
= "Responsable d'implantation"
1232 verbose_name_plural
= "Responsables d'implantation"