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