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