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