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