cleanup
[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
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 1092class ValeurPointManager(NoDeleteManager):
105dd778 1093
701f3bea 1094 def get_query_set(self):
105dd778 1095 now = datetime.datetime.now()
701f3bea
OL
1096 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
1097
6e7c919b 1098
d6985a3a 1099class ValeurPoint_(AUFMetadata):
ca1a7b76
EMS
1100 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1101 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1102 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1103
1104 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1105 """
ca1a7b76 1106
09aa8374 1107 actuelles = ValeurPointManager()
701f3bea 1108
8277a35b
NC
1109 valeur = models.FloatField(null=True)
1110 devise = models.ForeignKey('Devise', db_column='devise', null=True,
6e4600ef 1111 related_name='+', default=5)
ca1a7b76 1112 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1113 db_column='implantation',
1114 related_name='%(app_label)s_valeur_point')
9afaa55e 1115 # Méta
e9bbd6ba 1116 annee = models.IntegerField()
9afaa55e 1117
6e4600ef 1118 class Meta:
701f3bea 1119 ordering = ['-annee', 'implantation__nom']
6e7c919b 1120 abstract = True
c1195471
OL
1121 verbose_name = u"Valeur du point"
1122 verbose_name_plural = u"Valeurs du point"
6e0bbb73 1123
9afaa55e 1124 def __unicode__(self):
105dd778 1125 return u'%s %s [%s]' % (self.devise, self.annee, self.implantation.nom_court)
6e7c919b
NC
1126
1127
1128class ValeurPoint(ValeurPoint_):
1129 __doc__ = ValeurPoint_.__doc__
1130
e9bbd6ba 1131
105dd778
OL
1132
1133class DeviseManager(NoDeleteManager):
1134
1135 def get_query_set(self):
1136 # exclure US et CAN
1137 return super(DeviseManager, self).get_query_set().exclude(id__in=(3, 15))
1138
d6985a3a 1139class Devise(AUFMetadata):
6e4600ef 1140 """Devise monétaire.
1141 """
105dd778
OL
1142 objects = DeviseManager()
1143
e9bbd6ba 1144 code = models.CharField(max_length=10, unique=True)
1145 nom = models.CharField(max_length=255)
1146
6e4600ef 1147 class Meta:
1148 ordering = ['code']
c1195471
OL
1149 verbose_name = u"Devise"
1150 verbose_name_plural = u"Devises"
ca1a7b76 1151
e9bbd6ba 1152 def __unicode__(self):
1153 return u'%s - %s' % (self.code, self.nom)
1154
d6985a3a 1155class TypeContrat(AUFMetadata):
6e4600ef 1156 """Type de contrat.
1157 """
e9bbd6ba 1158 nom = models.CharField(max_length=255)
6e4600ef 1159 nom_long = models.CharField(max_length=255)
49f9f116 1160
8c1ae2b3 1161 class Meta:
1162 ordering = ['nom']
c1195471
OL
1163 verbose_name = u"Type de contrat"
1164 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1165
9afaa55e 1166 def __unicode__(self):
1167 return u'%s' % (self.nom)
ca1a7b76
EMS
1168
1169
2d4d2fcf 1170### AUTRES
1171
d6985a3a 1172class ResponsableImplantation(AUFMetadata):
ca1a7b76 1173 """Le responsable d'une implantation.
30be56d5 1174 Anciennement géré sur le Dossier du responsable.
1175 """
ca1a7b76 1176 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 1177 related_name='+',
1178 null=True, blank=True)
ca1a7b76
EMS
1179 implantation = models.ForeignKey(ref.Implantation,
1180 db_column='implantation', related_name='+',
6e4600ef 1181 unique=True)
30be56d5 1182
1183 def __unicode__(self):
1184 return u'%s : %s' % (self.implantation, self.employe)
ca1a7b76 1185
30be56d5 1186 class Meta:
1187 ordering = ['implantation__nom']
8c1ae2b3 1188 verbose_name = "Responsable d'implantation"
1189 verbose_name_plural = "Responsables d'implantation"