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