distinct
[auf_rh_dae.git] / project / rh / models.py
CommitLineData
e9bbd6ba 1# -=- encoding: utf-8 -=-
2
a4125771 3import datetime
c267f20c 4from datetime import date
ca1a7b76 5from decimal import Decimal
c267f20c 6
4c53dda4 7from django.db.models import signals
83b7692b 8from django.core.files.storage import FileSystemStorage
49f9f116 9from django.db import models
d6985a3a 10from django.conf import settings
c267f20c 11
94cb1300 12from auf.django.emploi.models import GENRE_CHOICES, SITUATION_CHOICES # devrait plutot être dans references
d6985a3a
OL
13from auf.django.metadata.models import AUFMetadata
14from auf.django.metadata.managers import NoDeleteManager
bf6f2712 15import auf.django.references.models as ref
b4aeadf3 16from validators import validate_date_passee
16b1454e 17from managers import PosteManager, DossierManager, DossierComparaisonManager, PosteComparaisonManager
83b7692b 18
a4125771 19
a4125771
OL
20# Gruick hack pour déterminer d'ou provient l'instanciation d'une classe pour l'héritage.
21# Cela permet de faire du dynamic loading par app sans avoir à redéfinir dans DAE la FK
22def app_context():
23 import inspect;
24 models_stack = [s[1].split('/')[-2] for s in inspect.stack() if s[1].endswith('models.py')]
25 return models_stack[-1]
26
27
2d4d2fcf 28# Constantes
4047b783 29HELP_TEXT_DATE = "format: jj-mm-aaaa"
ca1a7b76
EMS
30REGIME_TRAVAIL_DEFAULT = Decimal('100.00')
31REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = Decimal('35.00')
2d4d2fcf 32
83b7692b 33# Upload de fichiers
ca1a7b76 34storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
83b7692b 35 base_url=settings.PRIVE_MEDIA_URL)
36
37def poste_piece_dispatch(instance, filename):
38 path = "poste/%s/%s" % (instance.poste_id, filename)
39 return path
40
41def dossier_piece_dispatch(instance, filename):
42 path = "dossier/%s/%s" % (instance.dossier_id, filename)
43 return path
44
5ea6b5bb 45def employe_piece_dispatch(instance, filename):
46 path = "employe/%s/%s" % (instance.employe_id, filename)
47 return path
48
4ba73558
OL
49def contrat_dispatch(instance, filename):
50 path = "contrat/%s/%s" % (instance.dossier_id, filename)
51 return path
52
5f5a4f06 53
d6985a3a 54class Commentaire(AUFMetadata):
2d4d2fcf 55 texte = models.TextField()
098043b6 56 owner = models.ForeignKey('auth.User', db_column='owner', related_name='+', verbose_name=u"Commentaire de")
ca1a7b76 57
2d4d2fcf 58 class Meta:
59 abstract = True
6e4600ef 60 ordering = ['-date_creation']
ca1a7b76 61
6e4600ef 62 def __unicode__(self):
63 return u'%s' % (self.texte)
07b40eda 64
83b7692b 65
66### POSTE
67
68POSTE_APPEL_CHOICES = (
69 ('interne', 'Interne'),
70 ('externe', 'Externe'),
71)
72
d6985a3a 73class Poste_(AUFMetadata):
ca1a7b76 74 """Un Poste est un emploi (job) à combler dans une implantation.
6e4600ef 75 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
76 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
77 """
1f2979b8
OL
78
79 objects = PosteManager()
80
83b7692b 81 # Identification
ca1a7b76 82 nom = models.CharField(max_length=255,
c1195471 83 verbose_name = u"Titre du poste", )
2d4d2fcf 84 nom_feminin = models.CharField(max_length=255,
c1195471 85 verbose_name = u"Titre du poste (au féminin)",
6e4600ef 86 null=True)
105dd778 87 implantation = models.ForeignKey(ref.Implantation, help_text=u"Taper le nom de l'implantation ou sa région",
6e4600ef 88 db_column='implantation', related_name='+')
105dd778 89 type_poste = models.ForeignKey('TypePoste', db_column='type_poste', help_text=u"Taper le nom du type de poste",
6e4600ef 90 related_name='+',
91 null=True)
ca1a7b76 92 service = models.ForeignKey('Service', db_column='service',
6e4600ef 93 related_name='+',
3f5cbabe 94 verbose_name = u"Direction/Service/Pôle support", )
6e4600ef 95 responsable = models.ForeignKey('Poste', db_column='responsable',
105dd778 96 related_name='+', null=True,help_text=u"Taper le nom du poste ou du type de poste",
3f5cbabe 97 verbose_name = u"Poste du responsable", )
83b7692b 98
99 # Contrat
100 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 101 default=REGIME_TRAVAIL_DEFAULT, null=True,
ca1a7b76 102 verbose_name = u"Temps de travail",
8c1ae2b3 103 help_text="% du temps complet")
83b7692b 104 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
8277a35b 105 decimal_places=2, null=True,
2d4d2fcf 106 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
c1195471 107 verbose_name = u"Nb. heures par semaine")
83b7692b 108
109 # Recrutement
ca1a7b76 110 local = models.NullBooleanField(verbose_name = u"Local", default=True,
8277a35b 111 null=True, blank=True)
ca1a7b76 112 expatrie = models.NullBooleanField(verbose_name = u"Expatrié", default=False,
8277a35b
NC
113 null=True, blank=True)
114 mise_a_disposition = models.NullBooleanField(
c1195471 115 verbose_name = u"Mise à disposition",
8277a35b
NC
116 null=True, default=False)
117 appel = models.CharField(max_length=10, null=True,
c1195471 118 verbose_name = u"Appel à candidature",
6e4600ef 119 choices=POSTE_APPEL_CHOICES,
120 default='interne')
83b7692b 121
122 # Rémunération
ca1a7b76 123 classement_min = models.ForeignKey('Classement',
6e4600ef 124 db_column='classement_min', related_name='+',
125 null=True, blank=True)
ca1a7b76 126 classement_max = models.ForeignKey('Classement',
6e4600ef 127 db_column='classement_max', related_name='+',
128 null=True, blank=True)
105dd778 129 valeur_point_min = models.ForeignKey('ValeurPoint', help_text=u"Taper le code ou le nom de l'implantation",
ca1a7b76 130 db_column='valeur_point_min', related_name='+',
6e4600ef 131 null=True, blank=True)
105dd778 132 valeur_point_max = models.ForeignKey('ValeurPoint', help_text=u"Taper le code ou le nom de l'implantation",
ca1a7b76 133 db_column='valeur_point_max', related_name='+',
6e4600ef 134 null=True, blank=True)
8277a35b 135 devise_min = models.ForeignKey('Devise', db_column='devise_min', null=True,
6e4600ef 136 related_name='+', default=5)
8277a35b 137 devise_max = models.ForeignKey('Devise', db_column='devise_max', null=True,
6e4600ef 138 related_name='+', default=5)
83b7692b 139 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 140 null=True, default=0)
83b7692b 141 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 142 null=True, default=0)
83b7692b 143 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 144 null=True, default=0)
83b7692b 145 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 146 null=True, default=0)
83b7692b 147 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 148 null=True, default=0)
83b7692b 149 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
8277a35b 150 null=True, default=0)
83b7692b 151
152 # Comparatifs de rémunération
8277a35b 153 devise_comparaison = models.ForeignKey('Devise', null=True,
ca1a7b76 154 db_column='devise_comparaison',
6e4600ef 155 related_name='+',
156 default=5)
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
208 vacant = models.NullBooleanField(verbose_name = u"Vacant", null=True, blank=True)
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
OL
287 montant = models.IntegerField(null=True)
288 devise = models.ForeignKey("Devise", default=5, 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
OL
369 # meta dématérialisation : pour permettre le filtrage
370 nb_postes = models.IntegerField(verbose_name = u"Nombre de postes", null=True, blank=True)
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 ??
8277a35b
NC
554 statut = models.ForeignKey('Statut', related_name='+', default=3,
555 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)
93494cf1 689 devise = models.ForeignKey('Devise', default=5, 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='+',
c1195471 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