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