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