fix dernier salaire au lieu de premier
[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
8c1ae2b3 13HELP_TEXT_DATE = "format: aaaa-mm-jj"
6e4600ef 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.
15ea717e 34 supprime == True : objet réputé supprimé.
2d4d2fcf 35 """
36 actif = models.BooleanField(default=True)
15ea717e 37 supprime = models.BooleanField(default=False)
2d4d2fcf 38 date_creation = models.DateField(auto_now_add=True)
6e4600ef 39 user_creation = models.ForeignKey('auth.User',
40 db_column='user_creation', related_name='+',
50fa9bc1 41 null=True, blank=True)
07b40eda 42 date_modification = models.DateField(auto_now=True)
6e4600ef 43 user_modification = models.ForeignKey('auth.User',
44 db_column='user_modification', related_name='+',
50fa9bc1
OL
45 null=True, blank=True)
46 date_desactivation = models.DateField(null=True, blank=True)
6e4600ef 47 user_desactivation = models.ForeignKey('auth.User',
48 db_column='user_desactivation', related_name='+',
50fa9bc1 49 null=True, blank=True)
07b40eda 50
51 class Meta:
52 abstract = True
53
2d4d2fcf 54class Commentaire(Metadata):
55 texte = models.TextField()
6e4600ef 56 owner = models.ForeignKey('auth.User', db_column='owner', related_name='+')
2d4d2fcf 57
58 class Meta:
59 abstract = True
6e4600ef 60 ordering = ['-date_creation']
61
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
769a0755 73class Poste_(Metadata):
6e4600ef 74 """Un Poste est un emploi (job) à combler dans une implantation.
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 """
83b7692b 78 # Identification
2d4d2fcf 79 nom = models.CharField(max_length=255,
8c1ae2b3 80 verbose_name="Titre du poste", )
2d4d2fcf 81 nom_feminin = models.CharField(max_length=255,
8c1ae2b3 82 verbose_name="Titre du poste (au féminin)",
6e4600ef 83 null=True)
84 implantation = models.ForeignKey(ref.Implantation,
85 db_column='implantation', related_name='+')
86 type_poste = models.ForeignKey('TypePoste', db_column='type_poste',
87 related_name='+',
88 null=True)
89 service = models.ForeignKey('Service', db_column='service',
90 related_name='+',
8c1ae2b3 91 verbose_name="Direction/Service/Pôle support",
6e4600ef 92 default=1) # default = Rectorat
93 responsable = models.ForeignKey('Poste', db_column='responsable',
94 related_name='+',
8c1ae2b3 95 verbose_name="Poste du responsable",
6e4600ef 96 default=149) # default = Recteur
83b7692b 97
98 # Contrat
99 regime_travail = models.DecimalField(max_digits=12, decimal_places=2,
2d4d2fcf 100 default=REGIME_TRAVAIL_DEFAULT,
8c1ae2b3 101 verbose_name="Temps de travail",
102 help_text="% du temps complet")
83b7692b 103 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 104 decimal_places=2,
105 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
8c1ae2b3 106 verbose_name="Nb. heures par semaine")
83b7692b 107
108 # Recrutement
8c1ae2b3 109 local = models.BooleanField(verbose_name="Local", default=True,
6e4600ef 110 blank=True)
8c1ae2b3 111 expatrie = models.BooleanField(verbose_name="Expatrié", default=False,
83b7692b 112 blank=True)
6e4600ef 113 mise_a_disposition = models.BooleanField(
8c1ae2b3 114 verbose_name="Mise à disposition",
6e4600ef 115 default=False)
116 appel = models.CharField(max_length=10,
8c1ae2b3 117 verbose_name="Appel à candidature",
6e4600ef 118 choices=POSTE_APPEL_CHOICES,
119 default='interne')
83b7692b 120
121 # Rémunération
6e4600ef 122 classement_min = models.ForeignKey('Classement',
123 db_column='classement_min', related_name='+',
124 null=True, blank=True)
125 classement_max = models.ForeignKey('Classement',
126 db_column='classement_max', related_name='+',
127 null=True, blank=True)
128 valeur_point_min = models.ForeignKey('ValeurPoint',
129 db_column='valeur_point_min', related_name='+',
130 null=True, blank=True)
131 valeur_point_max = models.ForeignKey('ValeurPoint',
132 db_column='valeur_point_max', related_name='+',
133 null=True, blank=True)
134 devise_min = models.ForeignKey('Devise', db_column='devise_min',
135 related_name='+', default=5)
136 devise_max = models.ForeignKey('Devise', db_column='devise_max',
137 related_name='+', default=5)
83b7692b 138 salaire_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 139 default=0)
83b7692b 140 salaire_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 141 default=0)
83b7692b 142 indemn_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 143 default=0)
83b7692b 144 indemn_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 145 default=0)
83b7692b 146 autre_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 147 default=0)
83b7692b 148 autre_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 149 default=0)
83b7692b 150
151 # Comparatifs de rémunération
6e4600ef 152 devise_comparaison = models.ForeignKey('Devise',
153 db_column='devise_comparaison',
154 related_name='+',
155 default=5)
83b7692b 156 comp_locale_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 157 null=True, blank=True)
83b7692b 158 comp_locale_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 159 null=True, blank=True)
83b7692b 160 comp_universite_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 161 null=True, blank=True)
83b7692b 162 comp_universite_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 163 null=True, blank=True)
83b7692b 164 comp_fonctionpub_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 165 null=True, blank=True)
83b7692b 166 comp_fonctionpub_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 167 null=True, blank=True)
83b7692b 168 comp_ong_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 169 null=True, blank=True)
83b7692b 170 comp_ong_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 171 null=True, blank=True)
83b7692b 172 comp_autre_min = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 173 null=True, blank=True)
83b7692b 174 comp_autre_max = models.DecimalField(max_digits=12, decimal_places=2,
6e4600ef 175 null=True, blank=True)
83b7692b 176
177 # Justification
6e4600ef 178 justification = models.TextField(null=True, blank=True)
83b7692b 179
2d4d2fcf 180 # Autres Metadata
07b40eda 181 date_validation = models.DateTimeField(null=True, blank=True) # de dae
8c1ae2b3 182 date_debut = models.DateField(verbose_name="Date de début",
6e4600ef 183 help_text=HELP_TEXT_DATE)
8c1ae2b3 184 date_fin = models.DateField(verbose_name="Date de fin",
6e4600ef 185 help_text=HELP_TEXT_DATE,
186 null=True, blank=True)
187
188 class Meta:
769a0755 189 abstract = True
6e4600ef 190 ordering = ['implantation__nom', 'nom']
8c1ae2b3 191 verbose_name = "Poste"
192 verbose_name_plural = "Postes"
6e4600ef 193
83b7692b 194 def __unicode__(self):
8c1ae2b3 195 representation = u'%s - %s [%s]' % (self.implantation, self.nom,
196 self.id)
197 if self.is_vacant():
198 representation = representation + u' (vacant)'
199 return representation
200
201 def is_vacant(self):
202 # TODO : si existe un dossier actif pour ce poste, return False
203 # self.dossier_set.all() fonctionne pas
204 return False
83b7692b 205
206
37868f0b
NC
207class Poste(Poste_):
208 __doc__ = Poste_.__doc__
83b7692b 209
210
769a0755
NC
211class Poste(Poste_):
212 __doc__ = Poste_.__doc__
213
214
83b7692b 215POSTE_FINANCEMENT_CHOICES = (
216 ('A', 'A - Frais de personnel'),
217 ('B', 'B - Projet(s)-Titre(s)'),
218 ('C', 'C - Autre')
219)
220
f31ddfa0
NC
221
222class PosteFinancement_(models.Model):
6e4600ef 223 """Pour un Poste, structure d'informations décrivant comment on prévoit
224 financer ce Poste.
225 """
226 poste = models.ForeignKey('Poste', db_column='poste',
f31ddfa0 227 related_name='%(app_label)s_financements')
83b7692b 228 type = models.CharField(max_length=1, choices=POSTE_FINANCEMENT_CHOICES)
229 pourcentage = models.DecimalField(max_digits=12, decimal_places=2,
8c1ae2b3 230 help_text="ex.: 33.33 % (décimale avec point)")
83b7692b 231 commentaire = models.TextField(
8c1ae2b3 232 help_text="Spécifiez la source de financement.")
83b7692b 233
234 class Meta:
f31ddfa0 235 abstract = True
83b7692b 236 ordering = ['type']
6e4600ef 237
238 def __unicode__(self):
239 return u'%s : %s %' % (self.type, self.pourcentage)
83b7692b 240
f31ddfa0
NC
241
242class PosteFinancement(PosteFinancement_):
243 __doc__ = PosteFinancement_.__doc__
244
245
83b7692b 246class PostePiece(models.Model):
6e4600ef 247 """Documents relatifs au Poste.
7abc6d45 248 Ex.: Description de poste
249 """
8c1ae2b3 250 poste = models.ForeignKey('Poste', db_column='poste',
6e4600ef 251 related_name='pieces')
8c1ae2b3 252 nom = models.CharField(verbose_name="Nom", max_length=255)
253 fichier = models.FileField(verbose_name="Fichier",
83b7692b 254 upload_to=poste_piece_dispatch,
255 storage=storage_prive)
256
6e4600ef 257 class Meta:
258 ordering = ['nom']
259
260 def __unicode__(self):
261 return u'%s' % (self.nom)
262
07b40eda 263class PosteCommentaire(Commentaire):
8c1ae2b3 264 poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
83b7692b 265
2d4d2fcf 266
83b7692b 267### EMPLOYÉ/PERSONNE
e9bbd6ba 268
269GENRE_CHOICES = (
270 ('M', 'Homme'),
271 ('F', 'Femme'),
272)
273SITUATION_CHOICES = (
274 ('C', 'Célibataire'),
275 ('F', 'Fiancé'),
276 ('M', 'Marié'),
277)
278
2d4d2fcf 279class Employe(Metadata):
6e4600ef 280 """Personne occupant ou ayant occupé un Poste. Un Employe aura autant de
281 Dossiers qu'il occupe ou a occupé de Postes.
282
283 Cette classe aurait pu avantageusement s'appeler Personne car la notion
284 d'employé n'a pas de sens si aucun Dossier n'existe pour une personne.
285 """
9afaa55e 286 # Identification
e9bbd6ba 287 nom = models.CharField(max_length=255)
8c1ae2b3 288 prenom = models.CharField(max_length=255, verbose_name="Prénom")
6e4600ef 289 nom_affichage = models.CharField(max_length=255,
8c1ae2b3 290 verbose_name="Nom d'affichage",
6e4600ef 291 null=True, blank=True)
83b7692b 292 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 293 db_column='nationalite',
8c1ae2b3 294 related_name='employes_nationalite',
295 verbose_name="Nationalité")
6e4600ef 296 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 297 verbose_name="Date de naissance",
6e4600ef 298 null=True, blank=True)
2d4d2fcf 299 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 300
9afaa55e 301 # Infos personnelles
6e4600ef 302 situation_famille = models.CharField(max_length=1,
303 choices=SITUATION_CHOICES,
8c1ae2b3 304 verbose_name="Situation familiale",
6e4600ef 305 null=True, blank=True)
8c1ae2b3 306 date_entree = models.DateField(verbose_name="Date d'entrée à l'AUF",
6e4600ef 307 help_text=HELP_TEXT_DATE,
7abc6d45 308 null=True, blank=True)
83b7692b 309
9afaa55e 310 # Coordonnées
8c1ae2b3 311 tel_domicile = models.CharField(max_length=255,
312 verbose_name="Tél. domicile",
313 null=True, blank=True)
314 tel_cellulaire = models.CharField(max_length=255,
315 verbose_name="Tél. cellulaire",
316 null=True, blank=True)
e9bbd6ba 317 adresse = models.CharField(max_length=255, null=True, blank=True)
e9bbd6ba 318 ville = models.CharField(max_length=255, null=True, blank=True)
319 province = models.CharField(max_length=255, null=True, blank=True)
320 code_postal = models.CharField(max_length=255, null=True, blank=True)
6e4600ef 321 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
322 related_name='employes',
323 null=True, blank=True)
9afaa55e 324
6e4600ef 325 class Meta:
67666927 326 ordering = ['nom_affichage','nom','prenom']
8c1ae2b3 327 verbose_name = "Employé"
328 verbose_name_plural = "Employés"
6e4600ef 329
9afaa55e 330 def __unicode__(self):
8c1ae2b3 331 return u'%s' % (self.get_nom())
332
333 def get_nom(self):
ebc8eb32 334 nom_affichage = self.nom_affichage
335 if not nom_affichage:
336 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
8c1ae2b3 337 return nom_affichage
9afaa55e 338
7abc6d45 339class EmployePiece(models.Model):
6e4600ef 340 """Documents relatifs à un employé.
7abc6d45 341 Ex.: CV...
342 """
8c1ae2b3 343 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 344 related_name='+')
8c1ae2b3 345 nom = models.CharField(verbose_name="Nom", max_length=255)
346 fichier = models.FileField(verbose_name="Fichier",
7abc6d45 347 upload_to=dossier_piece_dispatch,
348 storage=storage_prive)
349
6e4600ef 350 class Meta:
351 ordering = ['nom']
352
353 def __unicode__(self):
354 return u'%s' % (self.nom)
355
07b40eda 356class EmployeCommentaire(Commentaire):
8c1ae2b3 357 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 358 related_name='+')
9afaa55e 359
2d4d2fcf 360
e9bbd6ba 361LIEN_PARENTE_CHOICES = (
362 ('Conjoint', 'Conjoint'),
363 ('Conjointe', 'Conjointe'),
364 ('Fille', 'Fille'),
365 ('Fils', 'Fils'),
366)
367
2d4d2fcf 368class AyantDroit(Metadata):
6e4600ef 369 """Personne en relation avec un Employe.
370 """
9afaa55e 371 # Identification
e9bbd6ba 372 nom = models.CharField(max_length=255)
8c1ae2b3 373 prenom = models.CharField(max_length=255,
374 verbose_name="Prénom",)
6e4600ef 375 nom_affichage = models.CharField(max_length=255,
8c1ae2b3 376 verbose_name="Nom d'affichage",
6e4600ef 377 null=True, blank=True)
2d4d2fcf 378 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 379 db_column='nationalite',
8c1ae2b3 380 related_name='ayantdroits_nationalite',
381 verbose_name="Nationalité")
6e4600ef 382 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 383 verbose_name="Date de naissance",
6e4600ef 384 null=True, blank=True)
2d4d2fcf 385 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 386
9afaa55e 387 # Relation
388 employe = models.ForeignKey('Employe', db_column='employe',
8c1ae2b3 389 related_name='ayantdroits',
390 verbose_name="Employé")
6e4600ef 391 lien_parente = models.CharField(max_length=10,
392 choices=LIEN_PARENTE_CHOICES,
8c1ae2b3 393 verbose_name="Lien de parenté",
6e4600ef 394 null=True, blank=True)
395
396 class Meta:
397 ordering = ['nom_affichage']
8c1ae2b3 398 verbose_name = "Ayant droit"
399 verbose_name_plural = "Ayants droit"
400
6e4600ef 401 def __unicode__(self):
8c1ae2b3 402 return u'%s' % (self.get_nom())
403
404 def get_nom(self):
405 nom_affichage = self.nom_affichage
406 if not nom_affichage:
407 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
408 return nom_affichage
83b7692b 409
07b40eda 410class AyantDroitCommentaire(Commentaire):
8c1ae2b3 411 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
6e4600ef 412 related_name='+')
83b7692b 413
2d4d2fcf 414
83b7692b 415### DOSSIER
416
417STATUT_RESIDENCE_CHOICES = (
418 ('local', 'Local'),
419 ('expat', 'Expatrié'),
420)
421
422COMPTE_COMPTA_CHOICES = (
423 ('coda', 'CODA'),
424 ('scs', 'SCS'),
425 ('aucun', 'Aucun'),
426)
427
769a0755 428class Dossier_(Metadata):
6e4600ef 429 """Le Dossier regroupe les informations relatives à l'occupation
430 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
431 par un Employe.
432
433 Plusieurs Contrats peuvent être associés au Dossier.
434 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
435 lequel aucun Dossier n'existe est un poste vacant.
436 """
83b7692b 437 # Identification
6e4600ef 438 employe = models.ForeignKey('Employe', db_column='employe',
8c1ae2b3 439 related_name='+',
440 verbose_name="Employé")
6e4600ef 441 poste = models.ForeignKey('Poste', db_column='poste',
442 related_name='+', editable=False)
443 statut = models.ForeignKey('Statut', related_name='+', default=3)
83b7692b 444 organisme_bstg = models.ForeignKey('OrganismeBstg',
6e4600ef 445 db_column='organisme_bstg',
446 related_name='+',
8c1ae2b3 447 verbose_name="Organisme",
448 help_text="Si détaché (DET) ou \
6e4600ef 449 mis à disposition (MAD), \
450 préciser l'organisme.",
451 null=True, blank=True)
452
83b7692b 453 # Recrutement
2d4d2fcf 454 remplacement = models.BooleanField(default=False)
83b7692b 455 statut_residence = models.CharField(max_length=10, default='local',
8c1ae2b3 456 verbose_name="Statut",
2d4d2fcf 457 choices=STATUT_RESIDENCE_CHOICES)
83b7692b 458
459 # Rémunération
6e4600ef 460 classement = models.ForeignKey('Classement', db_column='classement',
461 related_name='+',
462 null=True, blank=True)
2d4d2fcf 463 regime_travail = models.DecimalField(max_digits=12,
464 decimal_places=2,
465 default=REGIME_TRAVAIL_DEFAULT,
8c1ae2b3 466 verbose_name="Régime de travail",
467 help_text="% du temps complet")
83b7692b 468 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
2d4d2fcf 469 decimal_places=2,
470 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
8c1ae2b3 471 verbose_name="Nb. heures par semaine")
7abc6d45 472
473 # Occupation du Poste par cet Employe (anciennement "mandat")
8c1ae2b3 474 date_debut = models.DateField(verbose_name="Date de début d'occupation \
6e4600ef 475 de poste",
476 help_text=HELP_TEXT_DATE)
8c1ae2b3 477 date_fin = models.DateField(verbose_name="Date de fin d'occupation \
6e4600ef 478 de poste",
2d4d2fcf 479 help_text=HELP_TEXT_DATE,
6e4600ef 480 null=True, blank=True)
e9bbd6ba 481
2d4d2fcf 482 # Comptes
483 # TODO?
83b7692b 484
6e4600ef 485 class Meta:
769a0755 486 abstract = True
67666927 487 ordering = ['employe__nom_affichage', 'employe__nom', 'poste__nom']
8c1ae2b3 488 verbose_name = "Dossier"
489 verbose_name_plural = "Dossiers"
6e4600ef 490
83b7692b 491 def __unicode__(self):
8c1ae2b3 492 poste = self.poste.nom
493 if self.employe.genre == 'F':
494 poste = self.poste.nom_feminin
495 return u'%s - %s' % (self.employe, poste)
83b7692b 496
37868f0b
NC
497
498class Dossier(Dossier_):
499 __doc__ = Dossier_.__doc__
500
83b7692b 501
769a0755
NC
502
503class Dossier(Dossier_):
504 __doc__ = Dossier_.__doc__
505
506
83b7692b 507class DossierPiece(models.Model):
7abc6d45 508 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
509 Ex.: Lettre de motivation.
510 """
8c1ae2b3 511 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 512 related_name='+')
8c1ae2b3 513 nom = models.CharField(verbose_name="Nom", max_length=255)
514 fichier = models.FileField(verbose_name="Fichier",
83b7692b 515 upload_to=dossier_piece_dispatch,
516 storage=storage_prive)
517
6e4600ef 518 class Meta:
519 ordering = ['nom']
520
521 def __unicode__(self):
522 return u'%s' % (self.nom)
523
07b40eda 524class DossierCommentaire(Commentaire):
8c1ae2b3 525 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 526 related_name='+')
83b7692b 527
2d4d2fcf 528
07b40eda 529### RÉMUNÉRATION
e9bbd6ba 530
2d4d2fcf 531class RemunerationMixin(Metadata):
9afaa55e 532 # Identification
6e7c919b
NC
533 dossier = models.ForeignKey('Dossier', db_column='dossier',
534 related_name='%(app_label)s_%(class)s_remunerations')
83b7692b 535 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 536 related_name='+',
537 verbose_name="Type de rémunération")
7abc6d45 538 type_revalorisation = models.ForeignKey('TypeRevalorisation',
539 db_column='type_revalorisation',
6e4600ef 540 related_name='+',
8c1ae2b3 541 verbose_name="Type de revalorisation",
7abc6d45 542 null=True, blank=True)
50fa9bc1 543 montant = models.FloatField(null=True, blank=True,
6e4600ef 544 default=0)
2d4d2fcf 545 # Annuel (12 mois, 52 semaines, 364 jours?)
c589d980 546 devise = models.ForeignKey('Devise', to_field='id',
6e4600ef 547 db_column='devise', related_name='+',
548 default=5)
2d4d2fcf 549 # commentaire = precision
550 commentaire = models.CharField(max_length=255, null=True, blank=True)
551 # date_debut = anciennement date_effectif
8c1ae2b3 552 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
553 verbose_name="Date de début",
6e4600ef 554 null=True, blank=True)
555 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 556 verbose_name="Date de fin",
6e4600ef 557 null=True, blank=True)
83b7692b 558
2d4d2fcf 559 class Meta:
560 abstract = True
6e4600ef 561 ordering = ['type__nom', '-date_fin']
562
563 def __unicode__(self):
564 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
2d4d2fcf 565
f31ddfa0 566class Remuneration_(RemunerationMixin):
2d4d2fcf 567 """Structure de rémunération (données budgétaires) en situation normale
568 pour un Dossier. Si un Evenement existe, utiliser la structure de
569 rémunération EvenementRemuneration de cet événement.
570 """
83b7692b 571
572 def montant_mois(self):
573 return round(self.montant / 12, 2)
574
575 def taux_devise(self):
576 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
577
578 def montant_euro(self):
579 return round(float(self.montant) / float(self.taux_devise()), 2)
580
581 def montant_euro_mois(self):
582 return round(self.montant_euro() / 12, 2)
9afaa55e 583
584 def __unicode__(self):
585 try:
586 devise = self.devise.code
587 except:
588 devise = "???"
589 return "%s %s" % (self.montant, devise)
83b7692b 590
f31ddfa0
NC
591 class Meta:
592 abstract = True
8c1ae2b3 593 verbose_name = "Rémunération"
594 verbose_name_plural = "Rémunérations"
f31ddfa0
NC
595
596
597class Remuneration(Remuneration_):
598 __doc__ = Remuneration_.__doc__
599
2d4d2fcf 600
601### CONTRATS
602
603class Contrat(Metadata):
604 """Document juridique qui encadre la relation de travail d'un Employe
605 pour un Poste particulier. Pour un Dossier (qui documente cette
606 relation de travail) plusieurs contrats peuvent être associés.
607 """
6e4600ef 608 dossier = models.ForeignKey('Dossier', db_column='dossier',
609 related_name='+')
610 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 611 related_name='+',
612 verbose_name="Type de contrat")
613 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
614 verbose_name="Date de début")
6e4600ef 615 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 616 verbose_name="Date de fin",
6e4600ef 617 null=True, blank=True)
618
619 class Meta:
620 ordering = ['dossier__employe__nom_affichage']
8c1ae2b3 621 verbose_name = "Contrat"
622 verbose_name_plural = "Contrats"
6e4600ef 623
624 def __unicode__(self):
8c1ae2b3 625 return u'%s - %s' % (self.dossier, self.id)
6e4600ef 626
627# TODO? class ContratPiece(models.Model):
2d4d2fcf 628
629
630### ÉVÉNEMENTS
631
f31ddfa0 632class Evenement_(Metadata):
6e4600ef 633 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
634 d'un Dossier qui vient altérer des informations normales liées à un Dossier
635 (ex.: la Remuneration).
636
637 Ex.: congé de maternité, maladie...
638
639 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
640 différent et une rémunération en conséquence. On souhaite toutefois
641 conserver le Dossier intact afin d'éviter une re-saisie des données lors
642 du retour à la normale.
643 """
8c1ae2b3 644 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 645 related_name='+')
646 nom = models.CharField(max_length=255)
8c1ae2b3 647 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
648 verbose_name="Date de début")
649 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
650 verbose_name="Date de fin",
6e4600ef 651 null=True, blank=True)
f31ddfa0 652
6e4600ef 653 class Meta:
f31ddfa0 654 abstract = True
6e4600ef 655 ordering = ['nom']
8c1ae2b3 656 verbose_name = "Évènement"
657 verbose_name_plural = "Évènements"
6e4600ef 658
659 def __unicode__(self):
660 return u'%s' % (self.nom)
f31ddfa0
NC
661
662
663class Evenement(Evenement_):
664 __doc__ = Evenement_.__doc__
665
2d4d2fcf 666
f31ddfa0 667class EvenementRemuneration_(RemunerationMixin):
6e4600ef 668 """Structure de rémunération liée à un Evenement qui remplace
669 temporairement la Remuneration normale d'un Dossier, pour toute la durée
670 de l'Evenement.
671 """
672 evenement = models.ForeignKey("Evenement", db_column='evenement',
8c1ae2b3 673 related_name='+',
674 verbose_name="Évènement")
675 # TODO : le champ dossier hérité de Remuneration doit être dérivé
676 # de l'Evenement associé
83b7692b 677
6e7c919b
NC
678 class Meta:
679 abstract = True
8c1ae2b3 680 ordering = ['evenement', 'type__nom', '-date_fin']
681 verbose_name = "Évènement - rémunération"
682 verbose_name_plural = "Évènements - rémunérations"
6e7c919b
NC
683
684
685class EvenementRemuneration(EvenementRemuneration_):
686 __doc__ = EvenementRemuneration_.__doc__
83b7692b 687
f31ddfa0
NC
688 class Meta:
689 abstract = True
690
691
692class EvenementRemuneration(EvenementRemuneration_):
693 __doc__ = EvenementRemuneration_.__doc__
694
83b7692b 695
696### RÉFÉRENCES RH
697
6e4600ef 698class FamilleEmploi(Metadata):
699 """Catégorie utilisée dans la gestion des Postes.
700 Catégorie supérieure à TypePoste.
701 """
e9bbd6ba 702 nom = models.CharField(max_length=255)
6e4600ef 703
8c1ae2b3 704 class Meta:
705 ordering = ['nom']
706 verbose_name = "Famille d'emploi"
707 verbose_name_plural = "Familles d'emploi"
708
6e4600ef 709 def __unicode__(self):
710 return u'%s' % (self.nom)
e9bbd6ba 711
6e4600ef 712class TypePoste(Metadata):
713 """Catégorie de Poste.
714 """
e9bbd6ba 715 nom = models.CharField(max_length=255)
8c1ae2b3 716 nom_feminin = models.CharField(max_length=255,
717 verbose_name="Nom féminin")
6e4600ef 718
8c1ae2b3 719 is_responsable = models.BooleanField(default=False,
720 verbose_name="Poste de responsabilité")
9afaa55e 721 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 722 db_column='famille_emploi',
8c1ae2b3 723 related_name='+',
724 verbose_name="Famille d'emploi")
e9bbd6ba 725
6e4600ef 726 class Meta:
727 ordering = ['nom']
8c1ae2b3 728 verbose_name = "Type de poste"
729 verbose_name_plural = "Types de poste"
6e4600ef 730
e9bbd6ba 731 def __unicode__(self):
6e4600ef 732 return u'%s' % (self.nom)
e9bbd6ba 733
734
735TYPE_PAIEMENT_CHOICES = (
736 ('Régulier', 'Régulier'),
737 ('Ponctuel', 'Ponctuel'),
738)
739
740NATURE_REMUNERATION_CHOICES = (
741 ('Accessoire', 'Accessoire'),
742 ('Charges', 'Charges'),
743 ('Indemnité', 'Indemnité'),
7abc6d45 744 ('RAS', 'Rémunération autre source'),
e9bbd6ba 745 ('Traitement', 'Traitement'),
746)
747
6e4600ef 748class TypeRemuneration(Metadata):
749 """Catégorie de Remuneration.
750 """
e9bbd6ba 751 nom = models.CharField(max_length=255)
9afaa55e 752 type_paiement = models.CharField(max_length=30,
8c1ae2b3 753 choices=TYPE_PAIEMENT_CHOICES,
754 verbose_name="Type de paiement")
9afaa55e 755 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 756 choices=NATURE_REMUNERATION_CHOICES,
757 verbose_name="Nature de la rémunération")
758
759 class Meta:
760 ordering = ['nom']
761 verbose_name = "Type de rémunération"
762 verbose_name_plural = "Types de rémunération"
9afaa55e 763
764 def __unicode__(self):
6e4600ef 765 return u'%s' % (self.nom)
e9bbd6ba 766
6e4600ef 767class TypeRevalorisation(Metadata):
7abc6d45 768 """Justification du changement de la Remuneration.
6e4600ef 769 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 770 """
e9bbd6ba 771 nom = models.CharField(max_length=255)
8c1ae2b3 772
773 class Meta:
774 ordering = ['nom']
775 verbose_name = "Type de revalorisation"
776 verbose_name_plural = "Types de revalorisation"
e9bbd6ba 777
778 def __unicode__(self):
6e4600ef 779 return u'%s' % (self.nom)
780
781class Service(Metadata):
782 """Unité administrative où les Postes sont rattachés.
783 """
784 nom = models.CharField(max_length=255)
9afaa55e 785
786 class Meta:
787 ordering = ['nom']
8c1ae2b3 788 verbose_name = "Service"
789 verbose_name_plural = "Services"
e9bbd6ba 790
6e4600ef 791 def __unicode__(self):
792 return u'%s' % (self.nom)
793
e9bbd6ba 794
795TYPE_ORGANISME_CHOICES = (
796 ('MAD', 'Mise à disposition'),
797 ('DET', 'Détachement'),
798)
799
6e4600ef 800class OrganismeBstg(Metadata):
801 """Organisation d'où provient un Employe mis à disposition (MAD) de
802 ou détaché (DET) à l'AUF à titre gratuit.
803
804 (BSTG = bien et service à titre gratuit.)
805 """
e9bbd6ba 806 nom = models.CharField(max_length=255)
807 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
6e4600ef 808 pays = models.ForeignKey(ref.Pays, to_field='code',
809 db_column='pays',
810 related_name='organismes_bstg',
811 null=True, blank=True)
9afaa55e 812
813 class Meta:
814 ordering = ['type', 'nom']
8c1ae2b3 815 verbose_name = "Organisme BSTG"
816 verbose_name_plural = "Organismes BSTG"
9afaa55e 817
6e4600ef 818 def __unicode__(self):
8c1ae2b3 819 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 820
6e4600ef 821class Statut(Metadata):
822 """Statut de l'Employe dans le cadre d'un Dossier particulier.
823 """
9afaa55e 824 # Identification
e9bbd6ba 825 code = models.CharField(max_length=25, unique=True)
826 nom = models.CharField(max_length=255)
e9bbd6ba 827
6e4600ef 828 class Meta:
829 ordering = ['code']
8c1ae2b3 830 verbose_name = "Statut d'employé"
831 verbose_name_plural = "Statuts d'employé"
6e4600ef 832
9afaa55e 833 def __unicode__(self):
834 return u'%s : %s' % (self.code, self.nom)
835
83b7692b 836
e9bbd6ba 837TYPE_CLASSEMENT_CHOICES = (
6e4600ef 838 ('S', 'S -Soutien'),
839 ('T', 'T - Technicien'),
840 ('P', 'P - Professionel'),
841 ('C', 'C - Cadre'),
842 ('D', 'D - Direction'),
843 ('SO', 'SO - Sans objet [expatriés]'),
844 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 845)
83b7692b 846
6e7c919b
NC
847
848class Classement_(Metadata):
6e4600ef 849 """Éléments de classement de la
850 "Grille générique de classement hiérarchique".
851
852 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
853 classement dans la grille. Le classement donne le coefficient utilisé dans:
854
855 salaire de base = coefficient * valeur du point de l'Implantation du Poste
856 """
9afaa55e 857 # Identification
e9bbd6ba 858 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
8c1ae2b3 859 echelon = models.IntegerField(verbose_name="Échelon")
860 degre = models.IntegerField(verbose_name="Degré")
861 coefficient = models.FloatField(default=0, verbose_name="Coéfficient")
9afaa55e 862 # Méta
6e4600ef 863 # annee # au lieu de date_debut et date_fin
864 commentaire = models.TextField(null=True, blank=True)
865
866 class Meta:
6e7c919b 867 abstract = True
6e4600ef 868 ordering = ['type','echelon','degre','coefficient']
8c1ae2b3 869 verbose_name = "Classement"
870 verbose_name_plural = "Classements"
e9bbd6ba 871
872 def __unicode__(self):
873 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
874 self.coefficient)
875
6e7c919b
NC
876class Classement(Classement_):
877 __doc__ = Classement_.__doc__
878
879
880class TauxChange_(Metadata):
7abc6d45 881 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 882 pour chaque année budgétaire.
7abc6d45 883 """
9afaa55e 884 # Identification
8c1ae2b3 885 devise = models.ForeignKey('Devise', db_column='devise',
6e4600ef 886 related_name='+')
8c1ae2b3 887 annee = models.IntegerField(verbose_name="Année")
888 taux = models.FloatField(verbose_name="Taux vers l'euro")
6e7c919b 889
6e4600ef 890 class Meta:
6e7c919b 891 abstract = True
8c1ae2b3 892 ordering = ['-annee', 'devise__code']
893 verbose_name = "Taux de change"
894 verbose_name_plural = "Taux de change"
6e4600ef 895
896 def __unicode__(self):
8c1ae2b3 897 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 898
e9bbd6ba 899
6e7c919b
NC
900class TauxChange(TauxChange_):
901 __doc__ = TauxChange_.__doc__
902
903
904class ValeurPoint_(Metadata):
6e4600ef 905 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
906 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 907 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 908
909 salaire de base = coefficient * valeur du point de l'Implantation du Poste
910 """
9afaa55e 911 valeur = models.FloatField()
6e4600ef 912 devise = models.ForeignKey('Devise', db_column='devise',
913 related_name='+', default=5)
83b7692b 914 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
915 db_column='implantation',
916 related_name='%(app_label)s_valeur_point')
9afaa55e 917 # Méta
e9bbd6ba 918 annee = models.IntegerField()
9afaa55e 919
6e4600ef 920 class Meta:
921 ordering = ['annee', 'implantation__nom']
6e7c919b 922 abstract = True
8c1ae2b3 923 ordering = ['annee']
924 verbose_name = "Valeur du point"
925 verbose_name_plural = "Valeurs du point"
ee23ecbc 926
e9d7483c 927 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
ee23ecbc
NC
928 def get_tauxchange_courant(self):
929 """
930 Recherche le taux courant associé à la valeur d'un point.
931 Tous les taux de l'année courante sont chargés, pour optimiser un
932 affichage en liste. (On pourrait probablement améliorer le manager pour
933 lui greffer le taux courant sous forme de JOIN)
934 """
935 for tauxchange in self.tauxchange:
936 if tauxchange.implantation_id == self.implantation_id:
937 return tauxchange
938 return None
939
9afaa55e 940 def __unicode__(self):
8c1ae2b3 941 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
6e7c919b
NC
942
943
944class ValeurPoint(ValeurPoint_):
945 __doc__ = ValeurPoint_.__doc__
946
e9bbd6ba 947
6e4600ef 948class Devise(Metadata):
949 """Devise monétaire.
950 """
e9bbd6ba 951 code = models.CharField(max_length=10, unique=True)
952 nom = models.CharField(max_length=255)
953
6e4600ef 954 class Meta:
955 ordering = ['code']
8c1ae2b3 956 verbose_name = "Devise"
957 verbose_name_plural = "Devises"
6e4600ef 958
e9bbd6ba 959 def __unicode__(self):
960 return u'%s - %s' % (self.code, self.nom)
961
6e4600ef 962class TypeContrat(Metadata):
963 """Type de contrat.
964 """
e9bbd6ba 965 nom = models.CharField(max_length=255)
6e4600ef 966 nom_long = models.CharField(max_length=255)
49f9f116 967
8c1ae2b3 968 class Meta:
969 ordering = ['nom']
970 verbose_name = "Type de contrat"
971 verbose_name_plural = "Types de contrat"
972
9afaa55e 973 def __unicode__(self):
974 return u'%s' % (self.nom)
30be56d5 975
2d4d2fcf 976
977### AUTRES
978
6e4600ef 979class ResponsableImplantation(Metadata):
30be56d5 980 """Le responsable d'une implantation.
981 Anciennement géré sur le Dossier du responsable.
982 """
6e4600ef 983 employe = models.ForeignKey('Employe', db_column='employe',
984 related_name='+',
985 null=True, blank=True)
986 implantation = models.ForeignKey(ref.Implantation,
987 db_column='implantation', related_name='+',
988 unique=True)
30be56d5 989
990 def __unicode__(self):
991 return u'%s : %s' % (self.implantation, self.employe)
992
993 class Meta:
994 ordering = ['implantation__nom']
8c1ae2b3 995 verbose_name = "Responsable d'implantation"
996 verbose_name_plural = "Responsables d'implantation"