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