index improvement
[auf_rh_dae.git] / project / rh / models.py
CommitLineData
e9bbd6ba 1# -=- encoding: utf-8 -=-
2
a4125771 3import datetime
c267f20c 4from datetime import date
ca1a7b76 5from decimal import Decimal
c267f20c 6
83b7692b 7from django.core.files.storage import FileSystemStorage
49f9f116 8from django.db import models
838bc59d 9from django.db.models import Q
d6985a3a 10from django.conf import settings
c267f20c 11
94cb1300 12from auf.django.emploi.models import GENRE_CHOICES, SITUATION_CHOICES # devrait plutot être dans references
d6985a3a
OL
13from auf.django.metadata.models import AUFMetadata
14from auf.django.metadata.managers import NoDeleteManager
bf6f2712 15import auf.django.references.models as ref
b4aeadf3 16from validators import validate_date_passee
7ba822a6 17from managers import PosteManager, DossierManager, DossierComparaisonManager, \
6bee05ff
OL
18 PosteComparaisonManager, DeviseManager, ServiceManager, TypeRemunerationManager
19from change_list import RechercheTemporelle, KEY_STATUT, STATUT_ACTIF, \
20 STATUT_INACTIF, STATUT_FUTUR
83b7692b 21
a4125771 22
a4125771
OL
23# Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
24# Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
25def app_context():
26 import inspect;
27 models_stack = [s[1].split('/')[-2] for s in inspect.stack() if s[1].endswith('models.py')]
28 return models_stack[-1]
29
30
2d4d2fcf 31# Constantes
4047b783 32HELP_TEXT_DATE = "format: jj-mm-aaaa"
ca1a7b76
EMS
33REGIME_TRAVAIL_DEFAULT = Decimal('100.00')
34REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = Decimal('35.00')
e2827532 35REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT = "Saisir le nombre d'heure de travail à temps complet (100%), sans tenir compte du régime de travail"
2d4d2fcf 36
83b7692b 37# Upload de fichiers
ca1a7b76 38storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
83b7692b 39 base_url=settings.PRIVE_MEDIA_URL)
40
41def poste_piece_dispatch(instance, filename):
5be60939 42 path = "%s/poste/%s/%s" % (instance._meta.app_label, instance.poste_id, filename)
83b7692b 43 return path
44
45def dossier_piece_dispatch(instance, filename):
5be60939 46 path = "%s/dossier/%s/%s" % (instance._meta.app_label, instance.dossier_id, filename)
83b7692b 47 return path
48
5ea6b5bb 49def employe_piece_dispatch(instance, filename):
5be60939 50 path = "%s/employe/%s/%s" % (instance._meta.app_label, instance.employe_id, filename)
5ea6b5bb 51 return path
52
4ba73558 53def contrat_dispatch(instance, filename):
5be60939 54 path = "%s/contrat/%s/%s" % (instance._meta.app_label, instance.dossier_id, filename)
4ba73558
OL
55 return path
56
5f5a4f06 57
e84c8ef1
OL
58class DevisableMixin(object):
59
60 def get_annee_pour_taux_devise(self):
5a165e95 61 return datetime.datetime.now().year
e84c8ef1 62
03ff41e3
OL
63
64 def taux_devise(self, devise=None):
65 if devise is None:
66 devise = self.devise
67
68 if devise is None:
e84c8ef1 69 return None
03ff41e3 70 if devise.code == "EUR":
e84c8ef1
OL
71 return 1
72
73 annee = self.get_annee_pour_taux_devise()
03ff41e3 74 taux = [tc.taux for tc in TauxChange.objects.filter(devise=devise, annee=annee)]
e84c8ef1
OL
75 taux = set(taux)
76
77 if len(taux) == 0:
03ff41e3 78 raise Exception(u"Pas de taux pour %s en %s" % (devise.code, annee))
e84c8ef1
OL
79
80 if len(taux) > 1:
81 raise Exception(u"Il existe plusieurs taux de %s en %s" %
03ff41e3 82 (devise.code, annee))
e84c8ef1
OL
83 else:
84 return list(taux)[0]
85
86 def montant_euros(self):
87 try:
88 taux = self.taux_devise()
89 except Exception, e:
90 return e
91 if not taux:
92 return None
93 return int(round(float(self.montant) * float(taux), 2))
94
95
d6985a3a 96class Commentaire(AUFMetadata):
2d4d2fcf 97 texte = models.TextField()
098043b6 98 owner = models.ForeignKey('auth.User', db_column='owner', related_name='+', verbose_name=u"Commentaire de")
ca1a7b76 99
2d4d2fcf 100 class Meta:
101 abstract = True
6e4600ef 102 ordering = ['-date_creation']
ca1a7b76 103
6e4600ef 104 def __unicode__(self):
105 return u'%s' % (self.texte)
07b40eda 106
83b7692b 107
108### POSTE
109
110POSTE_APPEL_CHOICES = (
111 ('interne', 'Interne'),
112 ('externe', 'Externe'),
113)
114
d6985a3a 115class Poste_(AUFMetadata):
ca1a7b76 116 """Un Poste est un emploi (job) à combler dans une implantation.
6e4600ef 117 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
118 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
119 """
1f2979b8
OL
120
121 objects = PosteManager()
122
83b7692b 123 # Identification
ca1a7b76 124 nom = models.CharField(max_length=255,
c1195471 125 verbose_name = u"Titre du poste", )
2d4d2fcf 126 nom_feminin = models.CharField(max_length=255,
c1195471 127 verbose_name = u"Titre du poste (au féminin)",
6e4600ef 128 null=True)
105dd778 129 implantation = models.ForeignKey(ref.Implantation, help_text=u"Taper le nom de l'implantation ou sa région",
6e4600ef 130 db_column='implantation', related_name='+')
105dd778 131 type_poste = models.ForeignKey('TypePoste', db_column='type_poste', help_text=u"Taper le nom du type de poste",
6e4600ef 132 related_name='+',
ec1b4c33
JPC
133 null=True,
134 verbose_name=u"type de poste")
ca1a7b76 135 service = models.ForeignKey('Service', db_column='service',
6e4600ef 136 related_name='+',
d48f0922
OL
137 verbose_name = u"direction/service/pôle support",
138 null=True,)
6e4600ef 139 responsable = models.ForeignKey('Poste', db_column='responsable',
d48f0922
OL
140 related_name='+',
141 null=True,
142 help_text=u"Taper le nom du poste ou du type de poste",
3f5cbabe 143 verbose_name = u"Poste du responsable", )
83b7692b 144
145 # Contrat
146 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 147 default=REGIME_TRAVAIL_DEFAULT, null=True,
ca1a7b76 148 verbose_name = u"Temps de travail",
8c1ae2b3 149 help_text="% du temps complet")
83b7692b 150 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
8277a35b 151 decimal_places=2, null=True,
2d4d2fcf 152 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
e2827532
OL
153 verbose_name= u"Nb. heures par semaine",
154 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT)
83b7692b 155
156 # Recrutement
ca1a7b76 157 local = models.NullBooleanField(verbose_name = u"Local", default=True,
8277a35b 158 null=True, blank=True)
ca1a7b76 159 expatrie = models.NullBooleanField(verbose_name = u"Expatrié", default=False,
8277a35b
NC
160 null=True, blank=True)
161 mise_a_disposition = models.NullBooleanField(
c1195471 162 verbose_name = u"Mise à disposition",
8277a35b
NC
163 null=True, default=False)
164 appel = models.CharField(max_length=10, null=True,
c1195471 165 verbose_name = u"Appel à candidature",
6e4600ef 166 choices=POSTE_APPEL_CHOICES,
167 default='interne')
83b7692b 168
169 # Rémunération
ca1a7b76 170 classement_min = models.ForeignKey('Classement',
6e4600ef 171 db_column='classement_min', related_name='+',
172 null=True, blank=True)
ca1a7b76 173 classement_max = models.ForeignKey('Classement',
6e4600ef 174 db_column='classement_max', related_name='+',
175 null=True, blank=True)
105dd778 176 valeur_point_min = models.ForeignKey('ValeurPoint', help_text=u"Taper le code ou le nom de l'implantation",
ca1a7b76 177 db_column='valeur_point_min', related_name='+',
6e4600ef 178 null=True, blank=True)
105dd778 179 valeur_point_max = models.ForeignKey('ValeurPoint', help_text=u"Taper le code ou le nom de l'implantation",
ca1a7b76 180 db_column='valeur_point_max', related_name='+',
6e4600ef 181 null=True, blank=True)
8277a35b 182 devise_min = models.ForeignKey('Devise', db_column='devise_min', null=True,
eb6bf568 183 related_name='+',)
8277a35b 184 devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
eb6bf568 185 related_name='+',)
83b7692b 186 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 187 null=True, default=0)
83b7692b 188 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 189 null=True, default=0)
83b7692b 190 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 191 null=True, default=0)
83b7692b 192 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 193 null=True, default=0)
83b7692b 194 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 195 null=True, default=0)
83b7692b 196 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 197 null=True, default=0)
83b7692b 198
199 # Comparatifs de rémunération
2f7a8932 200 devise_comparaison = models.ForeignKey('Devise', null=True, blank=True,
ca1a7b76 201 db_column='devise_comparaison',
eb6bf568 202 related_name='+', )
83b7692b 203 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 204 null=True, blank=True)
83b7692b 205 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 206 null=True, blank=True)
83b7692b 207 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 208 null=True, blank=True)
83b7692b 209 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 210 null=True, blank=True)
83b7692b 211 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 212 null=True, blank=True)
83b7692b 213 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 214 null=True, blank=True)
83b7692b 215 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 216 null=True, blank=True)
83b7692b 217 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 218 null=True, blank=True)
83b7692b 219 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 220 null=True, blank=True)
83b7692b 221 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 222 null=True, blank=True)
83b7692b 223
224 # Justification
6e4600ef 225 justification = models.TextField(null=True, blank=True)
83b7692b 226
2d4d2fcf 227 # Autres Metadata
a2459daf 228 date_debut = models.DateField(verbose_name=u"Date de début", help_text=HELP_TEXT_DATE,
7332d8f9 229 null=True, blank=True)
a2459daf 230 date_fin = models.DateField(verbose_name=u"Date de fin", help_text=HELP_TEXT_DATE,
6e4600ef 231 null=True, blank=True)
232
233 class Meta:
37868f0b 234 abstract = True
6e4600ef 235 ordering = ['implantation__nom', 'nom']
c1195471
OL
236 verbose_name = u"Poste"
237 verbose_name_plural = u"Postes"
30e3cf7c 238 ordering = ["nom"]
6e4600ef 239
83b7692b 240 def __unicode__(self):
ca1a7b76 241 representation = u'%s - %s [%s]' % (self.implantation, self.nom,
8c1ae2b3 242 self.id)
8c1ae2b3 243 return representation
ca1a7b76 244
22343fe7 245
4c53dda4
OL
246 prefix_implantation = "implantation__region"
247 def get_regions(self):
248 return [self.implantation.region]
249
552d0db7 250 def get_devise(self):
523c8c0f
EMS
251 vp = ValeurPoint.objects.filter(implantation=self.implantation, devise__archive=False).order_by('annee')
252 if len(vp) > 0:
253 return vp[0].devise
254 else:
255 return Devise.objects.get(code='EUR')
4c53dda4
OL
256
257class Poste(Poste_):
258 __doc__ = Poste_.__doc__
259
260 # meta dématérialisation : pour permettre le filtrage
ec1b4c33 261 vacant = models.NullBooleanField(verbose_name = u"vacant", null=True, blank=True)
4c53dda4 262
8c1ae2b3 263 def is_vacant(self):
23102192
DB
264 vacant = True
265 if self.occupe_par():
266 vacant = False
267 return vacant
268
269 def occupe_par(self):
270 """Retourne la liste d'employé occupant ce poste.
271 Généralement, retourne une liste d'un élément.
272 Si poste inoccupé, retourne liste vide.
4c53dda4 273 UTILISE pour mettre a jour le flag vacant
23102192 274 """
f614ca5c 275 return [d.employe for d in self.rh_dossiers.filter(supprime=False).exclude(date_fin__lt=date.today())]
83b7692b 276
37868f0b 277
83b7692b 278POSTE_FINANCEMENT_CHOICES = (
279 ('A', 'A - Frais de personnel'),
280 ('B', 'B - Projet(s)-Titre(s)'),
281 ('C', 'C - Autre')
282)
283
6e7c919b
NC
284
285class PosteFinancement_(models.Model):
6e4600ef 286 """Pour un Poste, structure d'informations décrivant comment on prévoit
287 financer ce Poste.
288 """
a4125771 289 poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='%(app_label)s_financements')
83b7692b 290 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
291 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
8c1ae2b3 292 help_text="ex.: 33.33 % (décimale avec point)")
83b7692b 293 commentaire = models.TextField(
8c1ae2b3 294 help_text="Spécifiez la source de financement.")
83b7692b 295
296 class Meta:
6e7c919b 297 abstract = True
83b7692b 298 ordering = ['type']
ca1a7b76 299
6e4600ef 300 def __unicode__(self):
a184c555 301 return u'%s : %s %%' % (self.type, self.pourcentage)
83b7692b 302
abf91905
JPC
303 def choix(self):
304 return u"%s" % dict(POSTE_FINANCEMENT_CHOICES)[self.type]
305
6e7c919b
NC
306
307class PosteFinancement(PosteFinancement_):
a4125771 308 pass
6e7c919b
NC
309
310
317ce433 311class PostePiece_(models.Model):
6e4600ef 312 """Documents relatifs au Poste.
7abc6d45 313 Ex.: Description de poste
314 """
a4125771 315 poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='%(app_label)s_pieces')
c1195471 316 nom = models.CharField(verbose_name = u"Nom", max_length=255)
ca1a7b76
EMS
317 fichier = models.FileField(verbose_name = u"Fichier",
318 upload_to=poste_piece_dispatch,
83b7692b 319 storage=storage_prive)
320
6e4600ef 321 class Meta:
317ce433 322 abstract = True
6e4600ef 323 ordering = ['nom']
ca1a7b76 324
6e4600ef 325 def __unicode__(self):
326 return u'%s' % (self.nom)
327
317ce433 328class PostePiece(PostePiece_):
a4125771 329 pass
317ce433 330
e84c8ef1 331class PosteComparaison_(AUFMetadata, DevisableMixin):
068d1462
OL
332 """
333 De la même manière qu'un dossier, un poste peut-être comparé à un autre poste.
334 """
a4125771 335 poste = models.ForeignKey('%s.Poste' % app_context(), related_name='%(app_label)s_comparaisons_internes')
16b1454e
OL
336 objects = PosteComparaisonManager()
337
0f0dacbb 338 implantation = models.ForeignKey(ref.Implantation, null=True, blank=True, related_name="+")
c1195471 339 nom = models.CharField(verbose_name = u"Poste", max_length=255, null=True, blank=True)
068d1462 340 montant = models.IntegerField(null=True)
eb6bf568 341 devise = models.ForeignKey("Devise", related_name='+', null=True, blank=True)
1d0f4eef 342
317ce433
OL
343 class Meta:
344 abstract = True
345
1d0f4eef 346
783e077a
JPC
347 def __unicode__(self):
348 return self.nom
349
317ce433 350class PosteComparaison(PosteComparaison_):
c86fc8ef 351 objects = NoDeleteManager()
068d1462 352
317ce433 353class PosteCommentaire_(Commentaire):
a4125771 354 poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='+')
83b7692b 355
317ce433
OL
356 class Meta:
357 abstract = True
358
359class PosteCommentaire(PosteCommentaire_):
360 pass
2d4d2fcf 361
83b7692b 362### EMPLOYÉ/PERSONNE
e9bbd6ba 363
d6985a3a 364class Employe(AUFMetadata):
ca1a7b76 365 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
6e4600ef 366 Dossiers qu'il occupe ou a occupé de Postes.
ca1a7b76
EMS
367
368 Cette classe aurait pu avantageusement s'appeler Personne car la notion
6e4600ef 369 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
370 """
9afaa55e 371 # Identification
e9bbd6ba 372 nom = models.CharField(max_length=255)
c1195471 373 prenom = models.CharField(max_length=255, verbose_name = u"Prénom")
ca1a7b76 374 nom_affichage = models.CharField(max_length=255,
c1195471 375 verbose_name = u"Nom d'affichage",
6e4600ef 376 null=True, blank=True)
ca1a7b76 377 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 378 db_column='nationalite',
8c1ae2b3 379 related_name='employes_nationalite',
ca1a7b76
EMS
380 verbose_name = u"Nationalité",
381 blank=True, null=True)
a25e1d5c 382 date_naissance = models.DateField(verbose_name = u"Date de naissance",
4047b783 383 help_text=HELP_TEXT_DATE,
b4aeadf3 384 validators=[validate_date_passee],
6e4600ef 385 null=True, blank=True)
2d4d2fcf 386 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
ca1a7b76 387
9afaa55e 388 # Infos personnelles
ca1a7b76 389 situation_famille = models.CharField(max_length=1,
6e4600ef 390 choices=SITUATION_CHOICES,
c1195471 391 verbose_name = u"Situation familiale",
6e4600ef 392 null=True, blank=True)
c1195471 393 date_entree = models.DateField(verbose_name = u"Date d'entrée à l'AUF",
3feb97b0 394 help_text=HELP_TEXT_DATE,
7abc6d45 395 null=True, blank=True)
ca1a7b76 396
9afaa55e 397 # Coordonnées
ca1a7b76 398 tel_domicile = models.CharField(max_length=255,
c1195471 399 verbose_name = u"Tél. domicile",
8c1ae2b3 400 null=True, blank=True)
ca1a7b76 401 tel_cellulaire = models.CharField(max_length=255,
c1195471 402 verbose_name = u"Tél. cellulaire",
8c1ae2b3 403 null=True, blank=True)
e9bbd6ba 404 adresse = models.CharField(max_length=255, null=True, blank=True)
e9bbd6ba 405 ville = models.CharField(max_length=255, null=True, blank=True)
406 province = models.CharField(max_length=255, null=True, blank=True)
407 code_postal = models.CharField(max_length=255, null=True, blank=True)
6e4600ef 408 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
ca1a7b76 409 related_name='employes',
6e4600ef 410 null=True, blank=True)
9afaa55e 411
a45e414b 412 # meta dématérialisation : pour permettre le filtrage
ec1b4c33 413 nb_postes = models.IntegerField(verbose_name = u"nombre de postes", null=True, blank=True)
a45e414b 414
6e4600ef 415 class Meta:
9b0d1822 416 ordering = ['nom','prenom']
c1195471
OL
417 verbose_name = u"Employé"
418 verbose_name_plural = u"Employés"
ca1a7b76 419
9afaa55e 420 def __unicode__(self):
a2c3ad52 421 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
ca1a7b76 422
d04d084c 423 def civilite(self):
424 civilite = u''
425 if self.genre.upper() == u'M':
426 civilite = u'M.'
427 elif self.genre.upper() == u'F':
428 civilite = u'Mme'
429 return civilite
ca1a7b76 430
5ea6b5bb 431 def url_photo(self):
432 """Retourne l'URL du service retournant la photo de l'Employe.
433 Équivalent reverse url 'rh_photo' avec id en param.
434 """
435 from django.core.urlresolvers import reverse
436 return reverse('rh_photo', kwargs={'id':self.id})
ca1a7b76 437
c267f20c 438 def dossiers_passes(self):
6bee05ff
OL
439 params = {KEY_STATUT: STATUT_INACTIF, }
440 search = RechercheTemporelle(params, self.__class__)
441 search.purge_params(params)
dcd1b959
OL
442 q = search.get_q_temporel(self.rh_dossiers)
443 return self.rh_dossiers.filter(q)
ca1a7b76 444
c267f20c 445 def dossiers_futurs(self):
6bee05ff
OL
446 params = {KEY_STATUT: STATUT_FUTUR, }
447 search = RechercheTemporelle(params, self.__class__)
448 search.purge_params(params)
dcd1b959
OL
449 q = search.get_q_temporel(self.rh_dossiers)
450 return self.rh_dossiers.filter(q)
ca1a7b76 451
c267f20c 452 def dossiers_encours(self):
6bee05ff
OL
453 params = {KEY_STATUT: STATUT_ACTIF, }
454 search = RechercheTemporelle(params, self.__class__)
455 search.purge_params(params)
dcd1b959
OL
456 q = search.get_q_temporel(self.rh_dossiers)
457 return self.rh_dossiers.filter(q)
ca1a7b76 458
35c0c2fe 459 def postes_encours(self):
460 postes_encours = set()
461 for d in self.dossiers_encours():
462 postes_encours.add(d.poste)
463 return postes_encours
ca1a7b76 464
35c0c2fe 465 def poste_principal(self):
65f9fac8 466 """
467 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
ca1a7b76 468 Idée derrière :
65f9fac8 469 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
470 """
471 poste = Poste.objects.none()
472 try:
473 poste = self.dossiers_encours().order_by('date_debut')[0].poste
474 except:
475 pass
476 return poste
9afaa55e 477
b5cc0357 478 prefix_implantation = "rh_dossiers__poste__implantation__region"
aff1a4c6
PP
479 def get_regions(self):
480 regions = []
481 for d in self.dossiers.all():
482 regions.append(d.poste.implantation.region)
483 return regions
484
485
7abc6d45 486class EmployePiece(models.Model):
6e4600ef 487 """Documents relatifs à un employé.
7abc6d45 488 Ex.: CV...
489 """
f9e54d59 490 employe = models.ForeignKey('Employe', db_column='employe')
8c1ae2b3 491 nom = models.CharField(verbose_name="Nom", max_length=255)
ca1a7b76
EMS
492 fichier = models.FileField(verbose_name="Fichier",
493 upload_to=employe_piece_dispatch,
7abc6d45 494 storage=storage_prive)
495
6e4600ef 496 class Meta:
497 ordering = ['nom']
f9e54d59
PP
498 verbose_name = u"Employé pièce"
499 verbose_name_plural = u"Employé pièces"
500
6e4600ef 501 def __unicode__(self):
502 return u'%s' % (self.nom)
503
07b40eda 504class EmployeCommentaire(Commentaire):
8c1ae2b3 505 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 506 related_name='+')
9afaa55e 507
b343eb3d
PP
508 class Meta:
509 verbose_name = u"Employé commentaire"
510 verbose_name_plural = u"Employé commentaires"
511
2d4d2fcf 512
e9bbd6ba 513LIEN_PARENTE_CHOICES = (
514 ('Conjoint', 'Conjoint'),
515 ('Conjointe', 'Conjointe'),
516 ('Fille', 'Fille'),
517 ('Fils', 'Fils'),
518)
519
d6985a3a 520class AyantDroit(AUFMetadata):
6e4600ef 521 """Personne en relation avec un Employe.
522 """
9afaa55e 523 # Identification
e9bbd6ba 524 nom = models.CharField(max_length=255)
8c1ae2b3 525 prenom = models.CharField(max_length=255,
c1195471 526 verbose_name = u"Prénom",)
ca1a7b76 527 nom_affichage = models.CharField(max_length=255,
c1195471 528 verbose_name = u"Nom d'affichage",
6e4600ef 529 null=True, blank=True)
ca1a7b76 530 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 531 db_column='nationalite',
8c1ae2b3 532 related_name='ayantdroits_nationalite',
ca1a7b76
EMS
533 verbose_name = u"Nationalité",
534 null=True, blank=True)
a25e1d5c 535 date_naissance = models.DateField(verbose_name = u"Date de naissance",
cf022e27 536 help_text=HELP_TEXT_DATE,
25037368 537 validators=[validate_date_passee],
6e4600ef 538 null=True, blank=True)
2d4d2fcf 539 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
ca1a7b76 540
9afaa55e 541 # Relation
ca1a7b76 542 employe = models.ForeignKey('Employe', db_column='employe',
8c1ae2b3 543 related_name='ayantdroits',
c1195471 544 verbose_name = u"Employé")
ca1a7b76 545 lien_parente = models.CharField(max_length=10,
6e4600ef 546 choices=LIEN_PARENTE_CHOICES,
c1195471 547 verbose_name = u"Lien de parenté",
6e4600ef 548 null=True, blank=True)
549
550 class Meta:
a2c3ad52 551 ordering = ['nom', ]
c1195471
OL
552 verbose_name = u"Ayant droit"
553 verbose_name_plural = u"Ayants droit"
ca1a7b76 554
6e4600ef 555 def __unicode__(self):
a2c3ad52 556 return u'%s %s [%s]' % (self.nom.upper(), self.prenom, self.id)
83b7692b 557
aff1a4c6
PP
558 prefix_implantation = "employe__dossiers__poste__implantation__region"
559 def get_regions(self):
560 regions = []
561 for d in self.employe.dossiers.all():
562 regions.append(d.poste.implantation.region)
563 return regions
564
565
07b40eda 566class AyantDroitCommentaire(Commentaire):
8c1ae2b3 567 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
6e4600ef 568 related_name='+')
83b7692b 569
2d4d2fcf 570
83b7692b 571### DOSSIER
572
573STATUT_RESIDENCE_CHOICES = (
574 ('local', 'Local'),
575 ('expat', 'Expatrié'),
576)
577
578COMPTE_COMPTA_CHOICES = (
579 ('coda', 'CODA'),
580 ('scs', 'SCS'),
581 ('aucun', 'Aucun'),
582)
583
b78bbaf3 584class Dossier_(AUFMetadata, DevisableMixin):
6e4600ef 585 """Le Dossier regroupe les informations relatives à l'occupation
586 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
587 par un Employe.
ca1a7b76 588
6e4600ef 589 Plusieurs Contrats peuvent être associés au Dossier.
590 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
591 lequel aucun Dossier n'existe est un poste vacant.
592 """
3f5cbabe
OL
593
594 objects = DossierManager()
595
63e17dff 596 # TODO: OneToOne ??
eb6bf568 597 statut = models.ForeignKey('Statut', related_name='+', null=True)
ca1a7b76 598 organisme_bstg = models.ForeignKey('OrganismeBstg',
6e4600ef 599 db_column='organisme_bstg',
600 related_name='+',
ca1a7b76 601 verbose_name = u"Organisme",
8c1ae2b3 602 help_text="Si détaché (DET) ou \
6e4600ef 603 mis à disposition (MAD), \
604 préciser l'organisme.",
605 null=True, blank=True)
ca1a7b76 606
83b7692b 607 # Recrutement
2d4d2fcf 608 remplacement = models.BooleanField(default=False)
7c182958 609 remplacement_de = models.ForeignKey('self', related_name='+',
0b0545bd 610 help_text=u"Taper le nom de l'employé",
7c182958 611 null=True, blank=True)
ca1a7b76 612 statut_residence = models.CharField(max_length=10, default='local',
c1195471 613 verbose_name = u"Statut", null=True,
2d4d2fcf 614 choices=STATUT_RESIDENCE_CHOICES)
ca1a7b76 615
83b7692b 616 # Rémunération
ca1a7b76 617 classement = models.ForeignKey('Classement', db_column='classement',
6e4600ef 618 related_name='+',
619 null=True, blank=True)
8277a35b 620 regime_travail = models.DecimalField(max_digits=12, null=True,
2d4d2fcf 621 decimal_places=2,
622 default=REGIME_TRAVAIL_DEFAULT,
c1195471 623 verbose_name = u"Régime de travail",
8c1ae2b3 624 help_text="% du temps complet")
83b7692b 625 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
8277a35b 626 decimal_places=2, null=True,
2d4d2fcf 627 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
e2827532
OL
628 verbose_name=u"Nb. heures par semaine",
629 help_text=REGIME_TRAVAIL_NB_HEURE_SEMAINE_HELP_TEXT)
7abc6d45 630
631 # Occupation du Poste par cet Employe (anciennement "mandat")
c1195471 632 date_debut = models.DateField(verbose_name = u"Date de début d'occupation \
a25e1d5c 633 de poste",)
c1195471 634 date_fin = models.DateField(verbose_name = u"Date de fin d'occupation \
6e4600ef 635 de poste",
6e4600ef 636 null=True, blank=True)
ca1a7b76 637
2d4d2fcf 638 # Comptes
639 # TODO?
ca1a7b76 640
6e4600ef 641 class Meta:
37868f0b 642 abstract = True
49449367 643 ordering = ['employe__nom', ]
3f5f3898 644 verbose_name = u"Dossier"
8c1ae2b3 645 verbose_name_plural = "Dossiers"
ca1a7b76 646
65f9fac8 647 def salaire_theorique(self):
648 annee = date.today().year
649 coeff = self.classement.coefficient
650 implantation = self.poste.implantation
651 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
ca1a7b76 652
65f9fac8 653 montant = coeff * point.valeur
654 devise = point.devise
655 return {'montant':montant, 'devise':devise}
ca1a7b76 656
83b7692b 657 def __unicode__(self):
8c1ae2b3 658 poste = self.poste.nom
659 if self.employe.genre == 'F':
ca1a7b76 660 poste = self.poste.nom_feminin
8c1ae2b3 661 return u'%s - %s' % (self.employe, poste)
83b7692b 662
aff1a4c6
PP
663 prefix_implantation = "poste__implantation__region"
664 def get_regions(self):
665 return [self.poste.implantation.region]
666
37868f0b 667
3ebc0952 668 def remunerations(self):
838bc59d
OL
669 key = "%s_remunerations" % self._meta.app_label
670 remunerations = getattr(self, key)
671 return remunerations.all().order_by('-date_debut')
3ebc0952 672
02e69aa2 673 def remunerations_en_cours(self):
838bc59d
OL
674 q = Q(date_fin__exact=None) | Q(date_fin__gt=datetime.date.today())
675 return self.remunerations().all().filter(q).order_by('date_debut')
02e69aa2 676
09aa8374
OL
677 def get_salaire(self):
678 try:
679 return [r for r in self.remunerations().order_by('-date_debut') if r.type_id == 1][0]
680 except:
681 return None
3ebc0952 682
838bc59d
OL
683 def get_salaire_euros(self):
684 tx = self.taux_devise()
685 return (float)(tx) * (float)(self.salaire)
686
687 def get_remunerations_brutes(self):
688 """
689 1 Salaire de base
690 3 Indemnité de base
691 4 Indemnité d'expatriation
692 5 Indemnité pour frais
693 6 Indemnité de logement
694 7 Indemnité de fonction
695 8 Indemnité de responsabilité
696 9 Indemnité de transport
697 10 Indemnité compensatrice
698 11 Indemnité de subsistance
699 12 Indemnité différentielle
700 13 Prime d'installation
701 14 Billet d'avion
702 15 Déménagement
703 16 Indemnité de départ
704 18 Prime de 13ième mois
705 19 Prime d'intérim
706 """
707 ids = [1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19]
708 return [r for r in self.remunerations_en_cours().all() if r.type_id in ids]
709
710 def get_charges_salariales(self):
711 """
712 20 Charges salariales ?
713 """
714 ids = [20, ]
715 return [r for r in self.remunerations_en_cours().all() if r.type_id in ids]
716
838bc59d
OL
717 def get_charges_patronales(self):
718 """
719 17 Charges patronales
720 """
721 ids = [17, ]
722 return [r for r in self.remunerations_en_cours().all() if r.type_id in ids]
723
552d0db7
OL
724 def get_remunerations_tierces(self):
725 """
726 2 Salaire MAD
727 """
728 return [r for r in self.remunerations_en_cours().all() if r.type_id in (2, )]
729
730 # DEVISE LOCALE
731
732 def get_total_local_charges_salariales(self):
bc17b82c 733 devise = self.poste.get_devise()
552d0db7
OL
734 total = 0.0
735 for r in self.get_charges_salariales():
bc17b82c
OL
736 if r.devise != devise:
737 return None
738 total += float(r.montant)
552d0db7
OL
739 return total
740
741 def get_total_local_charges_patronales(self):
bc17b82c 742 devise = self.poste.get_devise()
552d0db7
OL
743 total = 0.0
744 for r in self.get_charges_patronales():
bc17b82c
OL
745 if r.devise != devise:
746 return None
552d0db7
OL
747 total += float(r.montant)
748 return total
749
750 def get_local_salaire_brut(self):
751 """
752 somme des rémuérations brutes
753 """
754 devise = self.poste.get_devise()
755 total = 0.0
756 for r in self.get_remunerations_brutes():
757 if r.devise != devise:
758 return None
759 total += float(r.montant)
760 return total
761
762 def get_local_salaire_net(self):
763 """
764 salaire brut - charges salariales
765 """
766 devise = self.poste.get_devise()
767 total_charges = 0.0
768 for r in self.get_charges_salariales():
769 if r.devise != devise:
770 return None
771 total_charges += float(r.montant)
772 return self.get_local_salaire_brut() - total_charges
773
774 def get_local_couts_auf(self):
775 """
776 salaire net + charges patronales
777 """
778 devise = self.poste.get_devise()
779 total_charges = 0.0
780 for r in self.get_charges_patronales():
781 if r.devise != devise:
782 return None
783 total_charges += float(r.montant)
784 return self.get_local_salaire_net() + total_charges
785
786 def get_total_local_remunerations_tierces(self):
787 devise = self.poste.get_devise()
788 total = 0.0
789 for r in self.get_remunerations_tierces():
790 if r.devise != devise:
791 return None
792 total += float(r.montant)
793 return total
794
795 # DEVISE EURO
796
797 def get_total_charges_salariales(self):
798 total = 0.0
799 for r in self.get_charges_salariales():
800 total += r.montant_euros()
801 return total
802
838bc59d
OL
803 def get_total_charges_patronales(self):
804 total = 0.0
805 for r in self.get_charges_patronales():
806 total += r.montant_euros()
807 return total
808
809 def get_salaire_brut(self):
810 """
811 somme des rémuérations brutes
812 """
813 total = 0.0
814 for r in self.get_remunerations_brutes():
815 total += r.montant_euros()
816 return total
817
818 def get_salaire_net(self):
819 """
820 salaire brut - charges salariales
821 """
822 total_charges = 0.0
823 for r in self.get_charges_salariales():
824 total_charges += r.montant_euros()
825 return self.get_salaire_brut() - total_charges
826
827 def get_couts_auf(self):
828 """
829 salaire net + charges patronales
830 """
831 total_charges = 0.0
832 for r in self.get_charges_patronales():
833 total_charges += r.montant_euros()
834 return self.get_salaire_net() + total_charges
835
838bc59d
OL
836 def get_total_remunerations_tierces(self):
837 total = 0.0
838 for r in self.get_remunerations_tierces():
839 total += r.montant_euros()
840 return total
841
22343fe7 842
37868f0b
NC
843class Dossier(Dossier_):
844 __doc__ = Dossier_.__doc__
0b0545bd
OL
845 poste = models.ForeignKey('%s.Poste' % app_context(),
846 db_column='poste',
847 related_name='%(app_label)s_dossiers',
848 help_text=u"Taper le nom du poste ou du type de poste",
849 )
850 employe = models.ForeignKey('Employe', db_column='employe',
851 help_text=u"Taper le nom de l'employé",
16b1454e
OL
852 related_name='%(app_label)s_dossiers',
853 verbose_name=u"Employé")
6bee05ff
OL
854 principal = models.BooleanField(verbose_name=u"Principal?", default=True,
855 help_text=u"Ce Dossier est pour le principal Poste occupé par l'Employé")
37868f0b
NC
856
857
fc917340 858class DossierPiece_(models.Model):
7abc6d45 859 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
860 Ex.: Lettre de motivation.
861 """
22343fe7 862 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_dossierpieces')
c1195471 863 nom = models.CharField(verbose_name = u"Nom", max_length=255)
ca1a7b76
EMS
864 fichier = models.FileField(verbose_name = u"Fichier",
865 upload_to=dossier_piece_dispatch,
83b7692b 866 storage=storage_prive)
867
6e4600ef 868 class Meta:
fc917340 869 abstract = True
6e4600ef 870 ordering = ['nom']
ca1a7b76 871
6e4600ef 872 def __unicode__(self):
873 return u'%s' % (self.nom)
874
fc917340 875class DossierPiece(DossierPiece_):
a4125771 876 pass
fc917340
OL
877
878class DossierCommentaire_(Commentaire):
a4125771 879 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
fc917340
OL
880 class Meta:
881 abstract = True
882
883class DossierCommentaire(DossierCommentaire_):
a4125771 884 pass
fc917340 885
e84c8ef1 886class DossierComparaison_(models.Model, DevisableMixin):
1d0f4eef
OL
887 """
888 Photo d'une comparaison salariale au moment de l'embauche.
889 """
a4125771 890 dossier = models.ForeignKey('%s.Dossier' % app_context(), related_name='%(app_label)s_comparaisons')
16b1454e
OL
891 objects = DossierComparaisonManager()
892
8c6269cc 893 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
1d0f4eef
OL
894 poste = models.CharField(max_length=255, null=True, blank=True)
895 personne = models.CharField(max_length=255, null=True, blank=True)
896 montant = models.IntegerField(null=True)
eb6bf568 897 devise = models.ForeignKey('Devise', related_name='+', null=True, blank=True)
1d0f4eef 898
fc917340
OL
899 class Meta:
900 abstract = True
901
3b14230d
OL
902 def __unicode__(self):
903 return "%s (%s)" % (self.poste, self.personne)
904
1d0f4eef 905
fc917340 906class DossierComparaison(DossierComparaison_):
a4125771 907 pass
2d4d2fcf 908
07b40eda 909### RÉMUNÉRATION
ca1a7b76 910
d6985a3a 911class RemunerationMixin(AUFMetadata):
a4125771 912 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_remunerations')
9afaa55e 913 # Identification
83b7692b 914 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 915 related_name='+',
c1195471 916 verbose_name = u"Type de rémunération")
ca1a7b76
EMS
917 type_revalorisation = models.ForeignKey('TypeRevalorisation',
918 db_column='type_revalorisation',
6e4600ef 919 related_name='+',
c1195471 920 verbose_name = u"Type de revalorisation",
7abc6d45 921 null=True, blank=True)
bc62f4a9 922 montant = models.DecimalField(null=True, blank=True,
4ce980ae 923 default=0, max_digits=12, decimal_places=2)
2d4d2fcf 924 # Annuel (12 mois, 52 semaines, 364 jours?)
a2c3ad52 925 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
2d4d2fcf 926 # commentaire = precision
927 commentaire = models.CharField(max_length=255, null=True, blank=True)
928 # date_debut = anciennement date_effectif
a25e1d5c 929 date_debut = models.DateField(verbose_name = u"Date de début",
6e4600ef 930 null=True, blank=True)
a25e1d5c 931 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 932 null=True, blank=True)
ca1a7b76
EMS
933
934 class Meta:
2d4d2fcf 935 abstract = True
6e4600ef 936 ordering = ['type__nom', '-date_fin']
ca1a7b76 937
6e4600ef 938 def __unicode__(self):
939 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
ca1a7b76 940
e84c8ef1 941class Remuneration_(RemunerationMixin, DevisableMixin):
2d4d2fcf 942 """Structure de rémunération (données budgétaires) en situation normale
ca1a7b76
EMS
943 pour un Dossier. Si un Evenement existe, utiliser la structure de
944 rémunération EvenementRemuneration de cet événement.
2d4d2fcf 945 """
83b7692b 946
947 def montant_mois(self):
948 return round(self.montant / 12, 2)
949
626beb4d
JPC
950 def montant_avec_regime(self):
951 return round(self.montant * (self.dossier.regime_travail/100), 2)
952
83b7692b 953 def montant_euro_mois(self):
e84c8ef1 954 return round(self.montant_euros() / 12, 2)
ca1a7b76 955
9afaa55e 956 def __unicode__(self):
957 try:
958 devise = self.devise.code
959 except:
960 devise = "???"
961 return "%s %s" % (self.montant, devise)
83b7692b 962
6e7c919b
NC
963 class Meta:
964 abstract = True
c1195471
OL
965 verbose_name = u"Rémunération"
966 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
967
968
969class Remuneration(Remuneration_):
a4125771 970 pass
6e7c919b 971
2d4d2fcf 972
973### CONTRATS
c41b7fcc
OL
974
975class ContratManager(NoDeleteManager):
976 def get_query_set(self):
977 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
978
2d4d2fcf 979
fc917340 980class Contrat_(AUFMetadata):
2d4d2fcf 981 """Document juridique qui encadre la relation de travail d'un Employe
ca1a7b76 982 pour un Poste particulier. Pour un Dossier (qui documente cette
2d4d2fcf 983 relation de travail) plusieurs contrats peuvent être associés.
984 """
c41b7fcc 985 objects = ContratManager()
865ca9e0 986 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_contrats')
6e4600ef 987 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 988 related_name='+',
7baa5523 989 verbose_name = u"type de contrat")
a25e1d5c
OL
990 date_debut = models.DateField(verbose_name = u"Date de début")
991 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 992 null=True, blank=True)
4ba73558
OL
993 fichier = models.FileField(verbose_name = u"Fichier",
994 upload_to=contrat_dispatch,
995 storage=storage_prive,
996 null=True, blank=True)
6e4600ef 997
998 class Meta:
fc917340 999 abstract = True
a2c3ad52 1000 ordering = ['dossier__employe__nom']
c1195471
OL
1001 verbose_name = u"Contrat"
1002 verbose_name_plural = u"Contrats"
ca1a7b76 1003
6e4600ef 1004 def __unicode__(self):
8c1ae2b3 1005 return u'%s - %s' % (self.dossier, self.id)
fc917340
OL
1006
1007class Contrat(Contrat_):
a4125771 1008 pass
6e4600ef 1009
2d4d2fcf 1010
1011### ÉVÉNEMENTS
1012
a4125771
OL
1013#class Evenement_(AUFMetadata):
1014# """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
1015# d'un Dossier qui vient altérer des informations normales liées à un Dossier
1016# (ex.: la Remuneration).
1017#
1018# Ex.: congé de maternité, maladie...
1019#
1020# Lors de ces situations exceptionnelles, l'Employe a un régime de travail
1021# différent et une rémunération en conséquence. On souhaite toutefois
1022# conserver le Dossier intact afin d'éviter une re-saisie des données lors
1023# du retour à la normale.
1024# """
1025# dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
1026# related_name='+')
1027# nom = models.CharField(max_length=255)
1028# date_debut = models.DateField(verbose_name = u"Date de début")
1029# date_fin = models.DateField(verbose_name = u"Date de fin",
1030# null=True, blank=True)
1031#
1032# class Meta:
1033# abstract = True
1034# ordering = ['nom']
1035# verbose_name = u"Évènement"
1036# verbose_name_plural = u"Évènements"
1037#
1038# def __unicode__(self):
1039# return u'%s' % (self.nom)
1040#
1041#
1042#class Evenement(Evenement_):
1043# __doc__ = Evenement_.__doc__
1044#
1045#
1046#class EvenementRemuneration_(RemunerationMixin):
1047# """Structure de rémunération liée à un Evenement qui remplace
1048# temporairement la Remuneration normale d'un Dossier, pour toute la durée
1049# de l'Evenement.
1050# """
1051# evenement = models.ForeignKey("Evenement", db_column='evenement',
1052# related_name='+',
1053# verbose_name = u"Évènement")
1054# # TODO : le champ dossier hérité de Remuneration doit être dérivé
1055# # de l'Evenement associé
1056#
1057# class Meta:
1058# abstract = True
1059# ordering = ['evenement', 'type__nom', '-date_fin']
1060# verbose_name = u"Évènement - rémunération"
1061# verbose_name_plural = u"Évènements - rémunérations"
1062#
1063#
1064#class EvenementRemuneration(EvenementRemuneration_):
1065# __doc__ = EvenementRemuneration_.__doc__
1066#
1067# class Meta:
1068# abstract = True
1069#
1070#
1071#class EvenementRemuneration(EvenementRemuneration_):
1072# __doc__ = EvenementRemuneration_.__doc__
865ca9e0 1073# TODO? class ContratPiece(models.Model):
f31ddfa0 1074
83b7692b 1075
ca1a7b76 1076### RÉFÉRENCES RH
83b7692b 1077
d6985a3a 1078class FamilleEmploi(AUFMetadata):
6e4600ef 1079 """Catégorie utilisée dans la gestion des Postes.
1080 Catégorie supérieure à TypePoste.
1081 """
e9bbd6ba 1082 nom = models.CharField(max_length=255)
ca1a7b76 1083
8c1ae2b3 1084 class Meta:
1085 ordering = ['nom']
c1195471
OL
1086 verbose_name = u"Famille d'emploi"
1087 verbose_name_plural = u"Familles d'emploi"
ca1a7b76 1088
6e4600ef 1089 def __unicode__(self):
1090 return u'%s' % (self.nom)
e9bbd6ba 1091
d6985a3a 1092class TypePoste(AUFMetadata):
6e4600ef 1093 """Catégorie de Poste.
1094 """
e9bbd6ba 1095 nom = models.CharField(max_length=255)
8c1ae2b3 1096 nom_feminin = models.CharField(max_length=255,
c1195471 1097 verbose_name = u"Nom féminin")
ca1a7b76 1098
8c1ae2b3 1099 is_responsable = models.BooleanField(default=False,
c1195471 1100 verbose_name = u"Poste de responsabilité")
ca1a7b76 1101 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 1102 db_column='famille_emploi',
8c1ae2b3 1103 related_name='+',
ec1b4c33 1104 verbose_name = u"famille d'emploi")
e9bbd6ba 1105
6e4600ef 1106 class Meta:
1107 ordering = ['nom']
c1195471
OL
1108 verbose_name = u"Type de poste"
1109 verbose_name_plural = u"Types de poste"
ca1a7b76 1110
e9bbd6ba 1111 def __unicode__(self):
6e4600ef 1112 return u'%s' % (self.nom)
e9bbd6ba 1113
1114
1115TYPE_PAIEMENT_CHOICES = (
a3e3bde0
JPC
1116 (u'Régulier', u'Régulier'),
1117 (u'Ponctuel', u'Ponctuel'),
e9bbd6ba 1118)
1119
1120NATURE_REMUNERATION_CHOICES = (
a3e3bde0
JPC
1121 (u'Accessoire', u'Accessoire'),
1122 (u'Charges', u'Charges'),
1123 (u'Indemnité', u'Indemnité'),
1124 (u'RAS', u'Rémunération autre source'),
1125 (u'Traitement', u'Traitement'),
e9bbd6ba 1126)
1127
d6985a3a 1128class TypeRemuneration(AUFMetadata):
6e4600ef 1129 """Catégorie de Remuneration.
1130 """
7ba822a6
OL
1131 objects = TypeRemunerationManager()
1132
e9bbd6ba 1133 nom = models.CharField(max_length=255)
ca1a7b76 1134 type_paiement = models.CharField(max_length=30,
8c1ae2b3 1135 choices=TYPE_PAIEMENT_CHOICES,
c1195471 1136 verbose_name = u"Type de paiement")
ca1a7b76 1137 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 1138 choices=NATURE_REMUNERATION_CHOICES,
c1195471 1139 verbose_name = u"Nature de la rémunération")
7ba822a6 1140 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
ca1a7b76 1141
8c1ae2b3 1142 class Meta:
1143 ordering = ['nom']
c1195471
OL
1144 verbose_name = u"Type de rémunération"
1145 verbose_name_plural = u"Types de rémunération"
9afaa55e 1146
1147 def __unicode__(self):
7ba822a6
OL
1148 if self.archive:
1149 archive = u"(archivé)"
1150 else:
1151 archive = ""
1152 return u'%s %s' % (self.nom, archive)
ca1a7b76 1153
d6985a3a 1154class TypeRevalorisation(AUFMetadata):
7abc6d45 1155 """Justification du changement de la Remuneration.
6e4600ef 1156 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 1157 """
e9bbd6ba 1158 nom = models.CharField(max_length=255)
ca1a7b76 1159
8c1ae2b3 1160 class Meta:
1161 ordering = ['nom']
c1195471
OL
1162 verbose_name = u"Type de revalorisation"
1163 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 1164
1165 def __unicode__(self):
6e4600ef 1166 return u'%s' % (self.nom)
ca1a7b76 1167
d6985a3a 1168class Service(AUFMetadata):
6e4600ef 1169 """Unité administrative où les Postes sont rattachés.
1170 """
7ba822a6
OL
1171 objects = ServiceManager()
1172
f614ca5c 1173 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
6e4600ef 1174 nom = models.CharField(max_length=255)
ca1a7b76 1175
9afaa55e 1176 class Meta:
1177 ordering = ['nom']
c1195471
OL
1178 verbose_name = u"Service"
1179 verbose_name_plural = u"Services"
e9bbd6ba 1180
6e4600ef 1181 def __unicode__(self):
41ced34a
OL
1182 if self.archive:
1183 archive = u"(archivé)"
1184 else:
1185 archive = ""
1186 return u'%s %s' % (self.nom, archive)
6e4600ef 1187
e9bbd6ba 1188
1189TYPE_ORGANISME_CHOICES = (
1190 ('MAD', 'Mise à disposition'),
1191 ('DET', 'Détachement'),
1192)
1193
d6985a3a 1194class OrganismeBstg(AUFMetadata):
ca1a7b76 1195 """Organisation d'où provient un Employe mis à disposition (MAD) de
6e4600ef 1196 ou détaché (DET) à l'AUF à titre gratuit.
ca1a7b76 1197
6e4600ef 1198 (BSTG = bien et service à titre gratuit.)
1199 """
e9bbd6ba 1200 nom = models.CharField(max_length=255)
1201 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
ca1a7b76 1202 pays = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 1203 db_column='pays',
1204 related_name='organismes_bstg',
1205 null=True, blank=True)
9afaa55e 1206
1207 class Meta:
1208 ordering = ['type', 'nom']
c1195471
OL
1209 verbose_name = u"Organisme BSTG"
1210 verbose_name_plural = u"Organismes BSTG"
9afaa55e 1211
6e4600ef 1212 def __unicode__(self):
8c1ae2b3 1213 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 1214
aff1a4c6
PP
1215 prefix_implantation = "pays__region"
1216 def get_regions(self):
1217 return [self.pays.region]
1218
1219
d6985a3a 1220class Statut(AUFMetadata):
6e4600ef 1221 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1222 """
9afaa55e 1223 # Identification
a122050d 1224 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.")
e9bbd6ba 1225 nom = models.CharField(max_length=255)
e9bbd6ba 1226
6e4600ef 1227 class Meta:
1228 ordering = ['code']
c1195471
OL
1229 verbose_name = u"Statut d'employé"
1230 verbose_name_plural = u"Statuts d'employé"
ca1a7b76 1231
9afaa55e 1232 def __unicode__(self):
1233 return u'%s : %s' % (self.code, self.nom)
1234
83b7692b 1235
e9bbd6ba 1236TYPE_CLASSEMENT_CHOICES = (
6e4600ef 1237 ('S', 'S -Soutien'),
1238 ('T', 'T - Technicien'),
1239 ('P', 'P - Professionel'),
1240 ('C', 'C - Cadre'),
1241 ('D', 'D - Direction'),
1242 ('SO', 'SO - Sans objet [expatriés]'),
1243 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 1244)
83b7692b 1245
952ecb37
OL
1246class ClassementManager(models.Manager):
1247 """
1248 Ordonner les spcéfiquement les classements.
1249 """
1250 def get_query_set(self):
1251 qs = super(self.__class__, self).get_query_set()
1252 qs = qs.extra(select={'ponderation': 'FIND_IN_SET(type,"SO,HG,S,T,P,C,D")'})
6559f73b 1253 qs = qs.extra(order_by=('ponderation', 'echelon', 'degre', ))
952ecb37
OL
1254 return qs.all()
1255
6e7c919b 1256
d6985a3a 1257class Classement_(AUFMetadata):
ca1a7b76 1258 """Éléments de classement de la
6e4600ef 1259 "Grille générique de classement hiérarchique".
ca1a7b76
EMS
1260
1261 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
6e4600ef 1262 classement dans la grille. Le classement donne le coefficient utilisé dans:
1263
1264 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1265 """
952ecb37
OL
1266 objects = ClassementManager()
1267
9afaa55e 1268 # Identification
e9bbd6ba 1269 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
ca1a7b76
EMS
1270 echelon = models.IntegerField(verbose_name=u"Échelon", blank=True, default=0)
1271 degre = models.IntegerField(verbose_name=u"Degré", blank=True, default=0)
1272 coefficient = models.FloatField(default=0, verbose_name=u"Coefficient",
8277a35b 1273 null=True)
9afaa55e 1274 # Méta
6e4600ef 1275 # annee # au lieu de date_debut et date_fin
1276 commentaire = models.TextField(null=True, blank=True)
ca1a7b76 1277
6e4600ef 1278 class Meta:
6e7c919b 1279 abstract = True
6e4600ef 1280 ordering = ['type','echelon','degre','coefficient']
c1195471
OL
1281 verbose_name = u"Classement"
1282 verbose_name_plural = u"Classements"
e9bbd6ba 1283
1284 def __unicode__(self):
22343fe7 1285 return u'%s.%s.%s' % (self.type, self.echelon, self.degre, )
e9bbd6ba 1286
6e7c919b
NC
1287class Classement(Classement_):
1288 __doc__ = Classement_.__doc__
1289
1290
d6985a3a 1291class TauxChange_(AUFMetadata):
ca1a7b76 1292 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 1293 pour chaque année budgétaire.
7abc6d45 1294 """
9afaa55e 1295 # Identification
8d3e2fff 1296 devise = models.ForeignKey('Devise', db_column='devise')
c1195471
OL
1297 annee = models.IntegerField(verbose_name = u"Année")
1298 taux = models.FloatField(verbose_name = u"Taux vers l'euro")
6e7c919b 1299
6e4600ef 1300 class Meta:
6e7c919b 1301 abstract = True
8c1ae2b3 1302 ordering = ['-annee', 'devise__code']
c1195471
OL
1303 verbose_name = u"Taux de change"
1304 verbose_name_plural = u"Taux de change"
ca1a7b76 1305
6e4600ef 1306 def __unicode__(self):
8c1ae2b3 1307 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1308
6e7c919b
NC
1309
1310class TauxChange(TauxChange_):
1311 __doc__ = TauxChange_.__doc__
1312
701f3bea 1313class ValeurPointManager(NoDeleteManager):
105dd778 1314
701f3bea
OL
1315 def get_query_set(self):
1316 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
1317
6e7c919b 1318
d6985a3a 1319class ValeurPoint_(AUFMetadata):
ca1a7b76
EMS
1320 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1321 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1322 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1323
1324 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1325 """
ca1a7b76 1326
09aa8374 1327 actuelles = ValeurPointManager()
701f3bea 1328
8277a35b 1329 valeur = models.FloatField(null=True)
f614ca5c 1330 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
ca1a7b76 1331 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1332 db_column='implantation',
1333 related_name='%(app_label)s_valeur_point')
9afaa55e 1334 # Méta
e9bbd6ba 1335 annee = models.IntegerField()
9afaa55e 1336
6e4600ef 1337 class Meta:
701f3bea 1338 ordering = ['-annee', 'implantation__nom']
6e7c919b 1339 abstract = True
c1195471
OL
1340 verbose_name = u"Valeur du point"
1341 verbose_name_plural = u"Valeurs du point"
6e0bbb73 1342
9afaa55e 1343 def __unicode__(self):
24eb08a3 1344 return u'%s %s %s [%s] %s' % (self.devise.code, self.annee, self.valeur, self.implantation.nom_court, self.devise.nom)
6e7c919b
NC
1345
1346
1347class ValeurPoint(ValeurPoint_):
1348 __doc__ = ValeurPoint_.__doc__
1349
e9bbd6ba 1350
105dd778 1351
d6985a3a 1352class Devise(AUFMetadata):
6e4600ef 1353 """Devise monétaire.
1354 """
4bdadf8b
OL
1355
1356 objects = DeviseManager()
1357
edb35076 1358 archive = models.BooleanField(verbose_name=u"Archivé", default=False)
e9bbd6ba 1359 code = models.CharField(max_length=10, unique=True)
1360 nom = models.CharField(max_length=255)
1361
6e4600ef 1362 class Meta:
1363 ordering = ['code']
c1195471
OL
1364 verbose_name = u"Devise"
1365 verbose_name_plural = u"Devises"
ca1a7b76 1366
e9bbd6ba 1367 def __unicode__(self):
1368 return u'%s - %s' % (self.code, self.nom)
1369
d6985a3a 1370class TypeContrat(AUFMetadata):
6e4600ef 1371 """Type de contrat.
1372 """
e9bbd6ba 1373 nom = models.CharField(max_length=255)
6e4600ef 1374 nom_long = models.CharField(max_length=255)
49f9f116 1375
8c1ae2b3 1376 class Meta:
1377 ordering = ['nom']
c1195471
OL
1378 verbose_name = u"Type de contrat"
1379 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1380
9afaa55e 1381 def __unicode__(self):
1382 return u'%s' % (self.nom)
ca1a7b76
EMS
1383
1384
2d4d2fcf 1385### AUTRES
1386
d6985a3a 1387class ResponsableImplantation(AUFMetadata):
ca1a7b76 1388 """Le responsable d'une implantation.
30be56d5 1389 Anciennement géré sur le Dossier du responsable.
1390 """
ca1a7b76 1391 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 1392 related_name='+',
1393 null=True, blank=True)
ca1a7b76
EMS
1394 implantation = models.ForeignKey(ref.Implantation,
1395 db_column='implantation', related_name='+',
6e4600ef 1396 unique=True)
30be56d5 1397
1398 def __unicode__(self):
1399 return u'%s : %s' % (self.implantation, self.employe)
ca1a7b76 1400
30be56d5 1401 class Meta:
1402 ordering = ['implantation__nom']
8c1ae2b3 1403 verbose_name = "Responsable d'implantation"
1404 verbose_name_plural = "Responsables d'implantation"
4c53dda4 1405