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