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