Merge branch 'dev' of ssh://git.auf/auf_rh_dae into dev
[auf_rh_dae.git] / project / rh / models.py
CommitLineData
e9bbd6ba 1# -=- encoding: utf-8 -=-
2
9afaa55e 3import datetime
83b7692b 4
5from django.core.files.storage import FileSystemStorage
49f9f116 6from django.db import models
83b7692b 7import settings
8
9import datamaster_modeles.models as ref
10
11
2d4d2fcf 12# Constantes
6e4600ef 13HELP_TEXT_DATE = u"format: aaaa-mm-jj"
14REGIME_TRAVAIL_DEFAULT = 100.00
15REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT = 35.00
2d4d2fcf 16
17
83b7692b 18# Upload de fichiers
19storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT,
20 base_url=settings.PRIVE_MEDIA_URL)
21
22def poste_piece_dispatch(instance, filename):
23 path = "poste/%s/%s" % (instance.poste_id, filename)
24 return path
25
26def dossier_piece_dispatch(instance, filename):
27 path = "dossier/%s/%s" % (instance.dossier_id, filename)
28 return path
29
2d4d2fcf 30# Abstracts
31class Metadata(models.Model):
32 """Méta-données AUF.
6e4600ef 33 Metadata.actif = flag remplaçant la suppression.
34 actif == False : objet réputé supprimé.
2d4d2fcf 35 """
36 actif = models.BooleanField(default=True)
37 date_creation = models.DateField(auto_now_add=True)
6e4600ef 38 user_creation = models.ForeignKey('auth.User',
39 db_column='user_creation', related_name='+',
50fa9bc1 40 null=True, blank=True)
07b40eda 41 date_modification = models.DateField(auto_now=True)
6e4600ef 42 user_modification = models.ForeignKey('auth.User',
43 db_column='user_modification', related_name='+',
50fa9bc1
OL
44 null=True, blank=True)
45 date_desactivation = models.DateField(null=True, blank=True)
6e4600ef 46 user_desactivation = models.ForeignKey('auth.User',
47 db_column='user_desactivation', related_name='+',
50fa9bc1 48 null=True, blank=True)
07b40eda 49
50 class Meta:
51 abstract = True
52
2d4d2fcf 53class Commentaire(Metadata):
54 texte = models.TextField()
6e4600ef 55 owner = models.ForeignKey('auth.User', db_column='owner', related_name='+')
2d4d2fcf 56
57 class Meta:
58 abstract = True
6e4600ef 59 ordering = ['-date_creation']
60
61 def __unicode__(self):
62 return u'%s' % (self.texte)
07b40eda 63
83b7692b 64
65### POSTE
66
67POSTE_APPEL_CHOICES = (
68 ('interne', 'Interne'),
69 ('externe', 'Externe'),
70)
71
37868f0b 72class Poste_(Metadata):
6e4600ef 73 """Un Poste est un emploi (job) à combler dans une implantation.
74 Un Poste peut être comblé par un Employe, auquel cas un Dossier est créé.
75 Si on veut recruter 2 jardiniers, 2 Postes distincts existent.
76 """
83b7692b 77 # Identification
2d4d2fcf 78 nom = models.CharField(max_length=255,
6e4600ef 79 verbose_name=u"Titre du poste", )
2d4d2fcf 80 nom_feminin = models.CharField(max_length=255,
50fa9bc1 81 verbose_name=u"Titre du poste (au féminin)",
6e4600ef 82 null=True)
83 implantation = models.ForeignKey(ref.Implantation,
84 db_column='implantation', related_name='+')
85 type_poste = models.ForeignKey('TypePoste', db_column='type_poste',
86 related_name='+',
87 null=True)
88 service = models.ForeignKey('Service', db_column='service',
89 related_name='+',
90 verbose_name=u"Direction/Service/Pôle support",
91 default=1) # default = Rectorat
92 responsable = models.ForeignKey('Poste', db_column='responsable',
93 related_name='+',
94 verbose_name=u"Poste du responsable",
95 default=149) # default = Recteur
83b7692b 96
97 # Contrat
98 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
2d4d2fcf 99 default=REGIME_TRAVAIL_DEFAULT,
6e4600ef 100 verbose_name=u"Temps de travail",
101 help_text=u"% du temps complet")
83b7692b 102 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 103 decimal_places=2,
104 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
6e4600ef 105 verbose_name=u"Nb. heures par semaine")
83b7692b 106
107 # Recrutement
6e4600ef 108 local = models.BooleanField(verbose_name=u"Local", default=True,
109 blank=True)
110 expatrie = models.BooleanField(verbose_name=u"Expatrié", default=False,
83b7692b 111 blank=True)
6e4600ef 112 mise_a_disposition = models.BooleanField(
113 verbose_name=u"Mise à disposition",
114 default=False)
115 appel = models.CharField(max_length=10,
116 verbose_name=u"Appel à candidature",
117 choices=POSTE_APPEL_CHOICES,
118 default='interne')
83b7692b 119
120 # Rémunération
6e4600ef 121 classement_min = models.ForeignKey('Classement',
122 db_column='classement_min', related_name='+',
123 null=True, blank=True)
124 classement_max = models.ForeignKey('Classement',
125 db_column='classement_max', related_name='+',
126 null=True, blank=True)
127 valeur_point_min = models.ForeignKey('ValeurPoint',
128 db_column='valeur_point_min', related_name='+',
129 null=True, blank=True)
130 valeur_point_max = models.ForeignKey('ValeurPoint',
131 db_column='valeur_point_max', related_name='+',
132 null=True, blank=True)
133 devise_min = models.ForeignKey('Devise', db_column='devise_min',
134 related_name='+', default=5)
135 devise_max = models.ForeignKey('Devise', db_column='devise_max',
136 related_name='+', default=5)
83b7692b 137 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 138 default=0)
83b7692b 139 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 140 default=0)
83b7692b 141 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 142 default=0)
83b7692b 143 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 144 default=0)
83b7692b 145 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 146 default=0)
83b7692b 147 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 148 default=0)
83b7692b 149
150 # Comparatifs de rémunération
6e4600ef 151 devise_comparaison = models.ForeignKey('Devise',
152 db_column='devise_comparaison',
153 related_name='+',
154 default=5)
83b7692b 155 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 156 null=True, blank=True)
83b7692b 157 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 158 null=True, blank=True)
83b7692b 159 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 160 null=True, blank=True)
83b7692b 161 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 162 null=True, blank=True)
83b7692b 163 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 164 null=True, blank=True)
83b7692b 165 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 166 null=True, blank=True)
83b7692b 167 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 168 null=True, blank=True)
83b7692b 169 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 170 null=True, blank=True)
83b7692b 171 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 172 null=True, blank=True)
83b7692b 173 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 174 null=True, blank=True)
83b7692b 175
176 # Justification
6e4600ef 177 justification = models.TextField(null=True, blank=True)
83b7692b 178
2d4d2fcf 179 # Autres Metadata
07b40eda 180 date_validation = models.DateTimeField(null=True, blank=True) # de dae
6e4600ef 181 date_debut = models.DateField(verbose_name=u"Date de début",
182 help_text=HELP_TEXT_DATE)
183 date_fin = models.DateField(verbose_name=u"Date de fin",
184 help_text=HELP_TEXT_DATE,
185 null=True, blank=True)
186
187 class Meta:
37868f0b 188 abstract = True
6e4600ef 189 ordering = ['implantation__nom', 'nom']
190
83b7692b 191 def __unicode__(self):
2d4d2fcf 192 # TODO : gérer si poste est vacant ou non dans affichage
6e4600ef 193 # TODO : gérer le nom_feminin (autre méthode appelée par __unicode__ ?)
83b7692b 194 return u'%s - %s [%s]' % (self.implantation, self.nom, self.id)
195
196
37868f0b
NC
197class Poste(Poste_):
198 __doc__ = Poste_.__doc__
199
200
83b7692b 201POSTE_FINANCEMENT_CHOICES = (
202 ('A', 'A - Frais de personnel'),
203 ('B', 'B - Projet(s)-Titre(s)'),
204 ('C', 'C - Autre')
205)
206
207class PosteFinancement(models.Model):
6e4600ef 208 """Pour un Poste, structure d'informations décrivant comment on prévoit
209 financer ce Poste.
210 """
211 poste = models.ForeignKey('Poste', db_column='poste',
212 related_name='financements')
83b7692b 213 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
214 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 215 help_text=u"ex.: 33.33 % (décimale avec point)")
83b7692b 216 commentaire = models.TextField(
6e4600ef 217 help_text=u"Spécifiez la source de financement.")
83b7692b 218
219 class Meta:
220 ordering = ['type']
6e4600ef 221
222 def __unicode__(self):
223 return u'%s : %s %' % (self.type, self.pourcentage)
83b7692b 224
225class PostePiece(models.Model):
6e4600ef 226 """Documents relatifs au Poste.
7abc6d45 227 Ex.: Description de poste
228 """
6e4600ef 229 poste = models.ForeignKey("Poste", db_column='poste',
230 related_name='pieces')
231 nom = models.CharField(verbose_name=u"Nom", max_length=255)
232 fichier = models.FileField(verbose_name=u"Fichier",
83b7692b 233 upload_to=poste_piece_dispatch,
234 storage=storage_prive)
235
6e4600ef 236 class Meta:
237 ordering = ['nom']
238
239 def __unicode__(self):
240 return u'%s' % (self.nom)
241
07b40eda 242class PosteCommentaire(Commentaire):
6e4600ef 243 poste = models.ForeignKey("Poste", db_column='poste', related_name='+')
83b7692b 244
2d4d2fcf 245
83b7692b 246### EMPLOYÉ/PERSONNE
e9bbd6ba 247
248GENRE_CHOICES = (
249 ('M', 'Homme'),
250 ('F', 'Femme'),
251)
252SITUATION_CHOICES = (
253 ('C', 'Célibataire'),
254 ('F', 'Fiancé'),
255 ('M', 'Marié'),
256)
257
2d4d2fcf 258class Employe(Metadata):
6e4600ef 259 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
260 Dossiers qu'il occupe ou a occupé de Postes.
261
262 Cette classe aurait pu avantageusement s'appeler Personne car la notion
263 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
264 """
9afaa55e 265 # Identification
e9bbd6ba 266 nom = models.CharField(max_length=255)
6e4600ef 267 prenom = models.CharField(max_length=255, verbose_name=u"Prénom")
268 # TODO : nom_affichage doit être obligatoire, pas nom et prenom
269 nom_affichage = models.CharField(max_length=255,
270 verbose_name=u"Nom d'affichage",
271 null=True, blank=True)
83b7692b 272 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 273 db_column='nationalite',
274 related_name='employes_nationalite')
275 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
276 null=True, blank=True)
2d4d2fcf 277 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 278
9afaa55e 279 # Infos personnelles
6e4600ef 280 situation_famille = models.CharField(max_length=1,
281 choices=SITUATION_CHOICES,
282 null=True, blank=True)
283 date_entree = models.DateField(verbose_name=u"Date d'entrée à l'AUF",
284 help_text=HELP_TEXT_DATE,
7abc6d45 285 null=True, blank=True)
83b7692b 286
9afaa55e 287 # Coordonnées
e9bbd6ba 288 tel_domicile = models.CharField(max_length=255, null=True, blank=True)
289 tel_cellulaire = models.CharField(max_length=255, null=True, blank=True)
290 adresse = models.CharField(max_length=255, null=True, blank=True)
e9bbd6ba 291 ville = models.CharField(max_length=255, null=True, blank=True)
292 province = models.CharField(max_length=255, null=True, blank=True)
293 code_postal = models.CharField(max_length=255, null=True, blank=True)
6e4600ef 294 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
295 related_name='employes',
296 null=True, blank=True)
9afaa55e 297
6e4600ef 298 class Meta:
299 ordering = ['nom_affichage']
300
9afaa55e 301 def __unicode__(self):
6e4600ef 302 # TODO : gérer nom d'affichage
303 return u'%s' % (self.nom_affichage)
9afaa55e 304
7abc6d45 305class EmployePiece(models.Model):
6e4600ef 306 """Documents relatifs à un employé.
7abc6d45 307 Ex.: CV...
308 """
6e4600ef 309 employe = models.ForeignKey("Employe", db_column='employe',
310 related_name='+')
311 nom = models.CharField(verbose_name=u"Nom", max_length=255)
312 fichier = models.FileField(verbose_name=u"Fichier",
7abc6d45 313 upload_to=dossier_piece_dispatch,
314 storage=storage_prive)
315
6e4600ef 316 class Meta:
317 ordering = ['nom']
318
319 def __unicode__(self):
320 return u'%s' % (self.nom)
321
07b40eda 322class EmployeCommentaire(Commentaire):
6e4600ef 323 employe = models.ForeignKey("Employe", db_column='employe',
324 related_name='+')
9afaa55e 325
2d4d2fcf 326
e9bbd6ba 327LIEN_PARENTE_CHOICES = (
328 ('Conjoint', 'Conjoint'),
329 ('Conjointe', 'Conjointe'),
330 ('Fille', 'Fille'),
331 ('Fils', 'Fils'),
332)
333
2d4d2fcf 334class AyantDroit(Metadata):
6e4600ef 335 """Personne en relation avec un Employe.
336 """
9afaa55e 337 # Identification
e9bbd6ba 338 nom = models.CharField(max_length=255)
339 prenom = models.CharField(max_length=255)
6e4600ef 340 # TODO : nom_affichage doit être obligatoire, pas nom et prenom
341 nom_affichage = models.CharField(max_length=255,
342 verbose_name=u"Nom d'affichage",
343 null=True, blank=True)
2d4d2fcf 344 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 345 db_column='nationalite',
346 related_name='ayantdroits_nationalite')
347 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
348 null=True, blank=True)
2d4d2fcf 349 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 350
9afaa55e 351 # Relation
352 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 353 related_name='ayantdroits')
354 lien_parente = models.CharField(max_length=10,
355 choices=LIEN_PARENTE_CHOICES,
356 null=True, blank=True)
357
358 class Meta:
359 ordering = ['nom_affichage']
360 def __unicode__(self):
361 # TODO : gérer nom d'affichage
362 return u'%s %s' % (self.prenom, self.nom.upper())
83b7692b 363
07b40eda 364class AyantDroitCommentaire(Commentaire):
6e4600ef 365 ayant_droit = models.ForeignKey("AyantDroit", db_column='ayant_droit',
366 related_name='+')
83b7692b 367
2d4d2fcf 368
83b7692b 369### DOSSIER
370
371STATUT_RESIDENCE_CHOICES = (
372 ('local', 'Local'),
373 ('expat', 'Expatrié'),
374)
375
376COMPTE_COMPTA_CHOICES = (
377 ('coda', 'CODA'),
378 ('scs', 'SCS'),
379 ('aucun', 'Aucun'),
380)
381
37868f0b 382class Dossier_(Metadata):
6e4600ef 383 """Le Dossier regroupe les informations relatives à l'occupation
384 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
385 par un Employe.
386
387 Plusieurs Contrats peuvent être associés au Dossier.
388 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
389 lequel aucun Dossier n'existe est un poste vacant.
390 """
83b7692b 391 # Identification
6e4600ef 392 employe = models.ForeignKey('Employe', db_column='employe',
393 related_name='+')
394 poste = models.ForeignKey('Poste', db_column='poste',
395 related_name='+', editable=False)
396 statut = models.ForeignKey('Statut', related_name='+', default=3)
83b7692b 397 organisme_bstg = models.ForeignKey('OrganismeBstg',
6e4600ef 398 db_column='organisme_bstg',
399 related_name='+',
400 verbose_name=u"Organisme",
401 help_text=u"Si détaché (DET) ou \
402 mis à disposition (MAD), \
403 préciser l'organisme.",
404 null=True, blank=True)
405
83b7692b 406 # Recrutement
2d4d2fcf 407 remplacement = models.BooleanField(default=False)
83b7692b 408 statut_residence = models.CharField(max_length=10, default='local',
6e4600ef 409 verbose_name=u"Statut",
2d4d2fcf 410 choices=STATUT_RESIDENCE_CHOICES)
83b7692b 411
412 # Rémunération
6e4600ef 413 classement = models.ForeignKey('Classement', db_column='classement',
414 related_name='+',
415 null=True, blank=True)
2d4d2fcf 416 regime_travail = models.DecimalField(max_digits=12,
417 decimal_places=2,
418 default=REGIME_TRAVAIL_DEFAULT,
6e4600ef 419 verbose_name=u"Régime de travail",
420 help_text=u"% du temps complet")
83b7692b 421 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 422 decimal_places=2,
423 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
6e4600ef 424 verbose_name=u"Nb. heures par semaine")
7abc6d45 425
426 # Occupation du Poste par cet Employe (anciennement "mandat")
6e4600ef 427 date_debut = models.DateField(verbose_name=u"Date de début d'occupation \
428 de poste",
429 help_text=HELP_TEXT_DATE)
430 date_fin = models.DateField(verbose_name=u"Date de fin d'occupation \
431 de poste",
2d4d2fcf 432 help_text=HELP_TEXT_DATE,
6e4600ef 433 null=True, blank=True)
e9bbd6ba 434
2d4d2fcf 435 # Comptes
436 # TODO?
83b7692b 437
6e4600ef 438 class Meta:
37868f0b 439 abstract = True
6e4600ef 440 ordering = ['poste__nom', 'employe__nom_affichage']
441
83b7692b 442 def __unicode__(self):
443 return u'%s - %s' % (self.poste.nom, self.employe)
444
37868f0b
NC
445
446class Dossier(Dossier_):
447 __doc__ = Dossier_.__doc__
448
449
83b7692b 450class DossierPiece(models.Model):
7abc6d45 451 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
452 Ex.: Lettre de motivation.
453 """
6e4600ef 454 dossier = models.ForeignKey("Dossier", db_column='dossier',
455 related_name='+')
456 nom = models.CharField(verbose_name=u"Nom", max_length=255)
457 fichier = models.FileField(verbose_name=u"Fichier",
83b7692b 458 upload_to=dossier_piece_dispatch,
459 storage=storage_prive)
460
6e4600ef 461 class Meta:
462 ordering = ['nom']
463
464 def __unicode__(self):
465 return u'%s' % (self.nom)
466
07b40eda 467class DossierCommentaire(Commentaire):
6e4600ef 468 dossier = models.ForeignKey("Dossier", db_column='dossier',
469 related_name='+')
83b7692b 470
2d4d2fcf 471
07b40eda 472### RÉMUNÉRATION
e9bbd6ba 473
2d4d2fcf 474class RemunerationMixin(Metadata):
9afaa55e 475 # Identification
e9bbd6ba 476 dossier = models.ForeignKey('Dossier', db_column='dossier')
83b7692b 477 type = models.ForeignKey('TypeRemuneration', db_column='type',
478 related_name='+')
7abc6d45 479 type_revalorisation = models.ForeignKey('TypeRevalorisation',
480 db_column='type_revalorisation',
6e4600ef 481 related_name='+',
7abc6d45 482 null=True, blank=True)
50fa9bc1 483 montant = models.FloatField(null=True, blank=True,
6e4600ef 484 default=0)
2d4d2fcf 485 # Annuel (12 mois, 52 semaines, 364 jours?)
c589d980 486 devise = models.ForeignKey('Devise', to_field='id',
6e4600ef 487 db_column='devise', related_name='+',
488 default=5)
2d4d2fcf 489 # commentaire = precision
490 commentaire = models.CharField(max_length=255, null=True, blank=True)
491 # date_debut = anciennement date_effectif
6e4600ef 492 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
493 null=True, blank=True)
494 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
495 null=True, blank=True)
83b7692b 496
2d4d2fcf 497 class Meta:
498 abstract = True
6e4600ef 499 ordering = ['type__nom', '-date_fin']
500
501 def __unicode__(self):
502 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
2d4d2fcf 503
504class Remuneration(RemunerationMixin):
505 """Structure de rémunération (données budgétaires) en situation normale
506 pour un Dossier. Si un Evenement existe, utiliser la structure de
507 rémunération EvenementRemuneration de cet événement.
508 """
83b7692b 509
510 def montant_mois(self):
511 return round(self.montant / 12, 2)
512
513 def taux_devise(self):
514 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
515
516 def montant_euro(self):
517 return round(float(self.montant) / float(self.taux_devise()), 2)
518
519 def montant_euro_mois(self):
520 return round(self.montant_euro() / 12, 2)
9afaa55e 521
522 def __unicode__(self):
523 try:
524 devise = self.devise.code
525 except:
526 devise = "???"
527 return "%s %s" % (self.montant, devise)
83b7692b 528
2d4d2fcf 529
530### CONTRATS
531
532class Contrat(Metadata):
533 """Document juridique qui encadre la relation de travail d'un Employe
534 pour un Poste particulier. Pour un Dossier (qui documente cette
535 relation de travail) plusieurs contrats peuvent être associés.
536 """
6e4600ef 537 dossier = models.ForeignKey('Dossier', db_column='dossier',
538 related_name='+')
539 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
540 related_name='+')
2d4d2fcf 541 date_debut = models.DateField(help_text=HELP_TEXT_DATE)
6e4600ef 542 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
543 null=True, blank=True)
544
545 class Meta:
546 ordering = ['dossier__employe__nom_affichage']
547
548 def __unicode__(self):
549 return u'%s - %s' % (self.dossier.employe.nom_affichage, self.id)
550
551# TODO? class ContratPiece(models.Model):
2d4d2fcf 552
553
554### ÉVÉNEMENTS
555
6e4600ef 556class Evenement(Metadata):
557 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
558 d'un Dossier qui vient altérer des informations normales liées à un Dossier
559 (ex.: la Remuneration).
560
561 Ex.: congé de maternité, maladie...
562
563 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
564 différent et une rémunération en conséquence. On souhaite toutefois
565 conserver le Dossier intact afin d'éviter une re-saisie des données lors
566 du retour à la normale.
567 """
568 dossier = models.ForeignKey("Dossier", db_column='dossier',
569 related_name='+')
570 nom = models.CharField(max_length=255)
571 date_debut = models.DateField(help_text=HELP_TEXT_DATE)
572 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
573 null=True, blank=True)
574 class Meta:
575 ordering = ['nom']
576
577 def __unicode__(self):
578 return u'%s' % (self.nom)
2d4d2fcf 579
580class EvenementRemuneration(RemunerationMixin):
6e4600ef 581 """Structure de rémunération liée à un Evenement qui remplace
582 temporairement la Remuneration normale d'un Dossier, pour toute la durée
583 de l'Evenement.
584 """
585 evenement = models.ForeignKey("Evenement", db_column='evenement',
586 related_name='+')
83b7692b 587
588
589### RÉFÉRENCES RH
590
6e4600ef 591class FamilleEmploi(Metadata):
592 """Catégorie utilisée dans la gestion des Postes.
593 Catégorie supérieure à TypePoste.
594 """
e9bbd6ba 595 nom = models.CharField(max_length=255)
6e4600ef 596
597 def __unicode__(self):
598 return u'%s' % (self.nom)
e9bbd6ba 599
6e4600ef 600class TypePoste(Metadata):
601 """Catégorie de Poste.
602 """
e9bbd6ba 603 nom = models.CharField(max_length=255)
604 nom_feminin = models.CharField(max_length=255)
6e4600ef 605
606 is_responsable = models.BooleanField(default=False)
9afaa55e 607 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 608 db_column='famille_emploi',
609 related_name='+')
e9bbd6ba 610
6e4600ef 611 class Meta:
612 ordering = ['nom']
613
e9bbd6ba 614 def __unicode__(self):
6e4600ef 615 # TODO : gérer nom féminin
616 return u'%s' % (self.nom)
e9bbd6ba 617
618
619TYPE_PAIEMENT_CHOICES = (
620 ('Régulier', 'Régulier'),
621 ('Ponctuel', 'Ponctuel'),
622)
623
624NATURE_REMUNERATION_CHOICES = (
625 ('Accessoire', 'Accessoire'),
626 ('Charges', 'Charges'),
627 ('Indemnité', 'Indemnité'),
7abc6d45 628 ('RAS', 'Rémunération autre source'),
e9bbd6ba 629 ('Traitement', 'Traitement'),
630)
631
6e4600ef 632class TypeRemuneration(Metadata):
633 """Catégorie de Remuneration.
634 """
e9bbd6ba 635 nom = models.CharField(max_length=255)
9afaa55e 636 type_paiement = models.CharField(max_length=30,
637 choices=TYPE_PAIEMENT_CHOICES)
638 nature_remuneration = models.CharField(max_length=30,
639 choices=NATURE_REMUNERATION_CHOICES)
9afaa55e 640
641 def __unicode__(self):
6e4600ef 642 return u'%s' % (self.nom)
e9bbd6ba 643
6e4600ef 644class TypeRevalorisation(Metadata):
7abc6d45 645 """Justification du changement de la Remuneration.
6e4600ef 646 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 647 """
e9bbd6ba 648 nom = models.CharField(max_length=255)
e9bbd6ba 649
650 def __unicode__(self):
6e4600ef 651 return u'%s' % (self.nom)
652
653class Service(Metadata):
654 """Unité administrative où les Postes sont rattachés.
655 """
656 nom = models.CharField(max_length=255)
9afaa55e 657
658 class Meta:
659 ordering = ['nom']
e9bbd6ba 660
6e4600ef 661 def __unicode__(self):
662 return u'%s' % (self.nom)
663
e9bbd6ba 664
665TYPE_ORGANISME_CHOICES = (
666 ('MAD', 'Mise à disposition'),
667 ('DET', 'Détachement'),
668)
669
6e4600ef 670class OrganismeBstg(Metadata):
671 """Organisation d'où provient un Employe mis à disposition (MAD) de
672 ou détaché (DET) à l'AUF à titre gratuit.
673
674 (BSTG = bien et service à titre gratuit.)
675 """
e9bbd6ba 676 nom = models.CharField(max_length=255)
677 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
6e4600ef 678 pays = models.ForeignKey(ref.Pays, to_field='code',
679 db_column='pays',
680 related_name='organismes_bstg',
681 null=True, blank=True)
9afaa55e 682
683 class Meta:
684 ordering = ['type', 'nom']
685
6e4600ef 686 def __unicode__(self):
687 return u'%s (%s)' % (self.nom, self.type)
83b7692b 688
6e4600ef 689class Statut(Metadata):
690 """Statut de l'Employe dans le cadre d'un Dossier particulier.
691 """
9afaa55e 692 # Identification
e9bbd6ba 693 code = models.CharField(max_length=25, unique=True)
694 nom = models.CharField(max_length=255)
e9bbd6ba 695
6e4600ef 696 class Meta:
697 ordering = ['code']
698
9afaa55e 699 def __unicode__(self):
700 return u'%s : %s' % (self.code, self.nom)
701
83b7692b 702
e9bbd6ba 703TYPE_CLASSEMENT_CHOICES = (
6e4600ef 704 ('S', 'S -Soutien'),
705 ('T', 'T - Technicien'),
706 ('P', 'P - Professionel'),
707 ('C', 'C - Cadre'),
708 ('D', 'D - Direction'),
709 ('SO', 'SO - Sans objet [expatriés]'),
710 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 711)
83b7692b 712
6e4600ef 713class Classement(Metadata):
714 """Éléments de classement de la
715 "Grille générique de classement hiérarchique".
716
717 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
718 classement dans la grille. Le classement donne le coefficient utilisé dans:
719
720 salaire de base = coefficient * valeur du point de l'Implantation du Poste
721 """
9afaa55e 722 # Identification
e9bbd6ba 723 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
724 echelon = models.IntegerField()
725 degre = models.IntegerField()
6e4600ef 726 coefficient = models.FloatField(default=0)
9afaa55e 727 # Méta
6e4600ef 728 # annee # au lieu de date_debut et date_fin
729 commentaire = models.TextField(null=True, blank=True)
730
731 class Meta:
732 ordering = ['type','echelon','degre','coefficient']
e9bbd6ba 733
734 def __unicode__(self):
735 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
736 self.coefficient)
737
6e4600ef 738class TauxChange(Metadata):
7abc6d45 739 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 740 pour chaque année budgétaire.
7abc6d45 741 """
9afaa55e 742 # Identification
6e4600ef 743 devise = models.ForeignKey('Devise', to_field='code', db_column='devise',
744 related_name='+')
e9bbd6ba 745 annee = models.IntegerField()
9afaa55e 746 taux = models.FloatField()
6e4600ef 747
748 class Meta:
749 ordering = ['annee', 'devise__code']
750
751 def __unicode__(self):
752 return u'%s : %s €' % (self.devise.code, self.taux)
e9bbd6ba 753
6e4600ef 754class ValeurPoint(Metadata):
755 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
756 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
757 du POste de ce Dossier : dossier.poste.implantation (pseudo code).
758
759 salaire de base = coefficient * valeur du point de l'Implantation du Poste
760 """
9afaa55e 761 valeur = models.FloatField()
6e4600ef 762 devise = models.ForeignKey('Devise', db_column='devise',
763 related_name='+', default=5)
83b7692b 764 implantation = models.ForeignKey(ref.Implantation,
9afaa55e 765 db_column='implantation',
6e4600ef 766 related_name='valeur_point')
9afaa55e 767 # Méta
e9bbd6ba 768 annee = models.IntegerField()
9afaa55e 769
2d4d2fcf 770 # Stockage de tous les taux de change
771 # pour optimiser la recherche de la devise associée
9afaa55e 772 annee_courante = datetime.datetime.now().year
6e4600ef 773 tauxchange = TauxChange.objects.select_related('devise') \
774 .filter(annee=annee_courante)
9afaa55e 775
6e4600ef 776 class Meta:
777 ordering = ['annee', 'implantation__nom']
778
9afaa55e 779 def __unicode__(self):
780 tx = self.get_tauxchange_courant()
781 if tx:
782 devise_code = tx.devise.code
783 else:
784 devise_code = "??"
2d4d2fcf 785 return u'%s %s (%s-%s)' % (self.valeur, devise_code,
786 self.implantation_id, self.annee)
9afaa55e 787
788 class Meta:
789 ordering = ['valeur']
e9bbd6ba 790
6e4600ef 791class Devise(Metadata):
792 """Devise monétaire.
793 """
e9bbd6ba 794 code = models.CharField(max_length=10, unique=True)
795 nom = models.CharField(max_length=255)
796
6e4600ef 797 class Meta:
798 ordering = ['code']
799
e9bbd6ba 800 def __unicode__(self):
801 return u'%s - %s' % (self.code, self.nom)
802
6e4600ef 803class TypeContrat(Metadata):
804 """Type de contrat.
805 """
e9bbd6ba 806 nom = models.CharField(max_length=255)
6e4600ef 807 nom_long = models.CharField(max_length=255)
49f9f116 808
9afaa55e 809 def __unicode__(self):
810 return u'%s' % (self.nom)
30be56d5 811
2d4d2fcf 812
813### AUTRES
814
6e4600ef 815class ResponsableImplantation(Metadata):
30be56d5 816 """Le responsable d'une implantation.
817 Anciennement géré sur le Dossier du responsable.
818 """
6e4600ef 819 employe = models.ForeignKey('Employe', db_column='employe',
820 related_name='+',
821 null=True, blank=True)
822 implantation = models.ForeignKey(ref.Implantation,
823 db_column='implantation', related_name='+',
824 unique=True)
30be56d5 825
826 def __unicode__(self):
827 return u'%s : %s' % (self.implantation, self.employe)
828
829 class Meta:
830 ordering = ['implantation__nom']