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