ayant droit 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",
cf022e27 488 help_text=HELP_TEXT_DATE,
25037368 489 validators=[validate_date_passee],
6e4600ef 490 null=True, blank=True)
2d4d2fcf 491 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
ca1a7b76 492
9afaa55e 493 # Relation
ca1a7b76 494 employe = models.ForeignKey('Employe', db_column='employe',
8c1ae2b3 495 related_name='ayantdroits',
c1195471 496 verbose_name = u"Employé")
ca1a7b76 497 lien_parente = models.CharField(max_length=10,
6e4600ef 498 choices=LIEN_PARENTE_CHOICES,
c1195471 499 verbose_name = u"Lien de parenté",
6e4600ef 500 null=True, blank=True)
501
502 class Meta:
503 ordering = ['nom_affichage']
c1195471
OL
504 verbose_name = u"Ayant droit"
505 verbose_name_plural = u"Ayants droit"
ca1a7b76 506
6e4600ef 507 def __unicode__(self):
8c1ae2b3 508 return u'%s' % (self.get_nom())
ca1a7b76 509
8c1ae2b3 510 def get_nom(self):
511 nom_affichage = self.nom_affichage
512 if not nom_affichage:
513 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
514 return nom_affichage
83b7692b 515
aff1a4c6
PP
516 prefix_implantation = "employe__dossiers__poste__implantation__region"
517 def get_regions(self):
518 regions = []
519 for d in self.employe.dossiers.all():
520 regions.append(d.poste.implantation.region)
521 return regions
522
523
07b40eda 524class AyantDroitCommentaire(Commentaire):
8c1ae2b3 525 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
6e4600ef 526 related_name='+')
83b7692b 527
2d4d2fcf 528
83b7692b 529### DOSSIER
530
531STATUT_RESIDENCE_CHOICES = (
532 ('local', 'Local'),
533 ('expat', 'Expatrié'),
534)
535
536COMPTE_COMPTA_CHOICES = (
537 ('coda', 'CODA'),
538 ('scs', 'SCS'),
539 ('aucun', 'Aucun'),
540)
541
d6985a3a 542class Dossier_(AUFMetadata):
6e4600ef 543 """Le Dossier regroupe les informations relatives à l'occupation
544 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
545 par un Employe.
ca1a7b76 546
6e4600ef 547 Plusieurs Contrats peuvent être associés au Dossier.
548 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
549 lequel aucun Dossier n'existe est un poste vacant.
550 """
3f5cbabe
OL
551
552 objects = DossierManager()
553
63e17dff 554 # TODO: OneToOne ??
8277a35b
NC
555 statut = models.ForeignKey('Statut', related_name='+', default=3,
556 null=True)
ca1a7b76 557 organisme_bstg = models.ForeignKey('OrganismeBstg',
6e4600ef 558 db_column='organisme_bstg',
559 related_name='+',
ca1a7b76 560 verbose_name = u"Organisme",
8c1ae2b3 561 help_text="Si détaché (DET) ou \
6e4600ef 562 mis à disposition (MAD), \
563 préciser l'organisme.",
564 null=True, blank=True)
ca1a7b76 565
83b7692b 566 # Recrutement
2d4d2fcf 567 remplacement = models.BooleanField(default=False)
7c182958
PP
568 remplacement_de = models.ForeignKey('self', related_name='+',
569 null=True, blank=True)
ca1a7b76 570 statut_residence = models.CharField(max_length=10, default='local',
c1195471 571 verbose_name = u"Statut", null=True,
2d4d2fcf 572 choices=STATUT_RESIDENCE_CHOICES)
ca1a7b76 573
83b7692b 574 # Rémunération
ca1a7b76 575 classement = models.ForeignKey('Classement', db_column='classement',
6e4600ef 576 related_name='+',
577 null=True, blank=True)
8277a35b 578 regime_travail = models.DecimalField(max_digits=12, null=True,
2d4d2fcf 579 decimal_places=2,
580 default=REGIME_TRAVAIL_DEFAULT,
c1195471 581 verbose_name = u"Régime de travail",
8c1ae2b3 582 help_text="% du temps complet")
83b7692b 583 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
8277a35b 584 decimal_places=2, null=True,
2d4d2fcf 585 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
c1195471 586 verbose_name = u"Nb. heures par semaine")
7abc6d45 587
588 # Occupation du Poste par cet Employe (anciennement "mandat")
c1195471 589 date_debut = models.DateField(verbose_name = u"Date de début d'occupation \
a25e1d5c 590 de poste",)
c1195471 591 date_fin = models.DateField(verbose_name = u"Date de fin d'occupation \
6e4600ef 592 de poste",
6e4600ef 593 null=True, blank=True)
ca1a7b76 594
2d4d2fcf 595 # Comptes
596 # TODO?
ca1a7b76 597
6e4600ef 598 class Meta:
37868f0b 599 abstract = True
49449367 600 ordering = ['employe__nom', ]
3f5f3898 601 verbose_name = u"Dossier"
8c1ae2b3 602 verbose_name_plural = "Dossiers"
ca1a7b76 603
65f9fac8 604 def salaire_theorique(self):
605 annee = date.today().year
606 coeff = self.classement.coefficient
607 implantation = self.poste.implantation
608 point = ValeurPoint.objects.get(implantation=implantation, annee=annee)
ca1a7b76 609
65f9fac8 610 montant = coeff * point.valeur
611 devise = point.devise
612 return {'montant':montant, 'devise':devise}
ca1a7b76 613
83b7692b 614 def __unicode__(self):
8c1ae2b3 615 poste = self.poste.nom
616 if self.employe.genre == 'F':
ca1a7b76 617 poste = self.poste.nom_feminin
8c1ae2b3 618 return u'%s - %s' % (self.employe, poste)
83b7692b 619
aff1a4c6
PP
620 prefix_implantation = "poste__implantation__region"
621 def get_regions(self):
622 return [self.poste.implantation.region]
623
37868f0b 624
3ebc0952 625 def remunerations(self):
a4125771 626 return self.rh_remunerations.all().order_by('date_debut')
3ebc0952 627
09aa8374
OL
628 def get_salaire(self):
629 try:
630 return [r for r in self.remunerations().order_by('-date_debut') if r.type_id == 1][0]
631 except:
632 return None
3ebc0952 633
37868f0b
NC
634class Dossier(Dossier_):
635 __doc__ = Dossier_.__doc__
a4125771 636 poste = models.ForeignKey('%s.Poste' % app_context(), db_column='poste', related_name='%(app_label)s_dossiers')
16b1454e
OL
637 employe = models.ForeignKey('Employe', db_column='employe',
638 related_name='%(app_label)s_dossiers',
639 verbose_name=u"Employé")
37868f0b
NC
640
641
fc917340 642class DossierPiece_(models.Model):
7abc6d45 643 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
644 Ex.: Lettre de motivation.
645 """
a4125771 646 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
c1195471 647 nom = models.CharField(verbose_name = u"Nom", max_length=255)
ca1a7b76
EMS
648 fichier = models.FileField(verbose_name = u"Fichier",
649 upload_to=dossier_piece_dispatch,
83b7692b 650 storage=storage_prive)
651
6e4600ef 652 class Meta:
fc917340 653 abstract = True
6e4600ef 654 ordering = ['nom']
ca1a7b76 655
6e4600ef 656 def __unicode__(self):
657 return u'%s' % (self.nom)
658
fc917340 659class DossierPiece(DossierPiece_):
a4125771 660 pass
fc917340
OL
661
662class DossierCommentaire_(Commentaire):
a4125771 663 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
fc917340
OL
664 class Meta:
665 abstract = True
666
667class DossierCommentaire(DossierCommentaire_):
a4125771 668 pass
fc917340
OL
669
670class DossierComparaison_(models.Model):
1d0f4eef
OL
671 """
672 Photo d'une comparaison salariale au moment de l'embauche.
673 """
a4125771 674 dossier = models.ForeignKey('%s.Dossier' % app_context(), related_name='%(app_label)s_comparaisons')
16b1454e
OL
675 objects = DossierComparaisonManager()
676
8c6269cc 677 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
1d0f4eef
OL
678 poste = models.CharField(max_length=255, null=True, blank=True)
679 personne = models.CharField(max_length=255, null=True, blank=True)
680 montant = models.IntegerField(null=True)
93494cf1 681 devise = models.ForeignKey('Devise', default=5, related_name='+', null=True, blank=True)
1d0f4eef 682
fc917340
OL
683 class Meta:
684 abstract = True
685
1d0f4eef 686 def taux_devise(self):
a4125771
OL
687 annee = self.dossier.poste.date_debut.year
688 taux = [tc.taux for tc in TauxChange.objects.filter(devise=self.devise, annee=annee)]
689 taux = set(taux)
690 if len(taux) != 1:
691 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 692 else:
a4125771 693 return list(taux)[0]
1d0f4eef
OL
694
695 def montant_euros(self):
696 return round(float(self.montant) * float(self.taux_devise()), 2)
697
fc917340 698class DossierComparaison(DossierComparaison_):
a4125771 699 pass
2d4d2fcf 700
07b40eda 701### RÉMUNÉRATION
ca1a7b76 702
d6985a3a 703class RemunerationMixin(AUFMetadata):
a4125771 704 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_remunerations')
9afaa55e 705 # Identification
83b7692b 706 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 707 related_name='+',
c1195471 708 verbose_name = u"Type de rémunération")
ca1a7b76
EMS
709 type_revalorisation = models.ForeignKey('TypeRevalorisation',
710 db_column='type_revalorisation',
6e4600ef 711 related_name='+',
c1195471 712 verbose_name = u"Type de revalorisation",
7abc6d45 713 null=True, blank=True)
50fa9bc1 714 montant = models.FloatField(null=True, blank=True,
6e4600ef 715 default=0)
2d4d2fcf 716 # Annuel (12 mois, 52 semaines, 364 jours?)
a4125771 717 devise = models.ForeignKey('Devise', db_column='devise', related_name='+', default=5)
2d4d2fcf 718 # commentaire = precision
719 commentaire = models.CharField(max_length=255, null=True, blank=True)
720 # date_debut = anciennement date_effectif
a25e1d5c 721 date_debut = models.DateField(verbose_name = u"Date de début",
6e4600ef 722 null=True, blank=True)
a25e1d5c 723 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 724 null=True, blank=True)
ca1a7b76
EMS
725
726 class Meta:
2d4d2fcf 727 abstract = True
6e4600ef 728 ordering = ['type__nom', '-date_fin']
ca1a7b76 729
6e4600ef 730 def __unicode__(self):
731 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
ca1a7b76 732
6e7c919b 733class Remuneration_(RemunerationMixin):
2d4d2fcf 734 """Structure de rémunération (données budgétaires) en situation normale
ca1a7b76
EMS
735 pour un Dossier. Si un Evenement existe, utiliser la structure de
736 rémunération EvenementRemuneration de cet événement.
2d4d2fcf 737 """
83b7692b 738
739 def montant_mois(self):
740 return round(self.montant / 12, 2)
741
742 def taux_devise(self):
a4125771
OL
743 if self.devise.code == "EUR":
744 return 1
745
746 annee = datetime.datetime.now().year
747 if self.date_debut is not None:
748 annee = self.date_debut.year
749 if self.dossier.poste.date_debut is not None:
8d72cd59 750 annee = self.dossier.poste.date_debut.year
a4125771
OL
751
752 taux = [tc.taux for tc in TauxChange.objects.filter(devise=self.devise_id, annee=annee)]
753 taux = set(taux)
754 if len(taux) != 1:
755 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))
756 else:
757 return list(taux)[0]
83b7692b 758
759 def montant_euro(self):
a4125771 760 return round(float(self.montant) * float(self.taux_devise()), 2)
83b7692b 761
762 def montant_euro_mois(self):
763 return round(self.montant_euro() / 12, 2)
ca1a7b76 764
9afaa55e 765 def __unicode__(self):
766 try:
767 devise = self.devise.code
768 except:
769 devise = "???"
770 return "%s %s" % (self.montant, devise)
83b7692b 771
6e7c919b
NC
772 class Meta:
773 abstract = True
c1195471
OL
774 verbose_name = u"Rémunération"
775 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
776
777
778class Remuneration(Remuneration_):
a4125771 779 pass
6e7c919b 780
2d4d2fcf 781
782### CONTRATS
c41b7fcc
OL
783
784class ContratManager(NoDeleteManager):
785 def get_query_set(self):
786 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
787
2d4d2fcf 788
fc917340 789class Contrat_(AUFMetadata):
2d4d2fcf 790 """Document juridique qui encadre la relation de travail d'un Employe
ca1a7b76 791 pour un Poste particulier. Pour un Dossier (qui documente cette
2d4d2fcf 792 relation de travail) plusieurs contrats peuvent être associés.
793 """
c41b7fcc 794 objects = ContratManager()
865ca9e0 795 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_contrats')
6e4600ef 796 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 797 related_name='+',
c1195471 798 verbose_name = u"Type de contrat")
a25e1d5c
OL
799 date_debut = models.DateField(verbose_name = u"Date de début")
800 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 801 null=True, blank=True)
802
803 class Meta:
fc917340 804 abstract = True
6e4600ef 805 ordering = ['dossier__employe__nom_affichage']
c1195471
OL
806 verbose_name = u"Contrat"
807 verbose_name_plural = u"Contrats"
ca1a7b76 808
6e4600ef 809 def __unicode__(self):
8c1ae2b3 810 return u'%s - %s' % (self.dossier, self.id)
fc917340
OL
811
812class Contrat(Contrat_):
a4125771 813 pass
6e4600ef 814
2d4d2fcf 815
816### ÉVÉNEMENTS
817
a4125771
OL
818#class Evenement_(AUFMetadata):
819# """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
820# d'un Dossier qui vient altérer des informations normales liées à un Dossier
821# (ex.: la Remuneration).
822#
823# Ex.: congé de maternité, maladie...
824#
825# Lors de ces situations exceptionnelles, l'Employe a un régime de travail
826# différent et une rémunération en conséquence. On souhaite toutefois
827# conserver le Dossier intact afin d'éviter une re-saisie des données lors
828# du retour à la normale.
829# """
830# dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
831# related_name='+')
832# nom = models.CharField(max_length=255)
833# date_debut = models.DateField(verbose_name = u"Date de début")
834# date_fin = models.DateField(verbose_name = u"Date de fin",
835# null=True, blank=True)
836#
837# class Meta:
838# abstract = True
839# ordering = ['nom']
840# verbose_name = u"Évènement"
841# verbose_name_plural = u"Évènements"
842#
843# def __unicode__(self):
844# return u'%s' % (self.nom)
845#
846#
847#class Evenement(Evenement_):
848# __doc__ = Evenement_.__doc__
849#
850#
851#class EvenementRemuneration_(RemunerationMixin):
852# """Structure de rémunération liée à un Evenement qui remplace
853# temporairement la Remuneration normale d'un Dossier, pour toute la durée
854# de l'Evenement.
855# """
856# evenement = models.ForeignKey("Evenement", db_column='evenement',
857# related_name='+',
858# verbose_name = u"Évènement")
859# # TODO : le champ dossier hérité de Remuneration doit être dérivé
860# # de l'Evenement associé
861#
862# class Meta:
863# abstract = True
864# ordering = ['evenement', 'type__nom', '-date_fin']
865# verbose_name = u"Évènement - rémunération"
866# verbose_name_plural = u"Évènements - rémunérations"
867#
868#
869#class EvenementRemuneration(EvenementRemuneration_):
870# __doc__ = EvenementRemuneration_.__doc__
871#
872# class Meta:
873# abstract = True
874#
875#
876#class EvenementRemuneration(EvenementRemuneration_):
877# __doc__ = EvenementRemuneration_.__doc__
865ca9e0 878# TODO? class ContratPiece(models.Model):
f31ddfa0 879
83b7692b 880
ca1a7b76 881### RÉFÉRENCES RH
83b7692b 882
d6985a3a 883class FamilleEmploi(AUFMetadata):
6e4600ef 884 """Catégorie utilisée dans la gestion des Postes.
885 Catégorie supérieure à TypePoste.
886 """
e9bbd6ba 887 nom = models.CharField(max_length=255)
ca1a7b76 888
8c1ae2b3 889 class Meta:
890 ordering = ['nom']
c1195471
OL
891 verbose_name = u"Famille d'emploi"
892 verbose_name_plural = u"Familles d'emploi"
ca1a7b76 893
6e4600ef 894 def __unicode__(self):
895 return u'%s' % (self.nom)
e9bbd6ba 896
d6985a3a 897class TypePoste(AUFMetadata):
6e4600ef 898 """Catégorie de Poste.
899 """
e9bbd6ba 900 nom = models.CharField(max_length=255)
8c1ae2b3 901 nom_feminin = models.CharField(max_length=255,
c1195471 902 verbose_name = u"Nom féminin")
ca1a7b76 903
8c1ae2b3 904 is_responsable = models.BooleanField(default=False,
c1195471 905 verbose_name = u"Poste de responsabilité")
ca1a7b76 906 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 907 db_column='famille_emploi',
8c1ae2b3 908 related_name='+',
c1195471 909 verbose_name = u"Famille d'emploi")
e9bbd6ba 910
6e4600ef 911 class Meta:
912 ordering = ['nom']
c1195471
OL
913 verbose_name = u"Type de poste"
914 verbose_name_plural = u"Types de poste"
ca1a7b76 915
e9bbd6ba 916 def __unicode__(self):
6e4600ef 917 return u'%s' % (self.nom)
e9bbd6ba 918
919
920TYPE_PAIEMENT_CHOICES = (
921 ('Régulier', 'Régulier'),
922 ('Ponctuel', 'Ponctuel'),
923)
924
925NATURE_REMUNERATION_CHOICES = (
926 ('Accessoire', 'Accessoire'),
927 ('Charges', 'Charges'),
928 ('Indemnité', 'Indemnité'),
7abc6d45 929 ('RAS', 'Rémunération autre source'),
e9bbd6ba 930 ('Traitement', 'Traitement'),
931)
932
d6985a3a 933class TypeRemuneration(AUFMetadata):
6e4600ef 934 """Catégorie de Remuneration.
935 """
e9bbd6ba 936 nom = models.CharField(max_length=255)
ca1a7b76 937 type_paiement = models.CharField(max_length=30,
8c1ae2b3 938 choices=TYPE_PAIEMENT_CHOICES,
c1195471 939 verbose_name = u"Type de paiement")
ca1a7b76 940 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 941 choices=NATURE_REMUNERATION_CHOICES,
c1195471 942 verbose_name = u"Nature de la rémunération")
ca1a7b76 943
8c1ae2b3 944 class Meta:
945 ordering = ['nom']
c1195471
OL
946 verbose_name = u"Type de rémunération"
947 verbose_name_plural = u"Types de rémunération"
9afaa55e 948
949 def __unicode__(self):
6e4600ef 950 return u'%s' % (self.nom)
ca1a7b76 951
d6985a3a 952class TypeRevalorisation(AUFMetadata):
7abc6d45 953 """Justification du changement de la Remuneration.
6e4600ef 954 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 955 """
e9bbd6ba 956 nom = models.CharField(max_length=255)
ca1a7b76 957
8c1ae2b3 958 class Meta:
959 ordering = ['nom']
c1195471
OL
960 verbose_name = u"Type de revalorisation"
961 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 962
963 def __unicode__(self):
6e4600ef 964 return u'%s' % (self.nom)
ca1a7b76 965
d6985a3a 966class Service(AUFMetadata):
6e4600ef 967 """Unité administrative où les Postes sont rattachés.
968 """
969 nom = models.CharField(max_length=255)
ca1a7b76 970
9afaa55e 971 class Meta:
972 ordering = ['nom']
c1195471
OL
973 verbose_name = u"Service"
974 verbose_name_plural = u"Services"
e9bbd6ba 975
6e4600ef 976 def __unicode__(self):
977 return u'%s' % (self.nom)
978
e9bbd6ba 979
980TYPE_ORGANISME_CHOICES = (
981 ('MAD', 'Mise à disposition'),
982 ('DET', 'Détachement'),
983)
984
d6985a3a 985class OrganismeBstg(AUFMetadata):
ca1a7b76 986 """Organisation d'où provient un Employe mis à disposition (MAD) de
6e4600ef 987 ou détaché (DET) à l'AUF à titre gratuit.
ca1a7b76 988
6e4600ef 989 (BSTG = bien et service à titre gratuit.)
990 """
e9bbd6ba 991 nom = models.CharField(max_length=255)
992 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
ca1a7b76 993 pays = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 994 db_column='pays',
995 related_name='organismes_bstg',
996 null=True, blank=True)
9afaa55e 997
998 class Meta:
999 ordering = ['type', 'nom']
c1195471
OL
1000 verbose_name = u"Organisme BSTG"
1001 verbose_name_plural = u"Organismes BSTG"
9afaa55e 1002
6e4600ef 1003 def __unicode__(self):
8c1ae2b3 1004 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 1005
aff1a4c6
PP
1006 prefix_implantation = "pays__region"
1007 def get_regions(self):
1008 return [self.pays.region]
1009
1010
d6985a3a 1011class Statut(AUFMetadata):
6e4600ef 1012 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1013 """
9afaa55e 1014 # Identification
a122050d 1015 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 1016 nom = models.CharField(max_length=255)
e9bbd6ba 1017
6e4600ef 1018 class Meta:
1019 ordering = ['code']
c1195471
OL
1020 verbose_name = u"Statut d'employé"
1021 verbose_name_plural = u"Statuts d'employé"
ca1a7b76 1022
9afaa55e 1023 def __unicode__(self):
1024 return u'%s : %s' % (self.code, self.nom)
1025
83b7692b 1026
e9bbd6ba 1027TYPE_CLASSEMENT_CHOICES = (
6e4600ef 1028 ('S', 'S -Soutien'),
1029 ('T', 'T - Technicien'),
1030 ('P', 'P - Professionel'),
1031 ('C', 'C - Cadre'),
1032 ('D', 'D - Direction'),
1033 ('SO', 'SO - Sans objet [expatriés]'),
1034 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 1035)
83b7692b 1036
6e7c919b 1037
d6985a3a 1038class Classement_(AUFMetadata):
ca1a7b76 1039 """Éléments de classement de la
6e4600ef 1040 "Grille générique de classement hiérarchique".
ca1a7b76
EMS
1041
1042 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
6e4600ef 1043 classement dans la grille. Le classement donne le coefficient utilisé dans:
1044
1045 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1046 """
9afaa55e 1047 # Identification
e9bbd6ba 1048 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
ca1a7b76
EMS
1049 echelon = models.IntegerField(verbose_name=u"Échelon", blank=True, default=0)
1050 degre = models.IntegerField(verbose_name=u"Degré", blank=True, default=0)
1051 coefficient = models.FloatField(default=0, verbose_name=u"Coefficient",
8277a35b 1052 null=True)
9afaa55e 1053 # Méta
6e4600ef 1054 # annee # au lieu de date_debut et date_fin
1055 commentaire = models.TextField(null=True, blank=True)
ca1a7b76 1056
6e4600ef 1057 class Meta:
6e7c919b 1058 abstract = True
6e4600ef 1059 ordering = ['type','echelon','degre','coefficient']
c1195471
OL
1060 verbose_name = u"Classement"
1061 verbose_name_plural = u"Classements"
e9bbd6ba 1062
1063 def __unicode__(self):
1064 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
1065 self.coefficient)
1066
6e7c919b
NC
1067class Classement(Classement_):
1068 __doc__ = Classement_.__doc__
1069
1070
d6985a3a 1071class TauxChange_(AUFMetadata):
ca1a7b76 1072 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 1073 pour chaque année budgétaire.
7abc6d45 1074 """
9afaa55e 1075 # Identification
8d3e2fff 1076 devise = models.ForeignKey('Devise', db_column='devise')
c1195471
OL
1077 annee = models.IntegerField(verbose_name = u"Année")
1078 taux = models.FloatField(verbose_name = u"Taux vers l'euro")
6e7c919b 1079
6e4600ef 1080 class Meta:
6e7c919b 1081 abstract = True
8c1ae2b3 1082 ordering = ['-annee', 'devise__code']
c1195471
OL
1083 verbose_name = u"Taux de change"
1084 verbose_name_plural = u"Taux de change"
ca1a7b76 1085
6e4600ef 1086 def __unicode__(self):
8c1ae2b3 1087 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1088
6e7c919b
NC
1089
1090class TauxChange(TauxChange_):
1091 __doc__ = TauxChange_.__doc__
1092
701f3bea
OL
1093class ValeurPointManager(NoDeleteManager):
1094 def get_query_set(self):
1095 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
1096
6e7c919b 1097
d6985a3a 1098class ValeurPoint_(AUFMetadata):
ca1a7b76
EMS
1099 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1100 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1101 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1102
1103 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1104 """
ca1a7b76 1105
09aa8374 1106 actuelles = ValeurPointManager()
701f3bea 1107
8277a35b
NC
1108 valeur = models.FloatField(null=True)
1109 devise = models.ForeignKey('Devise', db_column='devise', null=True,
6e4600ef 1110 related_name='+', default=5)
ca1a7b76 1111 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1112 db_column='implantation',
1113 related_name='%(app_label)s_valeur_point')
9afaa55e 1114 # Méta
e9bbd6ba 1115 annee = models.IntegerField()
9afaa55e 1116
6e4600ef 1117 class Meta:
701f3bea 1118 ordering = ['-annee', 'implantation__nom']
6e7c919b 1119 abstract = True
c1195471
OL
1120 verbose_name = u"Valeur du point"
1121 verbose_name_plural = u"Valeurs du point"
6e0bbb73 1122
9afaa55e 1123 def __unicode__(self):
8c1ae2b3 1124 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
6e7c919b
NC
1125
1126
1127class ValeurPoint(ValeurPoint_):
1128 __doc__ = ValeurPoint_.__doc__
1129
e9bbd6ba 1130
d6985a3a 1131class Devise(AUFMetadata):
6e4600ef 1132 """Devise monétaire.
1133 """
e9bbd6ba 1134 code = models.CharField(max_length=10, unique=True)
1135 nom = models.CharField(max_length=255)
1136
6e4600ef 1137 class Meta:
1138 ordering = ['code']
c1195471
OL
1139 verbose_name = u"Devise"
1140 verbose_name_plural = u"Devises"
ca1a7b76 1141
e9bbd6ba 1142 def __unicode__(self):
1143 return u'%s - %s' % (self.code, self.nom)
1144
d6985a3a 1145class TypeContrat(AUFMetadata):
6e4600ef 1146 """Type de contrat.
1147 """
e9bbd6ba 1148 nom = models.CharField(max_length=255)
6e4600ef 1149 nom_long = models.CharField(max_length=255)
49f9f116 1150
8c1ae2b3 1151 class Meta:
1152 ordering = ['nom']
c1195471
OL
1153 verbose_name = u"Type de contrat"
1154 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1155
9afaa55e 1156 def __unicode__(self):
1157 return u'%s' % (self.nom)
ca1a7b76
EMS
1158
1159
2d4d2fcf 1160### AUTRES
1161
d6985a3a 1162class ResponsableImplantation(AUFMetadata):
ca1a7b76 1163 """Le responsable d'une implantation.
30be56d5 1164 Anciennement géré sur le Dossier du responsable.
1165 """
ca1a7b76 1166 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 1167 related_name='+',
1168 null=True, blank=True)
ca1a7b76
EMS
1169 implantation = models.ForeignKey(ref.Implantation,
1170 db_column='implantation', related_name='+',
6e4600ef 1171 unique=True)
30be56d5 1172
1173 def __unicode__(self):
1174 return u'%s : %s' % (self.implantation, self.employe)
ca1a7b76 1175
30be56d5 1176 class Meta:
1177 ordering = ['implantation__nom']
8c1ae2b3 1178 verbose_name = "Responsable d'implantation"
1179 verbose_name_plural = "Responsables d'implantation"