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