fix dossiers employe listing
[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)
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):
54773196 366 return u'%s [%s]' % (self.get_nom(), self.id)
ca1a7b76 367
8c1ae2b3 368 def get_nom(self):
ebc8eb32 369 nom_affichage = self.nom_affichage
370 if not nom_affichage:
371 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
8c1ae2b3 372 return nom_affichage
ca1a7b76 373
d04d084c 374 def civilite(self):
375 civilite = u''
376 if self.genre.upper() == u'M':
377 civilite = u'M.'
378 elif self.genre.upper() == u'F':
379 civilite = u'Mme'
380 return civilite
ca1a7b76 381
5ea6b5bb 382 def url_photo(self):
383 """Retourne l'URL du service retournant la photo de l'Employe.
384 Équivalent reverse url 'rh_photo' avec id en param.
385 """
386 from django.core.urlresolvers import reverse
387 return reverse('rh_photo', kwargs={'id':self.id})
ca1a7b76 388
c267f20c 389 def dossiers_passes(self):
390 today = date.today()
65f9fac8 391 dossiers_passes = self.dossiers.filter(date_fin__lt=today).order_by('-date_fin')
392 for d in dossiers_passes:
393 d.archive = True
394 return dossiers_passes
ca1a7b76 395
c267f20c 396 def dossiers_futurs(self):
397 today = date.today()
398 return self.dossiers.filter(date_debut__gt=today).order_by('-date_fin')
ca1a7b76 399
c267f20c 400 def dossiers_encours(self):
401 dossiers_p_f = self.dossiers_passes() | self.dossiers_futurs()
402 ids_dossiers_p_f = [d.id for d in dossiers_p_f]
65f9fac8 403 dossiers_encours = self.dossiers.exclude(id__in=ids_dossiers_p_f).order_by('-date_fin')
ca1a7b76 404
65f9fac8 405 # TODO : supprimer ce code quand related_name fonctionnera ou d.remuneration_set
406 for d in dossiers_encours:
407 d.remunerations = Remuneration.objects.filter(dossier=d.id).order_by('-id')
408 return dossiers_encours
ca1a7b76 409
35c0c2fe 410 def postes_encours(self):
411 postes_encours = set()
412 for d in self.dossiers_encours():
413 postes_encours.add(d.poste)
414 return postes_encours
ca1a7b76 415
35c0c2fe 416 def poste_principal(self):
65f9fac8 417 """
418 Retourne le Poste du premier Dossier créé parmi les Dossiers en cours.
ca1a7b76 419 Idée derrière :
65f9fac8 420 si on ajout d'autre Dossiers, c'est pour des Postes secondaires.
421 """
422 poste = Poste.objects.none()
423 try:
424 poste = self.dossiers_encours().order_by('date_debut')[0].poste
425 except:
426 pass
427 return poste
9afaa55e 428
aff1a4c6
PP
429 prefix_implantation = "dossiers__poste__implantation__region"
430 def get_regions(self):
431 regions = []
432 for d in self.dossiers.all():
433 regions.append(d.poste.implantation.region)
434 return regions
435
436
7abc6d45 437class EmployePiece(models.Model):
6e4600ef 438 """Documents relatifs à un employé.
7abc6d45 439 Ex.: CV...
440 """
f9e54d59 441 employe = models.ForeignKey('Employe', db_column='employe')
8c1ae2b3 442 nom = models.CharField(verbose_name="Nom", max_length=255)
ca1a7b76
EMS
443 fichier = models.FileField(verbose_name="Fichier",
444 upload_to=employe_piece_dispatch,
7abc6d45 445 storage=storage_prive)
446
6e4600ef 447 class Meta:
448 ordering = ['nom']
f9e54d59
PP
449 verbose_name = u"Employé pièce"
450 verbose_name_plural = u"Employé pièces"
451
6e4600ef 452 def __unicode__(self):
453 return u'%s' % (self.nom)
454
07b40eda 455class EmployeCommentaire(Commentaire):
8c1ae2b3 456 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 457 related_name='+')
9afaa55e 458
b343eb3d
PP
459 class Meta:
460 verbose_name = u"Employé commentaire"
461 verbose_name_plural = u"Employé commentaires"
462
2d4d2fcf 463
e9bbd6ba 464LIEN_PARENTE_CHOICES = (
465 ('Conjoint', 'Conjoint'),
466 ('Conjointe', 'Conjointe'),
467 ('Fille', 'Fille'),
468 ('Fils', 'Fils'),
469)
470
d6985a3a 471class AyantDroit(AUFMetadata):
6e4600ef 472 """Personne en relation avec un Employe.
473 """
9afaa55e 474 # Identification
e9bbd6ba 475 nom = models.CharField(max_length=255)
8c1ae2b3 476 prenom = models.CharField(max_length=255,
c1195471 477 verbose_name = u"Prénom",)
ca1a7b76 478 nom_affichage = models.CharField(max_length=255,
c1195471 479 verbose_name = u"Nom d'affichage",
6e4600ef 480 null=True, blank=True)
ca1a7b76 481 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 482 db_column='nationalite',
8c1ae2b3 483 related_name='ayantdroits_nationalite',
ca1a7b76
EMS
484 verbose_name = u"Nationalité",
485 null=True, blank=True)
a25e1d5c 486 date_naissance = models.DateField(verbose_name = u"Date de naissance",
cf022e27 487 help_text=HELP_TEXT_DATE,
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 567 remplacement_de = models.ForeignKey('self', related_name='+',
0b0545bd 568 help_text=u"Taper le nom de l'employé",
7c182958 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__
0b0545bd
OL
636 poste = models.ForeignKey('%s.Poste' % app_context(),
637 db_column='poste',
638 related_name='%(app_label)s_dossiers',
639 help_text=u"Taper le nom du poste ou du type de poste",
640 )
641 employe = models.ForeignKey('Employe', db_column='employe',
642 help_text=u"Taper le nom de l'employé",
16b1454e
OL
643 related_name='%(app_label)s_dossiers',
644 verbose_name=u"Employé")
37868f0b
NC
645
646
fc917340 647class DossierPiece_(models.Model):
7abc6d45 648 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
649 Ex.: Lettre de motivation.
650 """
a4125771 651 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
c1195471 652 nom = models.CharField(verbose_name = u"Nom", max_length=255)
ca1a7b76
EMS
653 fichier = models.FileField(verbose_name = u"Fichier",
654 upload_to=dossier_piece_dispatch,
83b7692b 655 storage=storage_prive)
656
6e4600ef 657 class Meta:
fc917340 658 abstract = True
6e4600ef 659 ordering = ['nom']
ca1a7b76 660
6e4600ef 661 def __unicode__(self):
662 return u'%s' % (self.nom)
663
fc917340 664class DossierPiece(DossierPiece_):
a4125771 665 pass
fc917340
OL
666
667class DossierCommentaire_(Commentaire):
a4125771 668 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
fc917340
OL
669 class Meta:
670 abstract = True
671
672class DossierCommentaire(DossierCommentaire_):
a4125771 673 pass
fc917340
OL
674
675class DossierComparaison_(models.Model):
1d0f4eef
OL
676 """
677 Photo d'une comparaison salariale au moment de l'embauche.
678 """
a4125771 679 dossier = models.ForeignKey('%s.Dossier' % app_context(), related_name='%(app_label)s_comparaisons')
16b1454e
OL
680 objects = DossierComparaisonManager()
681
8c6269cc 682 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
1d0f4eef
OL
683 poste = models.CharField(max_length=255, null=True, blank=True)
684 personne = models.CharField(max_length=255, null=True, blank=True)
685 montant = models.IntegerField(null=True)
93494cf1 686 devise = models.ForeignKey('Devise', default=5, related_name='+', null=True, blank=True)
1d0f4eef 687
fc917340
OL
688 class Meta:
689 abstract = True
690
1d0f4eef 691 def taux_devise(self):
a4125771
OL
692 annee = self.dossier.poste.date_debut.year
693 taux = [tc.taux for tc in TauxChange.objects.filter(devise=self.devise, annee=annee)]
694 taux = set(taux)
695 if len(taux) != 1:
696 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 697 else:
a4125771 698 return list(taux)[0]
1d0f4eef
OL
699
700 def montant_euros(self):
701 return round(float(self.montant) * float(self.taux_devise()), 2)
702
fc917340 703class DossierComparaison(DossierComparaison_):
a4125771 704 pass
2d4d2fcf 705
07b40eda 706### RÉMUNÉRATION
ca1a7b76 707
d6985a3a 708class RemunerationMixin(AUFMetadata):
a4125771 709 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_remunerations')
9afaa55e 710 # Identification
83b7692b 711 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 712 related_name='+',
c1195471 713 verbose_name = u"Type de rémunération")
ca1a7b76
EMS
714 type_revalorisation = models.ForeignKey('TypeRevalorisation',
715 db_column='type_revalorisation',
6e4600ef 716 related_name='+',
c1195471 717 verbose_name = u"Type de revalorisation",
7abc6d45 718 null=True, blank=True)
50fa9bc1 719 montant = models.FloatField(null=True, blank=True,
6e4600ef 720 default=0)
2d4d2fcf 721 # Annuel (12 mois, 52 semaines, 364 jours?)
a4125771 722 devise = models.ForeignKey('Devise', db_column='devise', related_name='+', default=5)
2d4d2fcf 723 # commentaire = precision
724 commentaire = models.CharField(max_length=255, null=True, blank=True)
725 # date_debut = anciennement date_effectif
a25e1d5c 726 date_debut = models.DateField(verbose_name = u"Date de début",
6e4600ef 727 null=True, blank=True)
a25e1d5c 728 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 729 null=True, blank=True)
ca1a7b76
EMS
730
731 class Meta:
2d4d2fcf 732 abstract = True
6e4600ef 733 ordering = ['type__nom', '-date_fin']
ca1a7b76 734
6e4600ef 735 def __unicode__(self):
736 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
ca1a7b76 737
6e7c919b 738class Remuneration_(RemunerationMixin):
2d4d2fcf 739 """Structure de rémunération (données budgétaires) en situation normale
ca1a7b76
EMS
740 pour un Dossier. Si un Evenement existe, utiliser la structure de
741 rémunération EvenementRemuneration de cet événement.
2d4d2fcf 742 """
83b7692b 743
744 def montant_mois(self):
745 return round(self.montant / 12, 2)
746
747 def taux_devise(self):
a4125771
OL
748 if self.devise.code == "EUR":
749 return 1
750
751 annee = datetime.datetime.now().year
752 if self.date_debut is not None:
753 annee = self.date_debut.year
754 if self.dossier.poste.date_debut is not None:
8d72cd59 755 annee = self.dossier.poste.date_debut.year
a4125771
OL
756
757 taux = [tc.taux for tc in TauxChange.objects.filter(devise=self.devise_id, annee=annee)]
758 taux = set(taux)
759 if len(taux) != 1:
760 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))
761 else:
762 return list(taux)[0]
83b7692b 763
764 def montant_euro(self):
a4125771 765 return round(float(self.montant) * float(self.taux_devise()), 2)
83b7692b 766
767 def montant_euro_mois(self):
768 return round(self.montant_euro() / 12, 2)
ca1a7b76 769
9afaa55e 770 def __unicode__(self):
771 try:
772 devise = self.devise.code
773 except:
774 devise = "???"
775 return "%s %s" % (self.montant, devise)
83b7692b 776
6e7c919b
NC
777 class Meta:
778 abstract = True
c1195471
OL
779 verbose_name = u"Rémunération"
780 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
781
782
783class Remuneration(Remuneration_):
a4125771 784 pass
6e7c919b 785
2d4d2fcf 786
787### CONTRATS
c41b7fcc
OL
788
789class ContratManager(NoDeleteManager):
790 def get_query_set(self):
791 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
792
2d4d2fcf 793
fc917340 794class Contrat_(AUFMetadata):
2d4d2fcf 795 """Document juridique qui encadre la relation de travail d'un Employe
ca1a7b76 796 pour un Poste particulier. Pour un Dossier (qui documente cette
2d4d2fcf 797 relation de travail) plusieurs contrats peuvent être associés.
798 """
c41b7fcc 799 objects = ContratManager()
865ca9e0 800 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_contrats')
6e4600ef 801 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 802 related_name='+',
c1195471 803 verbose_name = u"Type de contrat")
a25e1d5c
OL
804 date_debut = models.DateField(verbose_name = u"Date de début")
805 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 806 null=True, blank=True)
807
808 class Meta:
fc917340 809 abstract = True
6e4600ef 810 ordering = ['dossier__employe__nom_affichage']
c1195471
OL
811 verbose_name = u"Contrat"
812 verbose_name_plural = u"Contrats"
ca1a7b76 813
6e4600ef 814 def __unicode__(self):
8c1ae2b3 815 return u'%s - %s' % (self.dossier, self.id)
fc917340
OL
816
817class Contrat(Contrat_):
a4125771 818 pass
6e4600ef 819
2d4d2fcf 820
821### ÉVÉNEMENTS
822
a4125771
OL
823#class Evenement_(AUFMetadata):
824# """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
825# d'un Dossier qui vient altérer des informations normales liées à un Dossier
826# (ex.: la Remuneration).
827#
828# Ex.: congé de maternité, maladie...
829#
830# Lors de ces situations exceptionnelles, l'Employe a un régime de travail
831# différent et une rémunération en conséquence. On souhaite toutefois
832# conserver le Dossier intact afin d'éviter une re-saisie des données lors
833# du retour à la normale.
834# """
835# dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
836# related_name='+')
837# nom = models.CharField(max_length=255)
838# date_debut = models.DateField(verbose_name = u"Date de début")
839# date_fin = models.DateField(verbose_name = u"Date de fin",
840# null=True, blank=True)
841#
842# class Meta:
843# abstract = True
844# ordering = ['nom']
845# verbose_name = u"Évènement"
846# verbose_name_plural = u"Évènements"
847#
848# def __unicode__(self):
849# return u'%s' % (self.nom)
850#
851#
852#class Evenement(Evenement_):
853# __doc__ = Evenement_.__doc__
854#
855#
856#class EvenementRemuneration_(RemunerationMixin):
857# """Structure de rémunération liée à un Evenement qui remplace
858# temporairement la Remuneration normale d'un Dossier, pour toute la durée
859# de l'Evenement.
860# """
861# evenement = models.ForeignKey("Evenement", db_column='evenement',
862# related_name='+',
863# verbose_name = u"Évènement")
864# # TODO : le champ dossier hérité de Remuneration doit être dérivé
865# # de l'Evenement associé
866#
867# class Meta:
868# abstract = True
869# ordering = ['evenement', 'type__nom', '-date_fin']
870# verbose_name = u"Évènement - rémunération"
871# verbose_name_plural = u"Évènements - rémunérations"
872#
873#
874#class EvenementRemuneration(EvenementRemuneration_):
875# __doc__ = EvenementRemuneration_.__doc__
876#
877# class Meta:
878# abstract = True
879#
880#
881#class EvenementRemuneration(EvenementRemuneration_):
882# __doc__ = EvenementRemuneration_.__doc__
865ca9e0 883# TODO? class ContratPiece(models.Model):
f31ddfa0 884
83b7692b 885
ca1a7b76 886### RÉFÉRENCES RH
83b7692b 887
d6985a3a 888class FamilleEmploi(AUFMetadata):
6e4600ef 889 """Catégorie utilisée dans la gestion des Postes.
890 Catégorie supérieure à TypePoste.
891 """
e9bbd6ba 892 nom = models.CharField(max_length=255)
ca1a7b76 893
8c1ae2b3 894 class Meta:
895 ordering = ['nom']
c1195471
OL
896 verbose_name = u"Famille d'emploi"
897 verbose_name_plural = u"Familles d'emploi"
ca1a7b76 898
6e4600ef 899 def __unicode__(self):
900 return u'%s' % (self.nom)
e9bbd6ba 901
d6985a3a 902class TypePoste(AUFMetadata):
6e4600ef 903 """Catégorie de Poste.
904 """
e9bbd6ba 905 nom = models.CharField(max_length=255)
8c1ae2b3 906 nom_feminin = models.CharField(max_length=255,
c1195471 907 verbose_name = u"Nom féminin")
ca1a7b76 908
8c1ae2b3 909 is_responsable = models.BooleanField(default=False,
c1195471 910 verbose_name = u"Poste de responsabilité")
ca1a7b76 911 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 912 db_column='famille_emploi',
8c1ae2b3 913 related_name='+',
c1195471 914 verbose_name = u"Famille d'emploi")
e9bbd6ba 915
6e4600ef 916 class Meta:
917 ordering = ['nom']
c1195471
OL
918 verbose_name = u"Type de poste"
919 verbose_name_plural = u"Types de poste"
ca1a7b76 920
e9bbd6ba 921 def __unicode__(self):
6e4600ef 922 return u'%s' % (self.nom)
e9bbd6ba 923
924
925TYPE_PAIEMENT_CHOICES = (
926 ('Régulier', 'Régulier'),
927 ('Ponctuel', 'Ponctuel'),
928)
929
930NATURE_REMUNERATION_CHOICES = (
931 ('Accessoire', 'Accessoire'),
932 ('Charges', 'Charges'),
933 ('Indemnité', 'Indemnité'),
7abc6d45 934 ('RAS', 'Rémunération autre source'),
e9bbd6ba 935 ('Traitement', 'Traitement'),
936)
937
d6985a3a 938class TypeRemuneration(AUFMetadata):
6e4600ef 939 """Catégorie de Remuneration.
940 """
e9bbd6ba 941 nom = models.CharField(max_length=255)
ca1a7b76 942 type_paiement = models.CharField(max_length=30,
8c1ae2b3 943 choices=TYPE_PAIEMENT_CHOICES,
c1195471 944 verbose_name = u"Type de paiement")
ca1a7b76 945 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 946 choices=NATURE_REMUNERATION_CHOICES,
c1195471 947 verbose_name = u"Nature de la rémunération")
ca1a7b76 948
8c1ae2b3 949 class Meta:
950 ordering = ['nom']
c1195471
OL
951 verbose_name = u"Type de rémunération"
952 verbose_name_plural = u"Types de rémunération"
9afaa55e 953
954 def __unicode__(self):
6e4600ef 955 return u'%s' % (self.nom)
ca1a7b76 956
d6985a3a 957class TypeRevalorisation(AUFMetadata):
7abc6d45 958 """Justification du changement de la Remuneration.
6e4600ef 959 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 960 """
e9bbd6ba 961 nom = models.CharField(max_length=255)
ca1a7b76 962
8c1ae2b3 963 class Meta:
964 ordering = ['nom']
c1195471
OL
965 verbose_name = u"Type de revalorisation"
966 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 967
968 def __unicode__(self):
6e4600ef 969 return u'%s' % (self.nom)
ca1a7b76 970
d6985a3a 971class Service(AUFMetadata):
6e4600ef 972 """Unité administrative où les Postes sont rattachés.
973 """
974 nom = models.CharField(max_length=255)
ca1a7b76 975
9afaa55e 976 class Meta:
977 ordering = ['nom']
c1195471
OL
978 verbose_name = u"Service"
979 verbose_name_plural = u"Services"
e9bbd6ba 980
6e4600ef 981 def __unicode__(self):
982 return u'%s' % (self.nom)
983
e9bbd6ba 984
985TYPE_ORGANISME_CHOICES = (
986 ('MAD', 'Mise à disposition'),
987 ('DET', 'Détachement'),
988)
989
d6985a3a 990class OrganismeBstg(AUFMetadata):
ca1a7b76 991 """Organisation d'où provient un Employe mis à disposition (MAD) de
6e4600ef 992 ou détaché (DET) à l'AUF à titre gratuit.
ca1a7b76 993
6e4600ef 994 (BSTG = bien et service à titre gratuit.)
995 """
e9bbd6ba 996 nom = models.CharField(max_length=255)
997 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
ca1a7b76 998 pays = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 999 db_column='pays',
1000 related_name='organismes_bstg',
1001 null=True, blank=True)
9afaa55e 1002
1003 class Meta:
1004 ordering = ['type', 'nom']
c1195471
OL
1005 verbose_name = u"Organisme BSTG"
1006 verbose_name_plural = u"Organismes BSTG"
9afaa55e 1007
6e4600ef 1008 def __unicode__(self):
8c1ae2b3 1009 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 1010
aff1a4c6
PP
1011 prefix_implantation = "pays__region"
1012 def get_regions(self):
1013 return [self.pays.region]
1014
1015
d6985a3a 1016class Statut(AUFMetadata):
6e4600ef 1017 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1018 """
9afaa55e 1019 # Identification
a122050d 1020 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 1021 nom = models.CharField(max_length=255)
e9bbd6ba 1022
6e4600ef 1023 class Meta:
1024 ordering = ['code']
c1195471
OL
1025 verbose_name = u"Statut d'employé"
1026 verbose_name_plural = u"Statuts d'employé"
ca1a7b76 1027
9afaa55e 1028 def __unicode__(self):
1029 return u'%s : %s' % (self.code, self.nom)
1030
83b7692b 1031
e9bbd6ba 1032TYPE_CLASSEMENT_CHOICES = (
6e4600ef 1033 ('S', 'S -Soutien'),
1034 ('T', 'T - Technicien'),
1035 ('P', 'P - Professionel'),
1036 ('C', 'C - Cadre'),
1037 ('D', 'D - Direction'),
1038 ('SO', 'SO - Sans objet [expatriés]'),
1039 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 1040)
83b7692b 1041
6e7c919b 1042
d6985a3a 1043class Classement_(AUFMetadata):
ca1a7b76 1044 """Éléments de classement de la
6e4600ef 1045 "Grille générique de classement hiérarchique".
ca1a7b76
EMS
1046
1047 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
6e4600ef 1048 classement dans la grille. Le classement donne le coefficient utilisé dans:
1049
1050 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1051 """
9afaa55e 1052 # Identification
e9bbd6ba 1053 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
ca1a7b76
EMS
1054 echelon = models.IntegerField(verbose_name=u"Échelon", blank=True, default=0)
1055 degre = models.IntegerField(verbose_name=u"Degré", blank=True, default=0)
1056 coefficient = models.FloatField(default=0, verbose_name=u"Coefficient",
8277a35b 1057 null=True)
9afaa55e 1058 # Méta
6e4600ef 1059 # annee # au lieu de date_debut et date_fin
1060 commentaire = models.TextField(null=True, blank=True)
ca1a7b76 1061
6e4600ef 1062 class Meta:
6e7c919b 1063 abstract = True
6e4600ef 1064 ordering = ['type','echelon','degre','coefficient']
c1195471
OL
1065 verbose_name = u"Classement"
1066 verbose_name_plural = u"Classements"
e9bbd6ba 1067
1068 def __unicode__(self):
1069 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
1070 self.coefficient)
1071
6e7c919b
NC
1072class Classement(Classement_):
1073 __doc__ = Classement_.__doc__
1074
1075
d6985a3a 1076class TauxChange_(AUFMetadata):
ca1a7b76 1077 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 1078 pour chaque année budgétaire.
7abc6d45 1079 """
9afaa55e 1080 # Identification
8d3e2fff 1081 devise = models.ForeignKey('Devise', db_column='devise')
c1195471
OL
1082 annee = models.IntegerField(verbose_name = u"Année")
1083 taux = models.FloatField(verbose_name = u"Taux vers l'euro")
6e7c919b 1084
6e4600ef 1085 class Meta:
6e7c919b 1086 abstract = True
8c1ae2b3 1087 ordering = ['-annee', 'devise__code']
c1195471
OL
1088 verbose_name = u"Taux de change"
1089 verbose_name_plural = u"Taux de change"
ca1a7b76 1090
6e4600ef 1091 def __unicode__(self):
8c1ae2b3 1092 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1093
6e7c919b
NC
1094
1095class TauxChange(TauxChange_):
1096 __doc__ = TauxChange_.__doc__
1097
701f3bea 1098class ValeurPointManager(NoDeleteManager):
105dd778 1099
701f3bea 1100 def get_query_set(self):
105dd778 1101 now = datetime.datetime.now()
701f3bea
OL
1102 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
1103
6e7c919b 1104
d6985a3a 1105class ValeurPoint_(AUFMetadata):
ca1a7b76
EMS
1106 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1107 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1108 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1109
1110 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1111 """
ca1a7b76 1112
09aa8374 1113 actuelles = ValeurPointManager()
701f3bea 1114
8277a35b
NC
1115 valeur = models.FloatField(null=True)
1116 devise = models.ForeignKey('Devise', db_column='devise', null=True,
6e4600ef 1117 related_name='+', default=5)
ca1a7b76 1118 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1119 db_column='implantation',
1120 related_name='%(app_label)s_valeur_point')
9afaa55e 1121 # Méta
e9bbd6ba 1122 annee = models.IntegerField()
9afaa55e 1123
6e4600ef 1124 class Meta:
701f3bea 1125 ordering = ['-annee', 'implantation__nom']
6e7c919b 1126 abstract = True
c1195471
OL
1127 verbose_name = u"Valeur du point"
1128 verbose_name_plural = u"Valeurs du point"
6e0bbb73 1129
9afaa55e 1130 def __unicode__(self):
105dd778 1131 return u'%s %s [%s]' % (self.devise, self.annee, self.implantation.nom_court)
6e7c919b
NC
1132
1133
1134class ValeurPoint(ValeurPoint_):
1135 __doc__ = ValeurPoint_.__doc__
1136
e9bbd6ba 1137
105dd778
OL
1138
1139class DeviseManager(NoDeleteManager):
1140
1141 def get_query_set(self):
1142 # exclure US et CAN
1143 return super(DeviseManager, self).get_query_set().exclude(id__in=(3, 15))
1144
d6985a3a 1145class Devise(AUFMetadata):
6e4600ef 1146 """Devise monétaire.
1147 """
105dd778
OL
1148 objects = DeviseManager()
1149
e9bbd6ba 1150 code = models.CharField(max_length=10, unique=True)
1151 nom = models.CharField(max_length=255)
1152
6e4600ef 1153 class Meta:
1154 ordering = ['code']
c1195471
OL
1155 verbose_name = u"Devise"
1156 verbose_name_plural = u"Devises"
ca1a7b76 1157
e9bbd6ba 1158 def __unicode__(self):
1159 return u'%s - %s' % (self.code, self.nom)
1160
d6985a3a 1161class TypeContrat(AUFMetadata):
6e4600ef 1162 """Type de contrat.
1163 """
e9bbd6ba 1164 nom = models.CharField(max_length=255)
6e4600ef 1165 nom_long = models.CharField(max_length=255)
49f9f116 1166
8c1ae2b3 1167 class Meta:
1168 ordering = ['nom']
c1195471
OL
1169 verbose_name = u"Type de contrat"
1170 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1171
9afaa55e 1172 def __unicode__(self):
1173 return u'%s' % (self.nom)
ca1a7b76
EMS
1174
1175
2d4d2fcf 1176### AUTRES
1177
d6985a3a 1178class ResponsableImplantation(AUFMetadata):
ca1a7b76 1179 """Le responsable d'une implantation.
30be56d5 1180 Anciennement géré sur le Dossier du responsable.
1181 """
ca1a7b76 1182 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 1183 related_name='+',
1184 null=True, blank=True)
ca1a7b76
EMS
1185 implantation = models.ForeignKey(ref.Implantation,
1186 db_column='implantation', related_name='+',
6e4600ef 1187 unique=True)
30be56d5 1188
1189 def __unicode__(self):
1190 return u'%s : %s' % (self.implantation, self.employe)
ca1a7b76 1191
30be56d5 1192 class Meta:
1193 ordering = ['implantation__nom']
8c1ae2b3 1194 verbose_name = "Responsable d'implantation"
1195 verbose_name_plural = "Responsables d'implantation"