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