fix #1450
[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
2d4d2fcf 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:
188 ordering = ['implantation__nom', 'nom']
189
83b7692b 190 def __unicode__(self):
2d4d2fcf 191 # TODO : gérer si poste est vacant ou non dans affichage
6e4600ef 192 # TODO : gérer le nom_feminin (autre méthode appelée par __unicode__ ?)
83b7692b 193 return u'%s - %s [%s]' % (self.implantation, self.nom, self.id)
194
195
196POSTE_FINANCEMENT_CHOICES = (
197 ('A', 'A - Frais de personnel'),
198 ('B', 'B - Projet(s)-Titre(s)'),
199 ('C', 'C - Autre')
200)
201
202class PosteFinancement(models.Model):
6e4600ef 203 """Pour un Poste, structure d'informations décrivant comment on prévoit
204 financer ce Poste.
205 """
206 poste = models.ForeignKey('Poste', db_column='poste',
207 related_name='financements')
83b7692b 208 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
209 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 210 help_text=u"ex.: 33.33 % (décimale avec point)")
83b7692b 211 commentaire = models.TextField(
6e4600ef 212 help_text=u"Spécifiez la source de financement.")
83b7692b 213
214 class Meta:
215 ordering = ['type']
6e4600ef 216
217 def __unicode__(self):
218 return u'%s : %s %' % (self.type, self.pourcentage)
83b7692b 219
220class PostePiece(models.Model):
6e4600ef 221 """Documents relatifs au Poste.
7abc6d45 222 Ex.: Description de poste
223 """
6e4600ef 224 poste = models.ForeignKey("Poste", db_column='poste',
225 related_name='pieces')
226 nom = models.CharField(verbose_name=u"Nom", max_length=255)
227 fichier = models.FileField(verbose_name=u"Fichier",
83b7692b 228 upload_to=poste_piece_dispatch,
229 storage=storage_prive)
230
6e4600ef 231 class Meta:
232 ordering = ['nom']
233
234 def __unicode__(self):
235 return u'%s' % (self.nom)
236
07b40eda 237class PosteCommentaire(Commentaire):
6e4600ef 238 poste = models.ForeignKey("Poste", db_column='poste', related_name='+')
83b7692b 239
2d4d2fcf 240
83b7692b 241### EMPLOYÉ/PERSONNE
e9bbd6ba 242
243GENRE_CHOICES = (
244 ('M', 'Homme'),
245 ('F', 'Femme'),
246)
247SITUATION_CHOICES = (
248 ('C', 'Célibataire'),
249 ('F', 'Fiancé'),
250 ('M', 'Marié'),
251)
252
2d4d2fcf 253class Employe(Metadata):
6e4600ef 254 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
255 Dossiers qu'il occupe ou a occupé de Postes.
256
257 Cette classe aurait pu avantageusement s'appeler Personne car la notion
258 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
259 """
9afaa55e 260 # Identification
e9bbd6ba 261 nom = models.CharField(max_length=255)
6e4600ef 262 prenom = models.CharField(max_length=255, verbose_name=u"Prénom")
263 # TODO : nom_affichage doit être obligatoire, pas nom et prenom
264 nom_affichage = models.CharField(max_length=255,
265 verbose_name=u"Nom d'affichage",
266 null=True, blank=True)
83b7692b 267 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 268 db_column='nationalite',
269 related_name='employes_nationalite')
270 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
271 null=True, blank=True)
2d4d2fcf 272 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 273
9afaa55e 274 # Infos personnelles
6e4600ef 275 situation_famille = models.CharField(max_length=1,
276 choices=SITUATION_CHOICES,
277 null=True, blank=True)
278 date_entree = models.DateField(verbose_name=u"Date d'entrée à l'AUF",
279 help_text=HELP_TEXT_DATE,
7abc6d45 280 null=True, blank=True)
83b7692b 281
9afaa55e 282 # Coordonnées
e9bbd6ba 283 tel_domicile = models.CharField(max_length=255, null=True, blank=True)
284 tel_cellulaire = models.CharField(max_length=255, null=True, blank=True)
285 adresse = models.CharField(max_length=255, null=True, blank=True)
e9bbd6ba 286 ville = models.CharField(max_length=255, null=True, blank=True)
287 province = models.CharField(max_length=255, null=True, blank=True)
288 code_postal = models.CharField(max_length=255, null=True, blank=True)
6e4600ef 289 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
290 related_name='employes',
291 null=True, blank=True)
9afaa55e 292
6e4600ef 293 class Meta:
294 ordering = ['nom_affichage']
295
9afaa55e 296 def __unicode__(self):
6e4600ef 297 # TODO : gérer nom d'affichage
298 return u'%s' % (self.nom_affichage)
9afaa55e 299
7abc6d45 300class EmployePiece(models.Model):
6e4600ef 301 """Documents relatifs à un employé.
7abc6d45 302 Ex.: CV...
303 """
6e4600ef 304 employe = models.ForeignKey("Employe", db_column='employe',
305 related_name='+')
306 nom = models.CharField(verbose_name=u"Nom", max_length=255)
307 fichier = models.FileField(verbose_name=u"Fichier",
7abc6d45 308 upload_to=dossier_piece_dispatch,
309 storage=storage_prive)
310
6e4600ef 311 class Meta:
312 ordering = ['nom']
313
314 def __unicode__(self):
315 return u'%s' % (self.nom)
316
07b40eda 317class EmployeCommentaire(Commentaire):
6e4600ef 318 employe = models.ForeignKey("Employe", db_column='employe',
319 related_name='+')
9afaa55e 320
2d4d2fcf 321
e9bbd6ba 322LIEN_PARENTE_CHOICES = (
323 ('Conjoint', 'Conjoint'),
324 ('Conjointe', 'Conjointe'),
325 ('Fille', 'Fille'),
326 ('Fils', 'Fils'),
327)
328
2d4d2fcf 329class AyantDroit(Metadata):
6e4600ef 330 """Personne en relation avec un Employe.
331 """
9afaa55e 332 # Identification
e9bbd6ba 333 nom = models.CharField(max_length=255)
334 prenom = models.CharField(max_length=255)
6e4600ef 335 # TODO : nom_affichage doit être obligatoire, pas nom et prenom
336 nom_affichage = models.CharField(max_length=255,
337 verbose_name=u"Nom d'affichage",
338 null=True, blank=True)
2d4d2fcf 339 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 340 db_column='nationalite',
341 related_name='ayantdroits_nationalite')
342 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
343 null=True, blank=True)
2d4d2fcf 344 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 345
9afaa55e 346 # Relation
347 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 348 related_name='ayantdroits')
349 lien_parente = models.CharField(max_length=10,
350 choices=LIEN_PARENTE_CHOICES,
351 null=True, blank=True)
352
353 class Meta:
354 ordering = ['nom_affichage']
355 def __unicode__(self):
356 # TODO : gérer nom d'affichage
357 return u'%s %s' % (self.prenom, self.nom.upper())
83b7692b 358
07b40eda 359class AyantDroitCommentaire(Commentaire):
6e4600ef 360 ayant_droit = models.ForeignKey("AyantDroit", db_column='ayant_droit',
361 related_name='+')
83b7692b 362
2d4d2fcf 363
83b7692b 364### DOSSIER
365
366STATUT_RESIDENCE_CHOICES = (
367 ('local', 'Local'),
368 ('expat', 'Expatrié'),
369)
370
371COMPTE_COMPTA_CHOICES = (
372 ('coda', 'CODA'),
373 ('scs', 'SCS'),
374 ('aucun', 'Aucun'),
375)
376
2d4d2fcf 377class Dossier(Metadata):
6e4600ef 378 """Le Dossier regroupe les informations relatives à l'occupation
379 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
380 par un Employe.
381
382 Plusieurs Contrats peuvent être associés au Dossier.
383 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
384 lequel aucun Dossier n'existe est un poste vacant.
385 """
83b7692b 386 # Identification
6e4600ef 387 employe = models.ForeignKey('Employe', db_column='employe',
388 related_name='+')
389 poste = models.ForeignKey('Poste', db_column='poste',
390 related_name='+', editable=False)
391 statut = models.ForeignKey('Statut', related_name='+', default=3)
83b7692b 392 organisme_bstg = models.ForeignKey('OrganismeBstg',
6e4600ef 393 db_column='organisme_bstg',
394 related_name='+',
395 verbose_name=u"Organisme",
396 help_text=u"Si détaché (DET) ou \
397 mis à disposition (MAD), \
398 préciser l'organisme.",
399 null=True, blank=True)
400
83b7692b 401 # Recrutement
2d4d2fcf 402 remplacement = models.BooleanField(default=False)
83b7692b 403 statut_residence = models.CharField(max_length=10, default='local',
6e4600ef 404 verbose_name=u"Statut",
2d4d2fcf 405 choices=STATUT_RESIDENCE_CHOICES)
83b7692b 406
407 # Rémunération
6e4600ef 408 classement = models.ForeignKey('Classement', db_column='classement',
409 related_name='+',
410 null=True, blank=True)
2d4d2fcf 411 regime_travail = models.DecimalField(max_digits=12,
412 decimal_places=2,
413 default=REGIME_TRAVAIL_DEFAULT,
6e4600ef 414 verbose_name=u"Régime de travail",
415 help_text=u"% du temps complet")
83b7692b 416 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 417 decimal_places=2,
418 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
6e4600ef 419 verbose_name=u"Nb. heures par semaine")
7abc6d45 420
421 # Occupation du Poste par cet Employe (anciennement "mandat")
6e4600ef 422 date_debut = models.DateField(verbose_name=u"Date de début d'occupation \
423 de poste",
424 help_text=HELP_TEXT_DATE)
425 date_fin = models.DateField(verbose_name=u"Date de fin d'occupation \
426 de poste",
2d4d2fcf 427 help_text=HELP_TEXT_DATE,
6e4600ef 428 null=True, blank=True)
e9bbd6ba 429
2d4d2fcf 430 # Comptes
431 # TODO?
83b7692b 432
6e4600ef 433 class Meta:
434 ordering = ['poste__nom', 'employe__nom_affichage']
435
83b7692b 436 def __unicode__(self):
437 return u'%s - %s' % (self.poste.nom, self.employe)
438
439class DossierPiece(models.Model):
7abc6d45 440 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
441 Ex.: Lettre de motivation.
442 """
6e4600ef 443 dossier = models.ForeignKey("Dossier", db_column='dossier',
444 related_name='+')
445 nom = models.CharField(verbose_name=u"Nom", max_length=255)
446 fichier = models.FileField(verbose_name=u"Fichier",
83b7692b 447 upload_to=dossier_piece_dispatch,
448 storage=storage_prive)
449
6e4600ef 450 class Meta:
451 ordering = ['nom']
452
453 def __unicode__(self):
454 return u'%s' % (self.nom)
455
07b40eda 456class DossierCommentaire(Commentaire):
6e4600ef 457 dossier = models.ForeignKey("Dossier", db_column='dossier',
458 related_name='+')
83b7692b 459
2d4d2fcf 460
07b40eda 461### RÉMUNÉRATION
e9bbd6ba 462
2d4d2fcf 463class RemunerationMixin(Metadata):
9afaa55e 464 # Identification
e9bbd6ba 465 dossier = models.ForeignKey('Dossier', db_column='dossier')
83b7692b 466 type = models.ForeignKey('TypeRemuneration', db_column='type',
467 related_name='+')
7abc6d45 468 type_revalorisation = models.ForeignKey('TypeRevalorisation',
469 db_column='type_revalorisation',
6e4600ef 470 related_name='+',
7abc6d45 471 null=True, blank=True)
50fa9bc1 472 montant = models.FloatField(null=True, blank=True,
6e4600ef 473 default=0)
2d4d2fcf 474 # Annuel (12 mois, 52 semaines, 364 jours?)
c589d980 475 devise = models.ForeignKey('Devise', to_field='id',
6e4600ef 476 db_column='devise', related_name='+',
477 default=5)
2d4d2fcf 478 # commentaire = precision
479 commentaire = models.CharField(max_length=255, null=True, blank=True)
480 # date_debut = anciennement date_effectif
6e4600ef 481 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
482 null=True, blank=True)
483 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
484 null=True, blank=True)
83b7692b 485
2d4d2fcf 486 class Meta:
487 abstract = True
6e4600ef 488 ordering = ['type__nom', '-date_fin']
489
490 def __unicode__(self):
491 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
2d4d2fcf 492
493class Remuneration(RemunerationMixin):
494 """Structure de rémunération (données budgétaires) en situation normale
495 pour un Dossier. Si un Evenement existe, utiliser la structure de
496 rémunération EvenementRemuneration de cet événement.
497 """
83b7692b 498
499 def montant_mois(self):
500 return round(self.montant / 12, 2)
501
502 def taux_devise(self):
503 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
504
505 def montant_euro(self):
506 return round(float(self.montant) / float(self.taux_devise()), 2)
507
508 def montant_euro_mois(self):
509 return round(self.montant_euro() / 12, 2)
9afaa55e 510
511 def __unicode__(self):
512 try:
513 devise = self.devise.code
514 except:
515 devise = "???"
516 return "%s %s" % (self.montant, devise)
83b7692b 517
2d4d2fcf 518
519### CONTRATS
520
521class Contrat(Metadata):
522 """Document juridique qui encadre la relation de travail d'un Employe
523 pour un Poste particulier. Pour un Dossier (qui documente cette
524 relation de travail) plusieurs contrats peuvent être associés.
525 """
6e4600ef 526 dossier = models.ForeignKey('Dossier', db_column='dossier',
527 related_name='+')
528 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
529 related_name='+')
2d4d2fcf 530 date_debut = models.DateField(help_text=HELP_TEXT_DATE)
6e4600ef 531 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
532 null=True, blank=True)
533
534 class Meta:
535 ordering = ['dossier__employe__nom_affichage']
536
537 def __unicode__(self):
538 return u'%s - %s' % (self.dossier.employe.nom_affichage, self.id)
539
540# TODO? class ContratPiece(models.Model):
2d4d2fcf 541
542
543### ÉVÉNEMENTS
544
6e4600ef 545class Evenement(Metadata):
546 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
547 d'un Dossier qui vient altérer des informations normales liées à un Dossier
548 (ex.: la Remuneration).
549
550 Ex.: congé de maternité, maladie...
551
552 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
553 différent et une rémunération en conséquence. On souhaite toutefois
554 conserver le Dossier intact afin d'éviter une re-saisie des données lors
555 du retour à la normale.
556 """
557 dossier = models.ForeignKey("Dossier", db_column='dossier',
558 related_name='+')
559 nom = models.CharField(max_length=255)
560 date_debut = models.DateField(help_text=HELP_TEXT_DATE)
561 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
562 null=True, blank=True)
563 class Meta:
564 ordering = ['nom']
565
566 def __unicode__(self):
567 return u'%s' % (self.nom)
2d4d2fcf 568
569class EvenementRemuneration(RemunerationMixin):
6e4600ef 570 """Structure de rémunération liée à un Evenement qui remplace
571 temporairement la Remuneration normale d'un Dossier, pour toute la durée
572 de l'Evenement.
573 """
574 evenement = models.ForeignKey("Evenement", db_column='evenement',
575 related_name='+')
83b7692b 576
577
578### RÉFÉRENCES RH
579
6e4600ef 580class FamilleEmploi(Metadata):
581 """Catégorie utilisée dans la gestion des Postes.
582 Catégorie supérieure à TypePoste.
583 """
e9bbd6ba 584 nom = models.CharField(max_length=255)
6e4600ef 585
586 def __unicode__(self):
587 return u'%s' % (self.nom)
e9bbd6ba 588
6e4600ef 589class TypePoste(Metadata):
590 """Catégorie de Poste.
591 """
e9bbd6ba 592 nom = models.CharField(max_length=255)
593 nom_feminin = models.CharField(max_length=255)
6e4600ef 594
595 is_responsable = models.BooleanField(default=False)
9afaa55e 596 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 597 db_column='famille_emploi',
598 related_name='+')
e9bbd6ba 599
6e4600ef 600 class Meta:
601 ordering = ['nom']
602
e9bbd6ba 603 def __unicode__(self):
6e4600ef 604 # TODO : gérer nom féminin
605 return u'%s' % (self.nom)
e9bbd6ba 606
607
608TYPE_PAIEMENT_CHOICES = (
609 ('Régulier', 'Régulier'),
610 ('Ponctuel', 'Ponctuel'),
611)
612
613NATURE_REMUNERATION_CHOICES = (
614 ('Accessoire', 'Accessoire'),
615 ('Charges', 'Charges'),
616 ('Indemnité', 'Indemnité'),
7abc6d45 617 ('RAS', 'Rémunération autre source'),
e9bbd6ba 618 ('Traitement', 'Traitement'),
619)
620
6e4600ef 621class TypeRemuneration(Metadata):
622 """Catégorie de Remuneration.
623 """
e9bbd6ba 624 nom = models.CharField(max_length=255)
9afaa55e 625 type_paiement = models.CharField(max_length=30,
626 choices=TYPE_PAIEMENT_CHOICES)
627 nature_remuneration = models.CharField(max_length=30,
628 choices=NATURE_REMUNERATION_CHOICES)
9afaa55e 629
630 def __unicode__(self):
6e4600ef 631 return u'%s' % (self.nom)
e9bbd6ba 632
6e4600ef 633class TypeRevalorisation(Metadata):
7abc6d45 634 """Justification du changement de la Remuneration.
6e4600ef 635 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 636 """
e9bbd6ba 637 nom = models.CharField(max_length=255)
e9bbd6ba 638
639 def __unicode__(self):
6e4600ef 640 return u'%s' % (self.nom)
641
642class Service(Metadata):
643 """Unité administrative où les Postes sont rattachés.
644 """
645 nom = models.CharField(max_length=255)
9afaa55e 646
647 class Meta:
648 ordering = ['nom']
e9bbd6ba 649
6e4600ef 650 def __unicode__(self):
651 return u'%s' % (self.nom)
652
e9bbd6ba 653
654TYPE_ORGANISME_CHOICES = (
655 ('MAD', 'Mise à disposition'),
656 ('DET', 'Détachement'),
657)
658
6e4600ef 659class OrganismeBstg(Metadata):
660 """Organisation d'où provient un Employe mis à disposition (MAD) de
661 ou détaché (DET) à l'AUF à titre gratuit.
662
663 (BSTG = bien et service à titre gratuit.)
664 """
e9bbd6ba 665 nom = models.CharField(max_length=255)
666 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
6e4600ef 667 pays = models.ForeignKey(ref.Pays, to_field='code',
668 db_column='pays',
669 related_name='organismes_bstg',
670 null=True, blank=True)
9afaa55e 671
672 class Meta:
673 ordering = ['type', 'nom']
674
6e4600ef 675 def __unicode__(self):
676 return u'%s (%s)' % (self.nom, self.type)
83b7692b 677
6e4600ef 678class Statut(Metadata):
679 """Statut de l'Employe dans le cadre d'un Dossier particulier.
680 """
9afaa55e 681 # Identification
e9bbd6ba 682 code = models.CharField(max_length=25, unique=True)
683 nom = models.CharField(max_length=255)
e9bbd6ba 684
6e4600ef 685 class Meta:
686 ordering = ['code']
687
9afaa55e 688 def __unicode__(self):
689 return u'%s : %s' % (self.code, self.nom)
690
83b7692b 691
e9bbd6ba 692TYPE_CLASSEMENT_CHOICES = (
6e4600ef 693 ('S', 'S -Soutien'),
694 ('T', 'T - Technicien'),
695 ('P', 'P - Professionel'),
696 ('C', 'C - Cadre'),
697 ('D', 'D - Direction'),
698 ('SO', 'SO - Sans objet [expatriés]'),
699 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 700)
83b7692b 701
6e4600ef 702class Classement(Metadata):
703 """Éléments de classement de la
704 "Grille générique de classement hiérarchique".
705
706 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
707 classement dans la grille. Le classement donne le coefficient utilisé dans:
708
709 salaire de base = coefficient * valeur du point de l'Implantation du Poste
710 """
9afaa55e 711 # Identification
e9bbd6ba 712 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
713 echelon = models.IntegerField()
714 degre = models.IntegerField()
6e4600ef 715 coefficient = models.FloatField(default=0)
9afaa55e 716 # Méta
6e4600ef 717 # annee # au lieu de date_debut et date_fin
718 commentaire = models.TextField(null=True, blank=True)
719
720 class Meta:
721 ordering = ['type','echelon','degre','coefficient']
e9bbd6ba 722
723 def __unicode__(self):
724 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
725 self.coefficient)
726
6e4600ef 727class TauxChange(Metadata):
7abc6d45 728 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 729 pour chaque année budgétaire.
7abc6d45 730 """
9afaa55e 731 # Identification
6e4600ef 732 devise = models.ForeignKey('Devise', to_field='code', db_column='devise',
733 related_name='+')
e9bbd6ba 734 annee = models.IntegerField()
9afaa55e 735 taux = models.FloatField()
6e4600ef 736
737 class Meta:
738 ordering = ['annee', 'devise__code']
739
740 def __unicode__(self):
741 return u'%s : %s €' % (self.devise.code, self.taux)
e9bbd6ba 742
6e4600ef 743class ValeurPoint(Metadata):
744 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
745 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
746 du POste de ce Dossier : dossier.poste.implantation (pseudo code).
747
748 salaire de base = coefficient * valeur du point de l'Implantation du Poste
749 """
9afaa55e 750 valeur = models.FloatField()
6e4600ef 751 devise = models.ForeignKey('Devise', db_column='devise',
752 related_name='+', default=5)
83b7692b 753 implantation = models.ForeignKey(ref.Implantation,
9afaa55e 754 db_column='implantation',
6e4600ef 755 related_name='valeur_point')
9afaa55e 756 # Méta
e9bbd6ba 757 annee = models.IntegerField()
9afaa55e 758
2d4d2fcf 759 # Stockage de tous les taux de change
760 # pour optimiser la recherche de la devise associée
9afaa55e 761 annee_courante = datetime.datetime.now().year
6e4600ef 762 tauxchange = TauxChange.objects.select_related('devise') \
763 .filter(annee=annee_courante)
9afaa55e 764
6e4600ef 765 class Meta:
766 ordering = ['annee', 'implantation__nom']
767
9afaa55e 768 def __unicode__(self):
769 tx = self.get_tauxchange_courant()
770 if tx:
771 devise_code = tx.devise.code
772 else:
773 devise_code = "??"
2d4d2fcf 774 return u'%s %s (%s-%s)' % (self.valeur, devise_code,
775 self.implantation_id, self.annee)
9afaa55e 776
777 class Meta:
778 ordering = ['valeur']
e9bbd6ba 779
6e4600ef 780class Devise(Metadata):
781 """Devise monétaire.
782 """
e9bbd6ba 783 code = models.CharField(max_length=10, unique=True)
784 nom = models.CharField(max_length=255)
785
6e4600ef 786 class Meta:
787 ordering = ['code']
788
e9bbd6ba 789 def __unicode__(self):
790 return u'%s - %s' % (self.code, self.nom)
791
6e4600ef 792class TypeContrat(Metadata):
793 """Type de contrat.
794 """
e9bbd6ba 795 nom = models.CharField(max_length=255)
6e4600ef 796 nom_long = models.CharField(max_length=255)
49f9f116 797
9afaa55e 798 def __unicode__(self):
799 return u'%s' % (self.nom)
30be56d5 800
2d4d2fcf 801
802### AUTRES
803
6e4600ef 804class ResponsableImplantation(Metadata):
30be56d5 805 """Le responsable d'une implantation.
806 Anciennement géré sur le Dossier du responsable.
807 """
6e4600ef 808 employe = models.ForeignKey('Employe', db_column='employe',
809 related_name='+',
810 null=True, blank=True)
811 implantation = models.ForeignKey(ref.Implantation,
812 db_column='implantation', related_name='+',
813 unique=True)
30be56d5 814
815 def __unicode__(self):
816 return u'%s : %s' % (self.implantation, self.employe)
817
818 class Meta:
819 ordering = ['implantation__nom']