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