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
20 # Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
21 # Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
24 models_stack
= [s
[1].split('/')[-2] for s
in inspect
.stack() if s
[1].endswith('models.py')]
25 return models_stack
[-1]
29 HELP_TEXT_DATE
= "format: jj-mm-aaaa"
30 REGIME_TRAVAIL_DEFAULT
= Decimal('100.00')
31 REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
= Decimal('35.00')
32 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"
35 storage_prive
= FileSystemStorage(settings
.PRIVE_MEDIA_ROOT
,
36 base_url
=settings
.PRIVE_MEDIA_URL
)
38 def poste_piece_dispatch(instance
, filename
):
39 path
= "%s/poste/%s/%s" % (instance
._meta
.app_label
, instance
.poste_id
, filename
)
42 def dossier_piece_dispatch(instance
, filename
):
43 path
= "%s/dossier/%s/%s" % (instance
._meta
.app_label
, instance
.dossier_id
, filename
)
46 def employe_piece_dispatch(instance
, filename
):
47 path
= "%s/employe/%s/%s" % (instance
._meta
.app_label
, instance
.employe_id
, filename
)
50 def contrat_dispatch(instance
, filename
):
51 path
= "%s/contrat/%s/%s" % (instance
._meta
.app_label
, instance
.dossier_id
, filename
)
55 class DevisableMixin(object):
57 def get_annee_pour_taux_devise(self
):
58 raise NotImplementedError
60 def taux_devise(self
):
61 if self
.devise
is None:
63 if self
.devise
.code
== "EUR":
66 annee
= self
.get_annee_pour_taux_devise()
67 taux
= [tc
.taux
for tc
in TauxChange
.objects
.filter(devise
=self
.devise
, annee
=annee
)]
71 raise Exception(u
"Pas de taux pour %s en %s" % (self
.devise
.code
, annee
))
74 raise Exception(u
"Il existe plusieurs taux de %s en %s" %
75 (self
.devise
.code
, annee
))
79 def montant_euros(self
):
81 taux
= self
.taux_devise()
86 return int(round(float(self
.montant
) * float(taux
), 2))
89 class Commentaire(AUFMetadata
):
90 texte
= models
.TextField()
91 owner
= models
.ForeignKey('auth.User', db_column
='owner', related_name
='+', verbose_name
=u
"Commentaire de")
95 ordering
= ['-date_creation']
97 def __unicode__(self
):
98 return u
'%s' % (self
.texte
)
103 POSTE_APPEL_CHOICES
= (
104 ('interne', 'Interne'),
105 ('externe', 'Externe'),
108 class Poste_(AUFMetadata
):
109 """Un Poste est un emploi (job) à combler dans une implantation.
110 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
111 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
114 objects
= PosteManager()
117 nom
= models
.CharField(max_length
=255,
118 verbose_name
= u
"Titre du poste", )
119 nom_feminin
= models
.CharField(max_length
=255,
120 verbose_name
= u
"Titre du poste (au féminin)",
122 implantation
= models
.ForeignKey(ref
.Implantation
, help_text
=u
"Taper le nom de l'implantation ou sa région",
123 db_column
='implantation', related_name
='+')
124 type_poste
= models
.ForeignKey('TypePoste', db_column
='type_poste', help_text
=u
"Taper le nom du type de poste",
127 verbose_name
=u
"type de poste")
128 service
= models
.ForeignKey('Service', db_column
='service',
130 verbose_name
= u
"direction/service/pôle support",
132 responsable
= models
.ForeignKey('Poste', db_column
='responsable',
135 help_text
=u
"Taper le nom du poste ou du type de poste",
136 verbose_name
= u
"Poste du responsable", )
139 regime_travail
= models
.DecimalField(max_digits
=12, decimal_places
=2,
140 default
=REGIME_TRAVAIL_DEFAULT
, null
=True,
141 verbose_name
= u
"Temps de travail",
142 help_text
="% du temps complet")
143 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
144 decimal_places
=2, null
=True,
145 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
146 verbose_name
= u
"Nb. heures par semaine",
147 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
)
150 local
= models
.NullBooleanField(verbose_name
= u
"Local", default
=True,
151 null
=True, blank
=True)
152 expatrie
= models
.NullBooleanField(verbose_name
= u
"Expatrié", default
=False,
153 null
=True, blank
=True)
154 mise_a_disposition
= models
.NullBooleanField(
155 verbose_name
= u
"Mise à disposition",
156 null
=True, default
=False)
157 appel
= models
.CharField(max_length
=10, null
=True,
158 verbose_name
= u
"Appel à candidature",
159 choices
=POSTE_APPEL_CHOICES
,
163 classement_min
= models
.ForeignKey('Classement',
164 db_column
='classement_min', related_name
='+',
165 null
=True, blank
=True)
166 classement_max
= models
.ForeignKey('Classement',
167 db_column
='classement_max', related_name
='+',
168 null
=True, blank
=True)
169 valeur_point_min
= models
.ForeignKey('ValeurPoint', help_text
=u
"Taper le code ou le nom de l'implantation",
170 db_column
='valeur_point_min', related_name
='+',
171 null
=True, blank
=True)
172 valeur_point_max
= models
.ForeignKey('ValeurPoint', help_text
=u
"Taper le code ou le nom de l'implantation",
173 db_column
='valeur_point_max', related_name
='+',
174 null
=True, blank
=True)
175 devise_min
= models
.ForeignKey('Devise', db_column
='devise_min', null
=True,
177 devise_max
= models
.ForeignKey('Devise', db_column
='devise_max', null
=True,
179 salaire_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
180 null
=True, default
=0)
181 salaire_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
182 null
=True, default
=0)
183 indemn_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
184 null
=True, default
=0)
185 indemn_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
186 null
=True, default
=0)
187 autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
188 null
=True, default
=0)
189 autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
190 null
=True, default
=0)
192 # Comparatifs de rémunération
193 devise_comparaison
= models
.ForeignKey('Devise', null
=True, blank
=True,
194 db_column
='devise_comparaison',
196 comp_locale_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
197 null
=True, blank
=True)
198 comp_locale_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
199 null
=True, blank
=True)
200 comp_universite_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
201 null
=True, blank
=True)
202 comp_universite_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
203 null
=True, blank
=True)
204 comp_fonctionpub_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
205 null
=True, blank
=True)
206 comp_fonctionpub_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
207 null
=True, blank
=True)
208 comp_ong_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
209 null
=True, blank
=True)
210 comp_ong_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
211 null
=True, blank
=True)
212 comp_autre_min
= models
.DecimalField(max_digits
=12, decimal_places
=2,
213 null
=True, blank
=True)
214 comp_autre_max
= models
.DecimalField(max_digits
=12, decimal_places
=2,
215 null
=True, blank
=True)
218 justification
= models
.TextField(null
=True, blank
=True)
221 date_debut
= models
.DateField(verbose_name
=u
"Date de début", help_text
=HELP_TEXT_DATE
,
222 null
=True, blank
=True)
223 date_fin
= models
.DateField(verbose_name
=u
"Date de fin", help_text
=HELP_TEXT_DATE
,
224 null
=True, blank
=True)
228 ordering
= ['implantation__nom', 'nom']
229 verbose_name
= u
"Poste"
230 verbose_name_plural
= u
"Postes"
233 def __unicode__(self
):
234 representation
= u
'%s - %s [%s]' % (self
.implantation
, self
.nom
,
236 return representation
239 prefix_implantation
= "implantation__region"
240 def get_regions(self
):
241 return [self
.implantation
.region
]
245 __doc__
= Poste_
.__doc__
247 # meta dématérialisation : pour permettre le filtrage
248 vacant
= models
.NullBooleanField(verbose_name
= u
"vacant", null
=True, blank
=True)
252 if self
.occupe_par():
256 def occupe_par(self
):
257 """Retourne la liste d'employé occupant ce poste.
258 Généralement, retourne une liste d'un élément.
259 Si poste inoccupé, retourne liste vide.
260 UTILISE pour mettre a jour le flag vacant
262 return [d
.employe
for d
in self
.rh_dossiers
.filter(supprime
=False).exclude(date_fin__lt
=date
.today())]
265 POSTE_FINANCEMENT_CHOICES
= (
266 ('A', 'A - Frais de personnel'),
267 ('B', 'B - Projet(s)-Titre(s)'),
272 class PosteFinancement_(models
.Model
):
273 """Pour un Poste, structure d'informations décrivant comment on prévoit
276 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_financements')
277 type = models
.CharField(max_length
=1, choices
=POSTE_FINANCEMENT_CHOICES
)
278 pourcentage
= models
.DecimalField(max_digits
=12, decimal_places
=2,
279 help_text
="ex.: 33.33 % (décimale avec point)")
280 commentaire
= models
.TextField(
281 help_text
="Spécifiez la source de financement.")
287 def __unicode__(self
):
288 return u
'%s : %s %%' % (self
.type, self
.pourcentage
)
291 return u
"%s" % dict(POSTE_FINANCEMENT_CHOICES
)[self
.type]
294 class PosteFinancement(PosteFinancement_
):
298 class PostePiece_(models
.Model
):
299 """Documents relatifs au Poste.
300 Ex.: Description de poste
302 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='%(app_label)s_pieces')
303 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
304 fichier
= models
.FileField(verbose_name
= u
"Fichier",
305 upload_to
=poste_piece_dispatch
,
306 storage
=storage_prive
)
312 def __unicode__(self
):
313 return u
'%s' % (self
.nom
)
315 class PostePiece(PostePiece_
):
318 class PosteComparaison_(AUFMetadata
, DevisableMixin
):
320 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
322 poste
= models
.ForeignKey('%s.Poste' % app_context(), related_name
='%(app_label)s_comparaisons_internes')
323 objects
= PosteComparaisonManager()
325 implantation
= models
.ForeignKey(ref
.Implantation
, null
=True, blank
=True, related_name
="+")
326 nom
= models
.CharField(verbose_name
= u
"Poste", max_length
=255, null
=True, blank
=True)
327 montant
= models
.IntegerField(null
=True)
328 devise
= models
.ForeignKey("Devise", related_name
='+', null
=True, blank
=True)
334 def get_annee_pour_taux_devise(self
):
335 return self
.poste
.date_debut
.year
337 def __unicode__(self
):
340 class PosteComparaison(PosteComparaison_
):
343 class PosteCommentaire_(Commentaire
):
344 poste
= models
.ForeignKey('%s.Poste' % app_context(), db_column
='poste', related_name
='+')
349 class PosteCommentaire(PosteCommentaire_
):
354 class Employe(AUFMetadata
):
355 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
356 Dossiers qu'il occupe ou a occupé de Postes.
358 Cette classe aurait pu avantageusement s'appeler Personne car la notion
359 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
362 nom
= models
.CharField(max_length
=255)
363 prenom
= models
.CharField(max_length
=255, verbose_name
= u
"Prénom")
364 nom_affichage
= models
.CharField(max_length
=255,
365 verbose_name
= u
"Nom d'affichage",
366 null
=True, blank
=True)
367 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
368 db_column
='nationalite',
369 related_name
='employes_nationalite',
370 verbose_name
= u
"Nationalité",
371 blank
=True, null
=True)
372 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
373 help_text
=HELP_TEXT_DATE
,
374 validators
=[validate_date_passee
],
375 null
=True, blank
=True)
376 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
379 situation_famille
= models
.CharField(max_length
=1,
380 choices
=SITUATION_CHOICES
,
381 verbose_name
= u
"Situation familiale",
382 null
=True, blank
=True)
383 date_entree
= models
.DateField(verbose_name
= u
"Date d'entrée à l'AUF",
384 help_text
=HELP_TEXT_DATE
,
385 null
=True, blank
=True)
388 tel_domicile
= models
.CharField(max_length
=255,
389 verbose_name
= u
"Tél. domicile",
390 null
=True, blank
=True)
391 tel_cellulaire
= models
.CharField(max_length
=255,
392 verbose_name
= u
"Tél. cellulaire",
393 null
=True, blank
=True)
394 adresse
= models
.CharField(max_length
=255, null
=True, blank
=True)
395 ville
= models
.CharField(max_length
=255, null
=True, blank
=True)
396 province
= models
.CharField(max_length
=255, null
=True, blank
=True)
397 code_postal
= models
.CharField(max_length
=255, null
=True, blank
=True)
398 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code', db_column
='pays',
399 related_name
='employes',
400 null
=True, blank
=True)
402 # meta dématérialisation : pour permettre le filtrage
403 nb_postes
= models
.IntegerField(verbose_name
= u
"nombre de postes", null
=True, blank
=True)
406 ordering
= ['nom','prenom']
407 verbose_name
= u
"Employé"
408 verbose_name_plural
= u
"Employés"
410 def __unicode__(self
):
411 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
415 if self
.genre
.upper() == u
'M':
417 elif self
.genre
.upper() == u
'F':
422 """Retourne l'URL du service retournant la photo de l'Employe.
423 Équivalent reverse url 'rh_photo' avec id en param.
425 from django
.core
.urlresolvers
import reverse
426 return reverse('rh_photo', kwargs
={'id':self
.id})
428 def dossiers_passes(self
):
430 dossiers_passes
= self
.dossiers
.filter(date_fin__lt
=today
).order_by('-date_fin')
431 for d
in dossiers_passes
:
433 return dossiers_passes
435 def dossiers_futurs(self
):
437 return self
.dossiers
.filter(date_debut__gt
=today
).order_by('-date_fin')
439 def dossiers_encours(self
):
440 dossiers_p_f
= self
.dossiers_passes() | self
.dossiers_futurs()
441 ids_dossiers_p_f
= [d
.id for d
in dossiers_p_f
]
442 dossiers_encours
= self
.dossiers
.exclude(id__in
=ids_dossiers_p_f
).order_by('-date_fin')
444 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
445 for d
in dossiers_encours
:
446 d
.remunerations
= Remuneration
.objects
.filter(dossier
=d
.id).order_by('-id')
447 return dossiers_encours
449 def postes_encours(self
):
450 postes_encours
= set()
451 for d
in self
.dossiers_encours():
452 postes_encours
.add(d
.poste
)
453 return postes_encours
455 def poste_principal(self
):
457 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
459 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
461 poste
= Poste
.objects
.none()
463 poste
= self
.dossiers_encours().order_by('date_debut')[0].poste
468 prefix_implantation
= "rh_dossiers__poste__implantation__region"
469 def get_regions(self
):
471 for d
in self
.dossiers
.all():
472 regions
.append(d
.poste
.implantation
.region
)
476 class EmployePiece(models
.Model
):
477 """Documents relatifs à un employé.
480 employe
= models
.ForeignKey('Employe', db_column
='employe')
481 nom
= models
.CharField(verbose_name
="Nom", max_length
=255)
482 fichier
= models
.FileField(verbose_name
="Fichier",
483 upload_to
=employe_piece_dispatch
,
484 storage
=storage_prive
)
488 verbose_name
= u
"Employé pièce"
489 verbose_name_plural
= u
"Employé pièces"
491 def __unicode__(self
):
492 return u
'%s' % (self
.nom
)
494 class EmployeCommentaire(Commentaire
):
495 employe
= models
.ForeignKey('Employe', db_column
='employe',
499 verbose_name
= u
"Employé commentaire"
500 verbose_name_plural
= u
"Employé commentaires"
503 LIEN_PARENTE_CHOICES
= (
504 ('Conjoint', 'Conjoint'),
505 ('Conjointe', 'Conjointe'),
510 class AyantDroit(AUFMetadata
):
511 """Personne en relation avec un Employe.
514 nom
= models
.CharField(max_length
=255)
515 prenom
= models
.CharField(max_length
=255,
516 verbose_name
= u
"Prénom",)
517 nom_affichage
= models
.CharField(max_length
=255,
518 verbose_name
= u
"Nom d'affichage",
519 null
=True, blank
=True)
520 nationalite
= models
.ForeignKey(ref
.Pays
, to_field
='code',
521 db_column
='nationalite',
522 related_name
='ayantdroits_nationalite',
523 verbose_name
= u
"Nationalité",
524 null
=True, blank
=True)
525 date_naissance
= models
.DateField(verbose_name
= u
"Date de naissance",
526 help_text
=HELP_TEXT_DATE
,
527 validators
=[validate_date_passee
],
528 null
=True, blank
=True)
529 genre
= models
.CharField(max_length
=1, choices
=GENRE_CHOICES
)
532 employe
= models
.ForeignKey('Employe', db_column
='employe',
533 related_name
='ayantdroits',
534 verbose_name
= u
"Employé")
535 lien_parente
= models
.CharField(max_length
=10,
536 choices
=LIEN_PARENTE_CHOICES
,
537 verbose_name
= u
"Lien de parenté",
538 null
=True, blank
=True)
542 verbose_name
= u
"Ayant droit"
543 verbose_name_plural
= u
"Ayants droit"
545 def __unicode__(self
):
546 return u
'%s %s [%s]' % (self
.nom
.upper(), self
.prenom
, self
.id)
548 prefix_implantation
= "employe__dossiers__poste__implantation__region"
549 def get_regions(self
):
551 for d
in self
.employe
.dossiers
.all():
552 regions
.append(d
.poste
.implantation
.region
)
556 class AyantDroitCommentaire(Commentaire
):
557 ayant_droit
= models
.ForeignKey('AyantDroit', db_column
='ayant_droit',
563 STATUT_RESIDENCE_CHOICES
= (
565 ('expat', 'Expatrié'),
568 COMPTE_COMPTA_CHOICES
= (
574 class Dossier_(AUFMetadata
):
575 """Le Dossier regroupe les informations relatives à l'occupation
576 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
579 Plusieurs Contrats peuvent être associés au Dossier.
580 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
581 lequel aucun Dossier n'existe est un poste vacant.
584 objects
= DossierManager()
587 statut
= models
.ForeignKey('Statut', related_name
='+', null
=True)
588 organisme_bstg
= models
.ForeignKey('OrganismeBstg',
589 db_column
='organisme_bstg',
591 verbose_name
= u
"Organisme",
592 help_text
="Si détaché (DET) ou \
593 mis à disposition (MAD), \
594 préciser l'organisme.",
595 null
=True, blank
=True)
598 remplacement
= models
.BooleanField(default
=False)
599 remplacement_de
= models
.ForeignKey('self', related_name
='+',
600 help_text
=u
"Taper le nom de l'employé",
601 null
=True, blank
=True)
602 statut_residence
= models
.CharField(max_length
=10, default
='local',
603 verbose_name
= u
"Statut", null
=True,
604 choices
=STATUT_RESIDENCE_CHOICES
)
607 classement
= models
.ForeignKey('Classement', db_column
='classement',
609 null
=True, blank
=True)
610 regime_travail
= models
.DecimalField(max_digits
=12, null
=True,
612 default
=REGIME_TRAVAIL_DEFAULT
,
613 verbose_name
= u
"Régime de travail",
614 help_text
="% du temps complet")
615 regime_travail_nb_heure_semaine
= models
.DecimalField(max_digits
=12,
616 decimal_places
=2, null
=True,
617 default
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT
,
618 verbose_name
=u
"Nb. heures par semaine",
619 help_text
=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT
)
621 # Occupation du Poste par cet Employe (anciennement "mandat")
622 date_debut
= models
.DateField(verbose_name
= u
"Date de début d'occupation \
624 date_fin
= models
.DateField(verbose_name
= u
"Date de fin d'occupation \
626 null
=True, blank
=True)
633 ordering
= ['employe__nom', ]
634 verbose_name
= u
"Dossier"
635 verbose_name_plural
= "Dossiers"
637 def salaire_theorique(self
):
638 annee
= date
.today().year
639 coeff
= self
.classement
.coefficient
640 implantation
= self
.poste
.implantation
641 point
= ValeurPoint
.objects
.get(implantation
=implantation
, annee
=annee
)
643 montant
= coeff
* point
.valeur
644 devise
= point
.devise
645 return {'montant':montant
, 'devise':devise
}
647 def __unicode__(self
):
648 poste
= self
.poste
.nom
649 if self
.employe
.genre
== 'F':
650 poste
= self
.poste
.nom_feminin
651 return u
'%s - %s' % (self
.employe
, poste
)
653 prefix_implantation
= "poste__implantation__region"
654 def get_regions(self
):
655 return [self
.poste
.implantation
.region
]
658 def remunerations(self
):
659 return self
.rh_remunerations
.all().order_by('date_debut')
661 def remunerations_en_cours(self
):
662 return self
.rh_remunerations
.all().filter(date_fin__exact
=None).order_by('date_debut')
664 def get_salaire(self
):
666 return [r
for r
in self
.remunerations().order_by('-date_debut') if r
.type_id
== 1][0]
671 class Dossier(Dossier_
):
672 __doc__
= Dossier_
.__doc__
673 poste
= models
.ForeignKey('%s.Poste' % app_context(),
675 related_name
='%(app_label)s_dossiers',
676 help_text
=u
"Taper le nom du poste ou du type de poste",
678 employe
= models
.ForeignKey('Employe', db_column
='employe',
679 help_text
=u
"Taper le nom de l'employé",
680 related_name
='%(app_label)s_dossiers',
681 verbose_name
=u
"Employé")
684 class DossierPiece_(models
.Model
):
685 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
686 Ex.: Lettre de motivation.
688 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_dossierpieces')
689 nom
= models
.CharField(verbose_name
= u
"Nom", max_length
=255)
690 fichier
= models
.FileField(verbose_name
= u
"Fichier",
691 upload_to
=dossier_piece_dispatch
,
692 storage
=storage_prive
)
698 def __unicode__(self
):
699 return u
'%s' % (self
.nom
)
701 class DossierPiece(DossierPiece_
):
704 class DossierCommentaire_(Commentaire
):
705 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='+')
709 class DossierCommentaire(DossierCommentaire_
):
712 class DossierComparaison_(models
.Model
, DevisableMixin
):
714 Photo d'une comparaison salariale au moment de l'embauche.
716 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), related_name
='%(app_label)s_comparaisons')
717 objects
= DossierComparaisonManager()
719 implantation
= models
.ForeignKey(ref
.Implantation
, related_name
="+", null
=True, blank
=True)
720 poste
= models
.CharField(max_length
=255, null
=True, blank
=True)
721 personne
= models
.CharField(max_length
=255, null
=True, blank
=True)
722 montant
= models
.IntegerField(null
=True)
723 devise
= models
.ForeignKey('Devise', related_name
='+', null
=True, blank
=True)
728 def get_annee_pour_taux_devise(self
):
729 return self
.dossier
.contrat_date_debut
.year
732 class DossierComparaison(DossierComparaison_
):
737 class RemunerationMixin(AUFMetadata
):
738 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_remunerations')
740 type = models
.ForeignKey('TypeRemuneration', db_column
='type',
742 verbose_name
= u
"Type de rémunération")
743 type_revalorisation
= models
.ForeignKey('TypeRevalorisation',
744 db_column
='type_revalorisation',
746 verbose_name
= u
"Type de revalorisation",
747 null
=True, blank
=True)
748 montant
= models
.DecimalField(null
=True, blank
=True,
749 default
=0, max_digits
=12, decimal_places
=2)
750 # Annuel (12 mois, 52 semaines, 364 jours?)
751 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
752 # commentaire = precision
753 commentaire
= models
.CharField(max_length
=255, null
=True, blank
=True)
754 # date_debut = anciennement date_effectif
755 date_debut
= models
.DateField(verbose_name
= u
"Date de début",
756 null
=True, blank
=True)
757 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
758 null
=True, blank
=True)
762 ordering
= ['type__nom', '-date_fin']
764 def __unicode__(self
):
765 return u
'%s %s (%s)' % (self
.montant
, self
.devise
.code
, self
.type.nom
)
767 class Remuneration_(RemunerationMixin
, DevisableMixin
):
768 """Structure de rémunération (données budgétaires) en situation normale
769 pour un Dossier. Si un Evenement existe, utiliser la structure de
770 rémunération EvenementRemuneration de cet événement.
773 def montant_mois(self
):
774 return round(self
.montant
/ 12, 2)
776 def montant_avec_regime(self
):
777 return round(self
.montant
* (self
.dossier
.regime_travail
/100), 2)
779 def get_annee_pour_taux_devise(self
):
780 annee
= datetime
.datetime
.now().year
781 if self
.dossier
.poste
.date_debut
is not None:
782 annee
= self
.dossier
.poste
.date_debut
.year
783 if self
.dossier
.date_debut
is not None:
784 annee
= self
.dossier
.date_debut
.year
785 if self
.date_debut
is not None:
786 annee
= self
.date_debut
.year
789 def montant_euro_mois(self
):
790 return round(self
.montant_euros() / 12, 2)
792 def __unicode__(self
):
794 devise
= self
.devise
.code
797 return "%s %s" % (self
.montant
, devise
)
801 verbose_name
= u
"Rémunération"
802 verbose_name_plural
= u
"Rémunérations"
805 class Remuneration(Remuneration_
):
811 class ContratManager(NoDeleteManager
):
812 def get_query_set(self
):
813 return super(ContratManager
, self
).get_query_set().select_related('dossier', 'dossier__poste')
816 class Contrat_(AUFMetadata
):
817 """Document juridique qui encadre la relation de travail d'un Employe
818 pour un Poste particulier. Pour un Dossier (qui documente cette
819 relation de travail) plusieurs contrats peuvent être associés.
821 objects
= ContratManager()
822 dossier
= models
.ForeignKey('%s.Dossier' % app_context(), db_column
='dossier', related_name
='%(app_label)s_contrats')
823 type_contrat
= models
.ForeignKey('TypeContrat', db_column
='type_contrat',
825 verbose_name
= u
"type de contrat")
826 date_debut
= models
.DateField(verbose_name
= u
"Date de début")
827 date_fin
= models
.DateField(verbose_name
= u
"Date de fin",
828 null
=True, blank
=True)
829 fichier
= models
.FileField(verbose_name
= u
"Fichier",
830 upload_to
=contrat_dispatch
,
831 storage
=storage_prive
,
832 null
=True, blank
=True)
836 ordering
= ['dossier__employe__nom']
837 verbose_name
= u
"Contrat"
838 verbose_name_plural
= u
"Contrats"
840 def __unicode__(self
):
841 return u
'%s - %s' % (self
.dossier
, self
.id)
843 class Contrat(Contrat_
):
849 #class Evenement_(AUFMetadata):
850 # """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
851 # d'un Dossier qui vient altérer des informations normales liées à un Dossier
852 # (ex.: la Remuneration).
854 # Ex.: congé de maternité, maladie...
856 # Lors de ces situations exceptionnelles, l'Employe a un régime de travail
857 # différent et une rémunération en conséquence. On souhaite toutefois
858 # conserver le Dossier intact afin d'éviter une re-saisie des données lors
859 # du retour à la normale.
861 # dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
863 # nom = models.CharField(max_length=255)
864 # date_debut = models.DateField(verbose_name = u"Date de début")
865 # date_fin = models.DateField(verbose_name = u"Date de fin",
866 # null=True, blank=True)
871 # verbose_name = u"Évènement"
872 # verbose_name_plural = u"Évènements"
874 # def __unicode__(self):
875 # return u'%s' % (self.nom)
878 #class Evenement(Evenement_):
879 # __doc__ = Evenement_.__doc__
882 #class EvenementRemuneration_(RemunerationMixin):
883 # """Structure de rémunération liée à un Evenement qui remplace
884 # temporairement la Remuneration normale d'un Dossier, pour toute la durée
887 # evenement = models.ForeignKey("Evenement", db_column='evenement',
889 # verbose_name = u"Évènement")
890 # # TODO : le champ dossier hérité de Remuneration doit être dérivé
891 # # de l'Evenement associé
895 # ordering = ['evenement', 'type__nom', '-date_fin']
896 # verbose_name = u"Évènement - rémunération"
897 # verbose_name_plural = u"Évènements - rémunérations"
900 #class EvenementRemuneration(EvenementRemuneration_):
901 # __doc__ = EvenementRemuneration_.__doc__
907 #class EvenementRemuneration(EvenementRemuneration_):
908 # __doc__ = EvenementRemuneration_.__doc__
909 # TODO? class ContratPiece(models.Model):
914 class FamilleEmploi(AUFMetadata
):
915 """Catégorie utilisée dans la gestion des Postes.
916 Catégorie supérieure à TypePoste.
918 nom
= models
.CharField(max_length
=255)
922 verbose_name
= u
"Famille d'emploi"
923 verbose_name_plural
= u
"Familles d'emploi"
925 def __unicode__(self
):
926 return u
'%s' % (self
.nom
)
928 class TypePoste(AUFMetadata
):
929 """Catégorie de Poste.
931 nom
= models
.CharField(max_length
=255)
932 nom_feminin
= models
.CharField(max_length
=255,
933 verbose_name
= u
"Nom féminin")
935 is_responsable
= models
.BooleanField(default
=False,
936 verbose_name
= u
"Poste de responsabilité")
937 famille_emploi
= models
.ForeignKey('FamilleEmploi',
938 db_column
='famille_emploi',
940 verbose_name
= u
"famille d'emploi")
944 verbose_name
= u
"Type de poste"
945 verbose_name_plural
= u
"Types de poste"
947 def __unicode__(self
):
948 return u
'%s' % (self
.nom
)
951 TYPE_PAIEMENT_CHOICES
= (
952 (u
'Régulier', u
'Régulier'),
953 (u
'Ponctuel', u
'Ponctuel'),
956 NATURE_REMUNERATION_CHOICES
= (
957 (u
'Accessoire', u
'Accessoire'),
958 (u
'Charges', u
'Charges'),
959 (u
'Indemnité', u
'Indemnité'),
960 (u
'RAS', u
'Rémunération autre source'),
961 (u
'Traitement', u
'Traitement'),
964 class TypeRemuneration(AUFMetadata
):
965 """Catégorie de Remuneration.
967 objects
= TypeRemunerationManager()
969 nom
= models
.CharField(max_length
=255)
970 type_paiement
= models
.CharField(max_length
=30,
971 choices
=TYPE_PAIEMENT_CHOICES
,
972 verbose_name
= u
"Type de paiement")
973 nature_remuneration
= models
.CharField(max_length
=30,
974 choices
=NATURE_REMUNERATION_CHOICES
,
975 verbose_name
= u
"Nature de la rémunération")
976 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
980 verbose_name
= u
"Type de rémunération"
981 verbose_name_plural
= u
"Types de rémunération"
983 def __unicode__(self
):
985 archive
= u
"(archivé)"
988 return u
'%s %s' % (self
.nom
, archive
)
990 class TypeRevalorisation(AUFMetadata
):
991 """Justification du changement de la Remuneration.
992 (Actuellement utilisé dans aucun traitement informatique.)
994 nom
= models
.CharField(max_length
=255)
998 verbose_name
= u
"Type de revalorisation"
999 verbose_name_plural
= u
"Types de revalorisation"
1001 def __unicode__(self
):
1002 return u
'%s' % (self
.nom
)
1004 class Service(AUFMetadata
):
1005 """Unité administrative où les Postes sont rattachés.
1007 objects
= ServiceManager()
1009 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1010 nom
= models
.CharField(max_length
=255)
1014 verbose_name
= u
"Service"
1015 verbose_name_plural
= u
"Services"
1017 def __unicode__(self
):
1019 archive
= u
"(archivé)"
1022 return u
'%s %s' % (self
.nom
, archive
)
1025 TYPE_ORGANISME_CHOICES
= (
1026 ('MAD', 'Mise à disposition'),
1027 ('DET', 'Détachement'),
1030 class OrganismeBstg(AUFMetadata
):
1031 """Organisation d'où provient un Employe mis à disposition (MAD) de
1032 ou détaché (DET) à l'AUF à titre gratuit.
1034 (BSTG = bien et service à titre gratuit.)
1036 nom
= models
.CharField(max_length
=255)
1037 type = models
.CharField(max_length
=10, choices
=TYPE_ORGANISME_CHOICES
)
1038 pays
= models
.ForeignKey(ref
.Pays
, to_field
='code',
1040 related_name
='organismes_bstg',
1041 null
=True, blank
=True)
1044 ordering
= ['type', 'nom']
1045 verbose_name
= u
"Organisme BSTG"
1046 verbose_name_plural
= u
"Organismes BSTG"
1048 def __unicode__(self
):
1049 return u
'%s (%s)' % (self
.nom
, self
.get_type_display())
1051 prefix_implantation
= "pays__region"
1052 def get_regions(self
):
1053 return [self
.pays
.region
]
1056 class Statut(AUFMetadata
):
1057 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1060 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.")
1061 nom
= models
.CharField(max_length
=255)
1065 verbose_name
= u
"Statut d'employé"
1066 verbose_name_plural
= u
"Statuts d'employé"
1068 def __unicode__(self
):
1069 return u
'%s : %s' % (self
.code
, self
.nom
)
1072 TYPE_CLASSEMENT_CHOICES
= (
1073 ('S', 'S -Soutien'),
1074 ('T', 'T - Technicien'),
1075 ('P', 'P - Professionel'),
1077 ('D', 'D - Direction'),
1078 ('SO', 'SO - Sans objet [expatriés]'),
1079 ('HG', 'HG - Hors grille [direction]'),
1082 class ClassementManager(models
.Manager
):
1084 Ordonner les spcéfiquement les classements.
1086 def get_query_set(self
):
1087 qs
= super(self
.__class__
, self
).get_query_set()
1088 qs
= qs
.extra(select
={'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'})
1089 qs
= qs
.extra(order_by
=('ponderation', 'echelon', 'degre', ))
1093 class Classement_(AUFMetadata
):
1094 """Éléments de classement de la
1095 "Grille générique de classement hiérarchique".
1097 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
1098 classement dans la grille. Le classement donne le coefficient utilisé dans:
1100 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1102 objects
= ClassementManager()
1105 type = models
.CharField(max_length
=10, choices
=TYPE_CLASSEMENT_CHOICES
)
1106 echelon
= models
.IntegerField(verbose_name
=u
"Échelon", blank
=True, default
=0)
1107 degre
= models
.IntegerField(verbose_name
=u
"Degré", blank
=True, default
=0)
1108 coefficient
= models
.FloatField(default
=0, verbose_name
=u
"Coefficient",
1111 # annee # au lieu de date_debut et date_fin
1112 commentaire
= models
.TextField(null
=True, blank
=True)
1116 ordering
= ['type','echelon','degre','coefficient']
1117 verbose_name
= u
"Classement"
1118 verbose_name_plural
= u
"Classements"
1120 def __unicode__(self
):
1121 return u
'%s.%s.%s' % (self
.type, self
.echelon
, self
.degre
, )
1123 class Classement(Classement_
):
1124 __doc__
= Classement_
.__doc__
1127 class TauxChange_(AUFMetadata
):
1128 """Taux de change de la devise vers l'euro (EUR)
1129 pour chaque année budgétaire.
1132 devise
= models
.ForeignKey('Devise', db_column
='devise')
1133 annee
= models
.IntegerField(verbose_name
= u
"Année")
1134 taux
= models
.FloatField(verbose_name
= u
"Taux vers l'euro")
1138 ordering
= ['-annee', 'devise__code']
1139 verbose_name
= u
"Taux de change"
1140 verbose_name_plural
= u
"Taux de change"
1142 def __unicode__(self
):
1143 return u
'%s : %s € (%s)' % (self
.devise
, self
.taux
, self
.annee
)
1146 class TauxChange(TauxChange_
):
1147 __doc__
= TauxChange_
.__doc__
1149 class ValeurPointManager(NoDeleteManager
):
1151 def get_query_set(self
):
1152 return super(ValeurPointManager
, self
).get_query_set().select_related('devise', 'implantation')
1155 class ValeurPoint_(AUFMetadata
):
1156 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1157 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
1158 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
1160 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1163 actuelles
= ValeurPointManager()
1165 valeur
= models
.FloatField(null
=True)
1166 devise
= models
.ForeignKey('Devise', db_column
='devise', related_name
='+',)
1167 implantation
= models
.ForeignKey(ref
.Implantation
,
1168 db_column
='implantation',
1169 related_name
='%(app_label)s_valeur_point')
1171 annee
= models
.IntegerField()
1174 ordering
= ['-annee', 'implantation__nom']
1176 verbose_name
= u
"Valeur du point"
1177 verbose_name_plural
= u
"Valeurs du point"
1179 def __unicode__(self
):
1180 return u
'%s %s %s [%s] %s' % (self
.devise
.code
, self
.annee
, self
.valeur
, self
.implantation
.nom_court
, self
.devise
.nom
)
1183 class ValeurPoint(ValeurPoint_
):
1184 __doc__
= ValeurPoint_
.__doc__
1188 class Devise(AUFMetadata
):
1189 """Devise monétaire.
1192 objects
= DeviseManager()
1194 archive
= models
.BooleanField(verbose_name
=u
"Archivé", default
=False)
1195 code
= models
.CharField(max_length
=10, unique
=True)
1196 nom
= models
.CharField(max_length
=255)
1200 verbose_name
= u
"Devise"
1201 verbose_name_plural
= u
"Devises"
1203 def __unicode__(self
):
1204 return u
'%s - %s' % (self
.code
, self
.nom
)
1206 class TypeContrat(AUFMetadata
):
1209 nom
= models
.CharField(max_length
=255)
1210 nom_long
= models
.CharField(max_length
=255)
1214 verbose_name
= u
"Type de contrat"
1215 verbose_name_plural
= u
"Types de contrat"
1217 def __unicode__(self
):
1218 return u
'%s' % (self
.nom
)
1223 class ResponsableImplantation(AUFMetadata
):
1224 """Le responsable d'une implantation.
1225 Anciennement géré sur le Dossier du responsable.
1227 employe
= models
.ForeignKey('Employe', db_column
='employe',
1229 null
=True, blank
=True)
1230 implantation
= models
.ForeignKey(ref
.Implantation
,
1231 db_column
='implantation', related_name
='+',
1234 def __unicode__(self
):
1235 return u
'%s : %s' % (self
.implantation
, self
.employe
)
1238 ordering
= ['implantation__nom']
1239 verbose_name
= "Responsable d'implantation"
1240 verbose_name_plural
= "Responsables d'implantation"