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