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