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