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