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