Merge branch 'dev' of ssh://git.auf/auf_rh_dae into dev
[auf_rh_dae.git] / project / rh / models.py
CommitLineData
e9bbd6ba 1# -=- encoding: utf-8 -=-
2
a4125771 3import datetime
c267f20c 4from datetime import date
ca1a7b76 5from decimal import Decimal
c267f20c 6
4c53dda4 7from django.db.models import signals
83b7692b 8from django.core.files.storage import FileSystemStorage
49f9f116 9from django.db import models
d6985a3a 10from django.conf import settings
c267f20c 11
94cb1300 12from auf.django.emploi.models import GENRE_CHOICES, SITUATION_CHOICES # devrait plutot être dans references
d6985a3a
OL
13from auf.django.metadata.models import AUFMetadata
14from auf.django.metadata.managers import NoDeleteManager
bf6f2712 15import auf.django.references.models as ref
b4aeadf3 16from validators import validate_date_passee
16b1454e 17from managers import PosteManager, DossierManager, DossierComparaisonManager, PosteComparaisonManager
83b7692b 18
a4125771
OL
19
20
21# Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
22# Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
23def app_context():
24 import inspect;
25 models_stack = [s[1].split('/')[-2] for s in inspect.stack() if s[1].endswith('models.py')]
26 return models_stack[-1]
27
28
2d4d2fcf 29# Constantes
4047b783 30HELP_TEXT_DATE = "format: jj-mm-aaaa"
ca1a7b76
EMS
31REGIME_TRAVAIL_DEFAULT = Decimal('100.00')
32REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = Decimal('35.00')
2d4d2fcf 33
83b7692b 34# Upload de fichiers
ca1a7b76 35storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
83b7692b 36 base_url=settings.PRIVE_MEDIA_URL)
37
38def poste_piece_dispatch(instance, filename):
39 path = "poste/%s/%s" % (instance.poste_id, filename)
40 return path
41
42def dossier_piece_dispatch(instance, filename):
43 path = "dossier/%s/%s" % (instance.dossier_id, filename)
44 return path
45
5ea6b5bb 46def employe_piece_dispatch(instance, filename):
47 path = "employe/%s/%s" % (instance.employe_id, filename)
48 return path
49
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
09aa8374
OL
630 def get_salaire(self):
631 try:
632 return [r for r in self.remunerations().order_by('-date_debut') if r.type_id == 1][0]
633 except:
634 return None
3ebc0952 635
37868f0b
NC
636class Dossier(Dossier_):
637 __doc__ = Dossier_.__doc__
0b0545bd
OL
638 poste = models.ForeignKey('%s.Poste' % app_context(),
639 db_column='poste',
640 related_name='%(app_label)s_dossiers',
641 help_text=u"Taper le nom du poste ou du type de poste",
642 )
643 employe = models.ForeignKey('Employe', db_column='employe',
644 help_text=u"Taper le nom de l'employé",
16b1454e
OL
645 related_name='%(app_label)s_dossiers',
646 verbose_name=u"Employé")
37868f0b
NC
647
648
fc917340 649class DossierPiece_(models.Model):
7abc6d45 650 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
651 Ex.: Lettre de motivation.
652 """
a4125771 653 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
c1195471 654 nom = models.CharField(verbose_name = u"Nom", max_length=255)
ca1a7b76
EMS
655 fichier = models.FileField(verbose_name = u"Fichier",
656 upload_to=dossier_piece_dispatch,
83b7692b 657 storage=storage_prive)
658
6e4600ef 659 class Meta:
fc917340 660 abstract = True
6e4600ef 661 ordering = ['nom']
ca1a7b76 662
6e4600ef 663 def __unicode__(self):
664 return u'%s' % (self.nom)
665
fc917340 666class DossierPiece(DossierPiece_):
a4125771 667 pass
fc917340
OL
668
669class DossierCommentaire_(Commentaire):
a4125771 670 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='+')
fc917340
OL
671 class Meta:
672 abstract = True
673
674class DossierCommentaire(DossierCommentaire_):
a4125771 675 pass
fc917340
OL
676
677class DossierComparaison_(models.Model):
1d0f4eef
OL
678 """
679 Photo d'une comparaison salariale au moment de l'embauche.
680 """
a4125771 681 dossier = models.ForeignKey('%s.Dossier' % app_context(), related_name='%(app_label)s_comparaisons')
16b1454e
OL
682 objects = DossierComparaisonManager()
683
8c6269cc 684 implantation = models.ForeignKey(ref.Implantation, related_name="+", null=True, blank=True)
1d0f4eef
OL
685 poste = models.CharField(max_length=255, null=True, blank=True)
686 personne = models.CharField(max_length=255, null=True, blank=True)
687 montant = models.IntegerField(null=True)
93494cf1 688 devise = models.ForeignKey('Devise', default=5, related_name='+', null=True, blank=True)
1d0f4eef 689
fc917340
OL
690 class Meta:
691 abstract = True
692
1d0f4eef 693 def taux_devise(self):
a4125771
OL
694 annee = self.dossier.poste.date_debut.year
695 taux = [tc.taux for tc in TauxChange.objects.filter(devise=self.devise, annee=annee)]
696 taux = set(taux)
697 if len(taux) != 1:
698 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 699 else:
a4125771 700 return list(taux)[0]
1d0f4eef
OL
701
702 def montant_euros(self):
703 return round(float(self.montant) * float(self.taux_devise()), 2)
704
fc917340 705class DossierComparaison(DossierComparaison_):
a4125771 706 pass
2d4d2fcf 707
07b40eda 708### RÉMUNÉRATION
ca1a7b76 709
d6985a3a 710class RemunerationMixin(AUFMetadata):
a4125771 711 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_remunerations')
9afaa55e 712 # Identification
83b7692b 713 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 714 related_name='+',
c1195471 715 verbose_name = u"Type de rémunération")
ca1a7b76
EMS
716 type_revalorisation = models.ForeignKey('TypeRevalorisation',
717 db_column='type_revalorisation',
6e4600ef 718 related_name='+',
c1195471 719 verbose_name = u"Type de revalorisation",
7abc6d45 720 null=True, blank=True)
50fa9bc1 721 montant = models.FloatField(null=True, blank=True,
6e4600ef 722 default=0)
2d4d2fcf 723 # Annuel (12 mois, 52 semaines, 364 jours?)
a2c3ad52 724 devise = models.ForeignKey('Devise', db_column='devise', related_name='+',)
2d4d2fcf 725 # commentaire = precision
726 commentaire = models.CharField(max_length=255, null=True, blank=True)
727 # date_debut = anciennement date_effectif
a25e1d5c 728 date_debut = models.DateField(verbose_name = u"Date de début",
6e4600ef 729 null=True, blank=True)
a25e1d5c 730 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 731 null=True, blank=True)
ca1a7b76
EMS
732
733 class Meta:
2d4d2fcf 734 abstract = True
6e4600ef 735 ordering = ['type__nom', '-date_fin']
ca1a7b76 736
6e4600ef 737 def __unicode__(self):
738 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
ca1a7b76 739
6e7c919b 740class Remuneration_(RemunerationMixin):
2d4d2fcf 741 """Structure de rémunération (données budgétaires) en situation normale
ca1a7b76
EMS
742 pour un Dossier. Si un Evenement existe, utiliser la structure de
743 rémunération EvenementRemuneration de cet événement.
2d4d2fcf 744 """
83b7692b 745
746 def montant_mois(self):
747 return round(self.montant / 12, 2)
748
749 def taux_devise(self):
a4125771
OL
750 if self.devise.code == "EUR":
751 return 1
752
753 annee = datetime.datetime.now().year
754 if self.date_debut is not None:
755 annee = self.date_debut.year
756 if self.dossier.poste.date_debut is not None:
8d72cd59 757 annee = self.dossier.poste.date_debut.year
a4125771
OL
758
759 taux = [tc.taux for tc in TauxChange.objects.filter(devise=self.devise_id, annee=annee)]
760 taux = set(taux)
761 if len(taux) != 1:
762 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))
763 else:
764 return list(taux)[0]
83b7692b 765
766 def montant_euro(self):
a4125771 767 return round(float(self.montant) * float(self.taux_devise()), 2)
83b7692b 768
769 def montant_euro_mois(self):
770 return round(self.montant_euro() / 12, 2)
ca1a7b76 771
9afaa55e 772 def __unicode__(self):
773 try:
774 devise = self.devise.code
775 except:
776 devise = "???"
777 return "%s %s" % (self.montant, devise)
83b7692b 778
6e7c919b
NC
779 class Meta:
780 abstract = True
c1195471
OL
781 verbose_name = u"Rémunération"
782 verbose_name_plural = u"Rémunérations"
6e7c919b
NC
783
784
785class Remuneration(Remuneration_):
a4125771 786 pass
6e7c919b 787
2d4d2fcf 788
789### CONTRATS
c41b7fcc
OL
790
791class ContratManager(NoDeleteManager):
792 def get_query_set(self):
793 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
794
2d4d2fcf 795
fc917340 796class Contrat_(AUFMetadata):
2d4d2fcf 797 """Document juridique qui encadre la relation de travail d'un Employe
ca1a7b76 798 pour un Poste particulier. Pour un Dossier (qui documente cette
2d4d2fcf 799 relation de travail) plusieurs contrats peuvent être associés.
800 """
c41b7fcc 801 objects = ContratManager()
865ca9e0 802 dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier', related_name='%(app_label)s_contrats')
6e4600ef 803 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 804 related_name='+',
c1195471 805 verbose_name = u"Type de contrat")
a25e1d5c
OL
806 date_debut = models.DateField(verbose_name = u"Date de début")
807 date_fin = models.DateField(verbose_name = u"Date de fin",
6e4600ef 808 null=True, blank=True)
4ba73558
OL
809 fichier = models.FileField(verbose_name = u"Fichier",
810 upload_to=contrat_dispatch,
811 storage=storage_prive,
812 null=True, blank=True)
6e4600ef 813
814 class Meta:
fc917340 815 abstract = True
a2c3ad52 816 ordering = ['dossier__employe__nom']
c1195471
OL
817 verbose_name = u"Contrat"
818 verbose_name_plural = u"Contrats"
ca1a7b76 819
6e4600ef 820 def __unicode__(self):
8c1ae2b3 821 return u'%s - %s' % (self.dossier, self.id)
fc917340
OL
822
823class Contrat(Contrat_):
a4125771 824 pass
6e4600ef 825
2d4d2fcf 826
827### ÉVÉNEMENTS
828
a4125771
OL
829#class Evenement_(AUFMetadata):
830# """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
831# d'un Dossier qui vient altérer des informations normales liées à un Dossier
832# (ex.: la Remuneration).
833#
834# Ex.: congé de maternité, maladie...
835#
836# Lors de ces situations exceptionnelles, l'Employe a un régime de travail
837# différent et une rémunération en conséquence. On souhaite toutefois
838# conserver le Dossier intact afin d'éviter une re-saisie des données lors
839# du retour à la normale.
840# """
841# dossier = models.ForeignKey('%s.Dossier' % app_context(), db_column='dossier',
842# related_name='+')
843# nom = models.CharField(max_length=255)
844# date_debut = models.DateField(verbose_name = u"Date de début")
845# date_fin = models.DateField(verbose_name = u"Date de fin",
846# null=True, blank=True)
847#
848# class Meta:
849# abstract = True
850# ordering = ['nom']
851# verbose_name = u"Évènement"
852# verbose_name_plural = u"Évènements"
853#
854# def __unicode__(self):
855# return u'%s' % (self.nom)
856#
857#
858#class Evenement(Evenement_):
859# __doc__ = Evenement_.__doc__
860#
861#
862#class EvenementRemuneration_(RemunerationMixin):
863# """Structure de rémunération liée à un Evenement qui remplace
864# temporairement la Remuneration normale d'un Dossier, pour toute la durée
865# de l'Evenement.
866# """
867# evenement = models.ForeignKey("Evenement", db_column='evenement',
868# related_name='+',
869# verbose_name = u"Évènement")
870# # TODO : le champ dossier hérité de Remuneration doit être dérivé
871# # de l'Evenement associé
872#
873# class Meta:
874# abstract = True
875# ordering = ['evenement', 'type__nom', '-date_fin']
876# verbose_name = u"Évènement - rémunération"
877# verbose_name_plural = u"Évènements - rémunérations"
878#
879#
880#class EvenementRemuneration(EvenementRemuneration_):
881# __doc__ = EvenementRemuneration_.__doc__
882#
883# class Meta:
884# abstract = True
885#
886#
887#class EvenementRemuneration(EvenementRemuneration_):
888# __doc__ = EvenementRemuneration_.__doc__
865ca9e0 889# TODO? class ContratPiece(models.Model):
f31ddfa0 890
83b7692b 891
ca1a7b76 892### RÉFÉRENCES RH
83b7692b 893
d6985a3a 894class FamilleEmploi(AUFMetadata):
6e4600ef 895 """Catégorie utilisée dans la gestion des Postes.
896 Catégorie supérieure à TypePoste.
897 """
e9bbd6ba 898 nom = models.CharField(max_length=255)
ca1a7b76 899
8c1ae2b3 900 class Meta:
901 ordering = ['nom']
c1195471
OL
902 verbose_name = u"Famille d'emploi"
903 verbose_name_plural = u"Familles d'emploi"
ca1a7b76 904
6e4600ef 905 def __unicode__(self):
906 return u'%s' % (self.nom)
e9bbd6ba 907
d6985a3a 908class TypePoste(AUFMetadata):
6e4600ef 909 """Catégorie de Poste.
910 """
e9bbd6ba 911 nom = models.CharField(max_length=255)
8c1ae2b3 912 nom_feminin = models.CharField(max_length=255,
c1195471 913 verbose_name = u"Nom féminin")
ca1a7b76 914
8c1ae2b3 915 is_responsable = models.BooleanField(default=False,
c1195471 916 verbose_name = u"Poste de responsabilité")
ca1a7b76 917 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 918 db_column='famille_emploi',
8c1ae2b3 919 related_name='+',
c1195471 920 verbose_name = u"Famille d'emploi")
e9bbd6ba 921
6e4600ef 922 class Meta:
923 ordering = ['nom']
c1195471
OL
924 verbose_name = u"Type de poste"
925 verbose_name_plural = u"Types de poste"
ca1a7b76 926
e9bbd6ba 927 def __unicode__(self):
6e4600ef 928 return u'%s' % (self.nom)
e9bbd6ba 929
930
931TYPE_PAIEMENT_CHOICES = (
a3e3bde0
JPC
932 (u'Régulier', u'Régulier'),
933 (u'Ponctuel', u'Ponctuel'),
e9bbd6ba 934)
935
936NATURE_REMUNERATION_CHOICES = (
a3e3bde0
JPC
937 (u'Accessoire', u'Accessoire'),
938 (u'Charges', u'Charges'),
939 (u'Indemnité', u'Indemnité'),
940 (u'RAS', u'Rémunération autre source'),
941 (u'Traitement', u'Traitement'),
e9bbd6ba 942)
943
d6985a3a 944class TypeRemuneration(AUFMetadata):
6e4600ef 945 """Catégorie de Remuneration.
946 """
e9bbd6ba 947 nom = models.CharField(max_length=255)
ca1a7b76 948 type_paiement = models.CharField(max_length=30,
8c1ae2b3 949 choices=TYPE_PAIEMENT_CHOICES,
c1195471 950 verbose_name = u"Type de paiement")
ca1a7b76 951 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 952 choices=NATURE_REMUNERATION_CHOICES,
c1195471 953 verbose_name = u"Nature de la rémunération")
ca1a7b76 954
8c1ae2b3 955 class Meta:
956 ordering = ['nom']
c1195471
OL
957 verbose_name = u"Type de rémunération"
958 verbose_name_plural = u"Types de rémunération"
9afaa55e 959
960 def __unicode__(self):
6e4600ef 961 return u'%s' % (self.nom)
ca1a7b76 962
d6985a3a 963class TypeRevalorisation(AUFMetadata):
7abc6d45 964 """Justification du changement de la Remuneration.
6e4600ef 965 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 966 """
e9bbd6ba 967 nom = models.CharField(max_length=255)
ca1a7b76 968
8c1ae2b3 969 class Meta:
970 ordering = ['nom']
c1195471
OL
971 verbose_name = u"Type de revalorisation"
972 verbose_name_plural = u"Types de revalorisation"
e9bbd6ba 973
974 def __unicode__(self):
6e4600ef 975 return u'%s' % (self.nom)
ca1a7b76 976
d6985a3a 977class Service(AUFMetadata):
6e4600ef 978 """Unité administrative où les Postes sont rattachés.
979 """
980 nom = models.CharField(max_length=255)
ca1a7b76 981
9afaa55e 982 class Meta:
983 ordering = ['nom']
c1195471
OL
984 verbose_name = u"Service"
985 verbose_name_plural = u"Services"
e9bbd6ba 986
6e4600ef 987 def __unicode__(self):
988 return u'%s' % (self.nom)
989
e9bbd6ba 990
991TYPE_ORGANISME_CHOICES = (
992 ('MAD', 'Mise à disposition'),
993 ('DET', 'Détachement'),
994)
995
d6985a3a 996class OrganismeBstg(AUFMetadata):
ca1a7b76 997 """Organisation d'où provient un Employe mis à disposition (MAD) de
6e4600ef 998 ou détaché (DET) à l'AUF à titre gratuit.
ca1a7b76 999
6e4600ef 1000 (BSTG = bien et service à titre gratuit.)
1001 """
e9bbd6ba 1002 nom = models.CharField(max_length=255)
1003 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
ca1a7b76 1004 pays = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 1005 db_column='pays',
1006 related_name='organismes_bstg',
1007 null=True, blank=True)
9afaa55e 1008
1009 class Meta:
1010 ordering = ['type', 'nom']
c1195471
OL
1011 verbose_name = u"Organisme BSTG"
1012 verbose_name_plural = u"Organismes BSTG"
9afaa55e 1013
6e4600ef 1014 def __unicode__(self):
8c1ae2b3 1015 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 1016
aff1a4c6
PP
1017 prefix_implantation = "pays__region"
1018 def get_regions(self):
1019 return [self.pays.region]
1020
1021
d6985a3a 1022class Statut(AUFMetadata):
6e4600ef 1023 """Statut de l'Employe dans le cadre d'un Dossier particulier.
1024 """
9afaa55e 1025 # Identification
a122050d 1026 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 1027 nom = models.CharField(max_length=255)
e9bbd6ba 1028
6e4600ef 1029 class Meta:
1030 ordering = ['code']
c1195471
OL
1031 verbose_name = u"Statut d'employé"
1032 verbose_name_plural = u"Statuts d'employé"
ca1a7b76 1033
9afaa55e 1034 def __unicode__(self):
1035 return u'%s : %s' % (self.code, self.nom)
1036
83b7692b 1037
e9bbd6ba 1038TYPE_CLASSEMENT_CHOICES = (
6e4600ef 1039 ('S', 'S -Soutien'),
1040 ('T', 'T - Technicien'),
1041 ('P', 'P - Professionel'),
1042 ('C', 'C - Cadre'),
1043 ('D', 'D - Direction'),
1044 ('SO', 'SO - Sans objet [expatriés]'),
1045 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 1046)
83b7692b 1047
6e7c919b 1048
d6985a3a 1049class Classement_(AUFMetadata):
ca1a7b76 1050 """Éléments de classement de la
6e4600ef 1051 "Grille générique de classement hiérarchique".
ca1a7b76
EMS
1052
1053 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
6e4600ef 1054 classement dans la grille. Le classement donne le coefficient utilisé dans:
1055
1056 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1057 """
9afaa55e 1058 # Identification
e9bbd6ba 1059 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
ca1a7b76
EMS
1060 echelon = models.IntegerField(verbose_name=u"Échelon", blank=True, default=0)
1061 degre = models.IntegerField(verbose_name=u"Degré", blank=True, default=0)
1062 coefficient = models.FloatField(default=0, verbose_name=u"Coefficient",
8277a35b 1063 null=True)
9afaa55e 1064 # Méta
6e4600ef 1065 # annee # au lieu de date_debut et date_fin
1066 commentaire = models.TextField(null=True, blank=True)
ca1a7b76 1067
6e4600ef 1068 class Meta:
6e7c919b 1069 abstract = True
6e4600ef 1070 ordering = ['type','echelon','degre','coefficient']
c1195471
OL
1071 verbose_name = u"Classement"
1072 verbose_name_plural = u"Classements"
e9bbd6ba 1073
1074 def __unicode__(self):
1075 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
1076 self.coefficient)
1077
6e7c919b
NC
1078class Classement(Classement_):
1079 __doc__ = Classement_.__doc__
1080
1081
d6985a3a 1082class TauxChange_(AUFMetadata):
ca1a7b76 1083 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 1084 pour chaque année budgétaire.
7abc6d45 1085 """
9afaa55e 1086 # Identification
8d3e2fff 1087 devise = models.ForeignKey('Devise', db_column='devise')
c1195471
OL
1088 annee = models.IntegerField(verbose_name = u"Année")
1089 taux = models.FloatField(verbose_name = u"Taux vers l'euro")
6e7c919b 1090
6e4600ef 1091 class Meta:
6e7c919b 1092 abstract = True
8c1ae2b3 1093 ordering = ['-annee', 'devise__code']
c1195471
OL
1094 verbose_name = u"Taux de change"
1095 verbose_name_plural = u"Taux de change"
ca1a7b76 1096
6e4600ef 1097 def __unicode__(self):
8c1ae2b3 1098 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 1099
6e7c919b
NC
1100
1101class TauxChange(TauxChange_):
1102 __doc__ = TauxChange_.__doc__
1103
701f3bea 1104class ValeurPointManager(NoDeleteManager):
105dd778 1105
701f3bea 1106 def get_query_set(self):
105dd778 1107 now = datetime.datetime.now()
701f3bea
OL
1108 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
1109
6e7c919b 1110
d6985a3a 1111class ValeurPoint_(AUFMetadata):
ca1a7b76
EMS
1112 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
1113 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 1114 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 1115
1116 salaire de base = coefficient * valeur du point de l'Implantation du Poste
1117 """
ca1a7b76 1118
09aa8374 1119 actuelles = ValeurPointManager()
701f3bea 1120
8277a35b
NC
1121 valeur = models.FloatField(null=True)
1122 devise = models.ForeignKey('Devise', db_column='devise', null=True,
6e4600ef 1123 related_name='+', default=5)
ca1a7b76 1124 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
1125 db_column='implantation',
1126 related_name='%(app_label)s_valeur_point')
9afaa55e 1127 # Méta
e9bbd6ba 1128 annee = models.IntegerField()
9afaa55e 1129
6e4600ef 1130 class Meta:
701f3bea 1131 ordering = ['-annee', 'implantation__nom']
6e7c919b 1132 abstract = True
c1195471
OL
1133 verbose_name = u"Valeur du point"
1134 verbose_name_plural = u"Valeurs du point"
6e0bbb73 1135
9afaa55e 1136 def __unicode__(self):
24eb08a3 1137 return u'%s %s %s [%s] %s' % (self.devise.code, self.annee, self.valeur, self.implantation.nom_court, self.devise.nom)
6e7c919b
NC
1138
1139
1140class ValeurPoint(ValeurPoint_):
1141 __doc__ = ValeurPoint_.__doc__
1142
e9bbd6ba 1143
105dd778 1144
d6985a3a 1145class Devise(AUFMetadata):
6e4600ef 1146 """Devise monétaire.
1147 """
e9bbd6ba 1148 code = models.CharField(max_length=10, unique=True)
1149 nom = models.CharField(max_length=255)
1150
6e4600ef 1151 class Meta:
1152 ordering = ['code']
c1195471
OL
1153 verbose_name = u"Devise"
1154 verbose_name_plural = u"Devises"
ca1a7b76 1155
e9bbd6ba 1156 def __unicode__(self):
1157 return u'%s - %s' % (self.code, self.nom)
1158
d6985a3a 1159class TypeContrat(AUFMetadata):
6e4600ef 1160 """Type de contrat.
1161 """
e9bbd6ba 1162 nom = models.CharField(max_length=255)
6e4600ef 1163 nom_long = models.CharField(max_length=255)
49f9f116 1164
8c1ae2b3 1165 class Meta:
1166 ordering = ['nom']
c1195471
OL
1167 verbose_name = u"Type de contrat"
1168 verbose_name_plural = u"Types de contrat"
8c1ae2b3 1169
9afaa55e 1170 def __unicode__(self):
1171 return u'%s' % (self.nom)
ca1a7b76
EMS
1172
1173
2d4d2fcf 1174### AUTRES
1175
d6985a3a 1176class ResponsableImplantation(AUFMetadata):
ca1a7b76 1177 """Le responsable d'une implantation.
30be56d5 1178 Anciennement géré sur le Dossier du responsable.
1179 """
ca1a7b76 1180 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 1181 related_name='+',
1182 null=True, blank=True)
ca1a7b76
EMS
1183 implantation = models.ForeignKey(ref.Implantation,
1184 db_column='implantation', related_name='+',
6e4600ef 1185 unique=True)
30be56d5 1186
1187 def __unicode__(self):
1188 return u'%s : %s' % (self.implantation, self.employe)
ca1a7b76 1189
30be56d5 1190 class Meta:
1191 ordering = ['implantation__nom']
8c1ae2b3 1192 verbose_name = "Responsable d'implantation"
1193 verbose_name_plural = "Responsables d'implantation"
4c53dda4 1194