#1886
[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
b4aeadf3 9from validators import validate_date_passee
83b7692b 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",
b4aeadf3 293 validators=[validate_date_passee],
6e4600ef 294 null=True, blank=True)
2d4d2fcf 295 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 296
9afaa55e 297 # Infos personnelles
6e4600ef 298 situation_famille = models.CharField(max_length=1,
299 choices=SITUATION_CHOICES,
8c1ae2b3 300 verbose_name="Situation familiale",
6e4600ef 301 null=True, blank=True)
8c1ae2b3 302 date_entree = models.DateField(verbose_name="Date d'entrée à l'AUF",
6e4600ef 303 help_text=HELP_TEXT_DATE,
7abc6d45 304 null=True, blank=True)
83b7692b 305
9afaa55e 306 # Coordonnées
8c1ae2b3 307 tel_domicile = models.CharField(max_length=255,
308 verbose_name="Tél. domicile",
309 null=True, blank=True)
310 tel_cellulaire = models.CharField(max_length=255,
311 verbose_name="Tél. cellulaire",
312 null=True, blank=True)
e9bbd6ba 313 adresse = models.CharField(max_length=255, null=True, blank=True)
e9bbd6ba 314 ville = models.CharField(max_length=255, null=True, blank=True)
315 province = models.CharField(max_length=255, null=True, blank=True)
316 code_postal = models.CharField(max_length=255, null=True, blank=True)
6e4600ef 317 pays = models.ForeignKey(ref.Pays, to_field='code', db_column='pays',
318 related_name='employes',
319 null=True, blank=True)
9afaa55e 320
6e4600ef 321 class Meta:
67666927 322 ordering = ['nom_affichage','nom','prenom']
8c1ae2b3 323 verbose_name = "Employé"
324 verbose_name_plural = "Employés"
6e4600ef 325
9afaa55e 326 def __unicode__(self):
8c1ae2b3 327 return u'%s' % (self.get_nom())
328
329 def get_nom(self):
ebc8eb32 330 nom_affichage = self.nom_affichage
331 if not nom_affichage:
332 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
8c1ae2b3 333 return nom_affichage
d04d084c 334
335 def civilite(self):
336 civilite = u''
337 if self.genre.upper() == u'M':
338 civilite = u'M.'
339 elif self.genre.upper() == u'F':
340 civilite = u'Mme'
341 return civilite
9afaa55e 342
7abc6d45 343class EmployePiece(models.Model):
6e4600ef 344 """Documents relatifs à un employé.
7abc6d45 345 Ex.: CV...
346 """
8c1ae2b3 347 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 348 related_name='+')
8c1ae2b3 349 nom = models.CharField(verbose_name="Nom", max_length=255)
350 fichier = models.FileField(verbose_name="Fichier",
7abc6d45 351 upload_to=dossier_piece_dispatch,
352 storage=storage_prive)
353
6e4600ef 354 class Meta:
355 ordering = ['nom']
356
357 def __unicode__(self):
358 return u'%s' % (self.nom)
359
07b40eda 360class EmployeCommentaire(Commentaire):
8c1ae2b3 361 employe = models.ForeignKey('Employe', db_column='employe',
6e4600ef 362 related_name='+')
9afaa55e 363
2d4d2fcf 364
e9bbd6ba 365LIEN_PARENTE_CHOICES = (
366 ('Conjoint', 'Conjoint'),
367 ('Conjointe', 'Conjointe'),
368 ('Fille', 'Fille'),
369 ('Fils', 'Fils'),
370)
371
d6985a3a 372class AyantDroit(AUFMetadata):
6e4600ef 373 """Personne en relation avec un Employe.
374 """
9afaa55e 375 # Identification
e9bbd6ba 376 nom = models.CharField(max_length=255)
8c1ae2b3 377 prenom = models.CharField(max_length=255,
378 verbose_name="Prénom",)
6e4600ef 379 nom_affichage = models.CharField(max_length=255,
8c1ae2b3 380 verbose_name="Nom d'affichage",
6e4600ef 381 null=True, blank=True)
2d4d2fcf 382 nationalite = models.ForeignKey(ref.Pays, to_field='code',
6e4600ef 383 db_column='nationalite',
8c1ae2b3 384 related_name='ayantdroits_nationalite',
385 verbose_name="Nationalité")
6e4600ef 386 date_naissance = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 387 verbose_name="Date de naissance",
6e4600ef 388 null=True, blank=True)
2d4d2fcf 389 genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
83b7692b 390
9afaa55e 391 # Relation
392 employe = models.ForeignKey('Employe', db_column='employe',
8c1ae2b3 393 related_name='ayantdroits',
394 verbose_name="Employé")
6e4600ef 395 lien_parente = models.CharField(max_length=10,
396 choices=LIEN_PARENTE_CHOICES,
8c1ae2b3 397 verbose_name="Lien de parenté",
6e4600ef 398 null=True, blank=True)
399
400 class Meta:
401 ordering = ['nom_affichage']
8c1ae2b3 402 verbose_name = "Ayant droit"
403 verbose_name_plural = "Ayants droit"
404
6e4600ef 405 def __unicode__(self):
8c1ae2b3 406 return u'%s' % (self.get_nom())
407
408 def get_nom(self):
409 nom_affichage = self.nom_affichage
410 if not nom_affichage:
411 nom_affichage = u'%s %s' % (self.nom.upper(), self.prenom)
412 return nom_affichage
83b7692b 413
07b40eda 414class AyantDroitCommentaire(Commentaire):
8c1ae2b3 415 ayant_droit = models.ForeignKey('AyantDroit', db_column='ayant_droit',
6e4600ef 416 related_name='+')
83b7692b 417
2d4d2fcf 418
83b7692b 419### DOSSIER
420
421STATUT_RESIDENCE_CHOICES = (
422 ('local', 'Local'),
423 ('expat', 'Expatrié'),
424)
425
426COMPTE_COMPTA_CHOICES = (
427 ('coda', 'CODA'),
428 ('scs', 'SCS'),
429 ('aucun', 'Aucun'),
430)
431
d6985a3a 432class Dossier_(AUFMetadata):
6e4600ef 433 """Le Dossier regroupe les informations relatives à l'occupation
434 d'un Poste par un Employe. Un seul Dossier existe par Poste occupé
435 par un Employe.
436
437 Plusieurs Contrats peuvent être associés au Dossier.
438 Une structure de Remuneration est rattachée au Dossier. Un Poste pour
439 lequel aucun Dossier n'existe est un poste vacant.
440 """
83b7692b 441 # Identification
6e4600ef 442 employe = models.ForeignKey('Employe', db_column='employe',
d04d084c 443 related_name='dossiers',
8c1ae2b3 444 verbose_name="Employé")
1f2979b8 445 poste = models.ForeignKey('Poste', db_column='poste', related_name='+')
8277a35b
NC
446 statut = models.ForeignKey('Statut', related_name='+', default=3,
447 null=True)
83b7692b 448 organisme_bstg = models.ForeignKey('OrganismeBstg',
6e4600ef 449 db_column='organisme_bstg',
450 related_name='+',
8c1ae2b3 451 verbose_name="Organisme",
452 help_text="Si détaché (DET) ou \
6e4600ef 453 mis à disposition (MAD), \
454 préciser l'organisme.",
455 null=True, blank=True)
456
83b7692b 457 # Recrutement
2d4d2fcf 458 remplacement = models.BooleanField(default=False)
83b7692b 459 statut_residence = models.CharField(max_length=10, default='local',
8277a35b 460 verbose_name="Statut", null=True,
2d4d2fcf 461 choices=STATUT_RESIDENCE_CHOICES)
83b7692b 462
463 # Rémunération
6e4600ef 464 classement = models.ForeignKey('Classement', db_column='classement',
465 related_name='+',
466 null=True, blank=True)
8277a35b 467 regime_travail = models.DecimalField(max_digits=12, null=True,
2d4d2fcf 468 decimal_places=2,
469 default=REGIME_TRAVAIL_DEFAULT,
8c1ae2b3 470 verbose_name="Régime de travail",
471 help_text="% du temps complet")
83b7692b 472 regime_travail_nb_heure_semaine = models.DecimalField(max_digits=12,
8277a35b 473 decimal_places=2, null=True,
2d4d2fcf 474 default=REGIME_TRAVAIL_NB_HEURE_SEMAINE_DEFAULT,
8c1ae2b3 475 verbose_name="Nb. heures par semaine")
7abc6d45 476
477 # Occupation du Poste par cet Employe (anciennement "mandat")
8c1ae2b3 478 date_debut = models.DateField(verbose_name="Date de début d'occupation \
6e4600ef 479 de poste",
480 help_text=HELP_TEXT_DATE)
8c1ae2b3 481 date_fin = models.DateField(verbose_name="Date de fin d'occupation \
6e4600ef 482 de poste",
2d4d2fcf 483 help_text=HELP_TEXT_DATE,
6e4600ef 484 null=True, blank=True)
e9bbd6ba 485
2d4d2fcf 486 # Comptes
487 # TODO?
83b7692b 488
6e4600ef 489 class Meta:
37868f0b 490 abstract = True
67666927 491 ordering = ['employe__nom_affichage', 'employe__nom', 'poste__nom']
8c1ae2b3 492 verbose_name = "Dossier"
493 verbose_name_plural = "Dossiers"
6e4600ef 494
83b7692b 495 def __unicode__(self):
8c1ae2b3 496 poste = self.poste.nom
497 if self.employe.genre == 'F':
498 poste = self.poste.nom_feminin
499 return u'%s - %s' % (self.employe, poste)
83b7692b 500
37868f0b
NC
501
502class Dossier(Dossier_):
503 __doc__ = Dossier_.__doc__
504
505
83b7692b 506class DossierPiece(models.Model):
7abc6d45 507 """Documents relatifs au Dossier (à l'occupation de ce poste par employé).
508 Ex.: Lettre de motivation.
509 """
8c1ae2b3 510 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 511 related_name='+')
8c1ae2b3 512 nom = models.CharField(verbose_name="Nom", max_length=255)
513 fichier = models.FileField(verbose_name="Fichier",
83b7692b 514 upload_to=dossier_piece_dispatch,
515 storage=storage_prive)
516
6e4600ef 517 class Meta:
518 ordering = ['nom']
519
520 def __unicode__(self):
521 return u'%s' % (self.nom)
522
07b40eda 523class DossierCommentaire(Commentaire):
8c1ae2b3 524 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 525 related_name='+')
83b7692b 526
2d4d2fcf 527
07b40eda 528### RÉMUNÉRATION
e9bbd6ba 529
d6985a3a 530class RemunerationMixin(AUFMetadata):
9afaa55e 531 # Identification
6e7c919b
NC
532 dossier = models.ForeignKey('Dossier', db_column='dossier',
533 related_name='%(app_label)s_%(class)s_remunerations')
83b7692b 534 type = models.ForeignKey('TypeRemuneration', db_column='type',
8c1ae2b3 535 related_name='+',
536 verbose_name="Type de rémunération")
7abc6d45 537 type_revalorisation = models.ForeignKey('TypeRevalorisation',
538 db_column='type_revalorisation',
6e4600ef 539 related_name='+',
8c1ae2b3 540 verbose_name="Type de revalorisation",
7abc6d45 541 null=True, blank=True)
50fa9bc1 542 montant = models.FloatField(null=True, blank=True,
6e4600ef 543 default=0)
2d4d2fcf 544 # Annuel (12 mois, 52 semaines, 364 jours?)
c589d980 545 devise = models.ForeignKey('Devise', to_field='id',
6e4600ef 546 db_column='devise', related_name='+',
547 default=5)
2d4d2fcf 548 # commentaire = precision
549 commentaire = models.CharField(max_length=255, null=True, blank=True)
550 # date_debut = anciennement date_effectif
8c1ae2b3 551 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
552 verbose_name="Date de début",
6e4600ef 553 null=True, blank=True)
554 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 555 verbose_name="Date de fin",
6e4600ef 556 null=True, blank=True)
83b7692b 557
2d4d2fcf 558 class Meta:
559 abstract = True
6e4600ef 560 ordering = ['type__nom', '-date_fin']
561
562 def __unicode__(self):
563 return u'%s %s (%s)' % (self.montant, self.devise.code, self.type.nom)
2d4d2fcf 564
6e7c919b 565class Remuneration_(RemunerationMixin):
2d4d2fcf 566 """Structure de rémunération (données budgétaires) en situation normale
567 pour un Dossier. Si un Evenement existe, utiliser la structure de
568 rémunération EvenementRemuneration de cet événement.
569 """
83b7692b 570
571 def montant_mois(self):
572 return round(self.montant / 12, 2)
573
574 def taux_devise(self):
575 return self.devise.tauxchange_set.order_by('-annee').all()[0].taux
576
577 def montant_euro(self):
578 return round(float(self.montant) / float(self.taux_devise()), 2)
579
580 def montant_euro_mois(self):
581 return round(self.montant_euro() / 12, 2)
9afaa55e 582
583 def __unicode__(self):
584 try:
585 devise = self.devise.code
586 except:
587 devise = "???"
588 return "%s %s" % (self.montant, devise)
83b7692b 589
6e7c919b
NC
590 class Meta:
591 abstract = True
8c1ae2b3 592 verbose_name = "Rémunération"
593 verbose_name_plural = "Rémunérations"
6e7c919b
NC
594
595
596class Remuneration(Remuneration_):
597 __doc__ = Remuneration_.__doc__
598
2d4d2fcf 599
600### CONTRATS
c41b7fcc
OL
601
602class ContratManager(NoDeleteManager):
603 def get_query_set(self):
604 return super(ContratManager, self).get_query_set().select_related('dossier', 'dossier__poste')
605
2d4d2fcf 606
d6985a3a 607class Contrat(AUFMetadata):
2d4d2fcf 608 """Document juridique qui encadre la relation de travail d'un Employe
609 pour un Poste particulier. Pour un Dossier (qui documente cette
610 relation de travail) plusieurs contrats peuvent être associés.
611 """
c41b7fcc
OL
612
613 objects = ContratManager()
614
6e4600ef 615 dossier = models.ForeignKey('Dossier', db_column='dossier',
616 related_name='+')
617 type_contrat = models.ForeignKey('TypeContrat', db_column='type_contrat',
8c1ae2b3 618 related_name='+',
619 verbose_name="Type de contrat")
620 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
621 verbose_name="Date de début")
6e4600ef 622 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
8c1ae2b3 623 verbose_name="Date de fin",
6e4600ef 624 null=True, blank=True)
625
626 class Meta:
627 ordering = ['dossier__employe__nom_affichage']
8c1ae2b3 628 verbose_name = "Contrat"
629 verbose_name_plural = "Contrats"
6e4600ef 630
631 def __unicode__(self):
8c1ae2b3 632 return u'%s - %s' % (self.dossier, self.id)
6e4600ef 633
634# TODO? class ContratPiece(models.Model):
2d4d2fcf 635
636
637### ÉVÉNEMENTS
638
d6985a3a 639class Evenement_(AUFMetadata):
6e4600ef 640 """Un Evenement sert à déclarer une situation temporaire (exceptionnelle)
641 d'un Dossier qui vient altérer des informations normales liées à un Dossier
642 (ex.: la Remuneration).
643
644 Ex.: congé de maternité, maladie...
645
646 Lors de ces situations exceptionnelles, l'Employe a un régime de travail
647 différent et une rémunération en conséquence. On souhaite toutefois
648 conserver le Dossier intact afin d'éviter une re-saisie des données lors
649 du retour à la normale.
650 """
8c1ae2b3 651 dossier = models.ForeignKey('Dossier', db_column='dossier',
6e4600ef 652 related_name='+')
653 nom = models.CharField(max_length=255)
8c1ae2b3 654 date_debut = models.DateField(help_text=HELP_TEXT_DATE,
655 verbose_name="Date de début")
656 date_fin = models.DateField(help_text=HELP_TEXT_DATE,
657 verbose_name="Date de fin",
6e4600ef 658 null=True, blank=True)
6e7c919b 659
6e4600ef 660 class Meta:
6e7c919b 661 abstract = True
6e4600ef 662 ordering = ['nom']
8c1ae2b3 663 verbose_name = "Évènement"
664 verbose_name_plural = "Évènements"
6e4600ef 665
666 def __unicode__(self):
667 return u'%s' % (self.nom)
6e7c919b
NC
668
669
670class Evenement(Evenement_):
671 __doc__ = Evenement_.__doc__
672
2d4d2fcf 673
6e7c919b 674class EvenementRemuneration_(RemunerationMixin):
6e4600ef 675 """Structure de rémunération liée à un Evenement qui remplace
676 temporairement la Remuneration normale d'un Dossier, pour toute la durée
677 de l'Evenement.
678 """
679 evenement = models.ForeignKey("Evenement", db_column='evenement',
8c1ae2b3 680 related_name='+',
681 verbose_name="Évènement")
682 # TODO : le champ dossier hérité de Remuneration doit être dérivé
683 # de l'Evenement associé
83b7692b 684
6e7c919b
NC
685 class Meta:
686 abstract = True
8c1ae2b3 687 ordering = ['evenement', 'type__nom', '-date_fin']
688 verbose_name = "Évènement - rémunération"
689 verbose_name_plural = "Évènements - rémunérations"
6e7c919b
NC
690
691
692class EvenementRemuneration(EvenementRemuneration_):
693 __doc__ = EvenementRemuneration_.__doc__
83b7692b 694
f31ddfa0
NC
695 class Meta:
696 abstract = True
697
698
699class EvenementRemuneration(EvenementRemuneration_):
700 __doc__ = EvenementRemuneration_.__doc__
701
83b7692b 702
703### RÉFÉRENCES RH
704
d6985a3a 705class FamilleEmploi(AUFMetadata):
6e4600ef 706 """Catégorie utilisée dans la gestion des Postes.
707 Catégorie supérieure à TypePoste.
708 """
e9bbd6ba 709 nom = models.CharField(max_length=255)
6e4600ef 710
8c1ae2b3 711 class Meta:
712 ordering = ['nom']
713 verbose_name = "Famille d'emploi"
714 verbose_name_plural = "Familles d'emploi"
715
6e4600ef 716 def __unicode__(self):
717 return u'%s' % (self.nom)
e9bbd6ba 718
d6985a3a 719class TypePoste(AUFMetadata):
6e4600ef 720 """Catégorie de Poste.
721 """
e9bbd6ba 722 nom = models.CharField(max_length=255)
8c1ae2b3 723 nom_feminin = models.CharField(max_length=255,
724 verbose_name="Nom féminin")
6e4600ef 725
8c1ae2b3 726 is_responsable = models.BooleanField(default=False,
727 verbose_name="Poste de responsabilité")
9afaa55e 728 famille_emploi = models.ForeignKey('FamilleEmploi',
6e4600ef 729 db_column='famille_emploi',
8c1ae2b3 730 related_name='+',
731 verbose_name="Famille d'emploi")
e9bbd6ba 732
6e4600ef 733 class Meta:
734 ordering = ['nom']
8c1ae2b3 735 verbose_name = "Type de poste"
736 verbose_name_plural = "Types de poste"
6e4600ef 737
e9bbd6ba 738 def __unicode__(self):
6e4600ef 739 return u'%s' % (self.nom)
e9bbd6ba 740
741
742TYPE_PAIEMENT_CHOICES = (
743 ('Régulier', 'Régulier'),
744 ('Ponctuel', 'Ponctuel'),
745)
746
747NATURE_REMUNERATION_CHOICES = (
748 ('Accessoire', 'Accessoire'),
749 ('Charges', 'Charges'),
750 ('Indemnité', 'Indemnité'),
7abc6d45 751 ('RAS', 'Rémunération autre source'),
e9bbd6ba 752 ('Traitement', 'Traitement'),
753)
754
d6985a3a 755class TypeRemuneration(AUFMetadata):
6e4600ef 756 """Catégorie de Remuneration.
757 """
e9bbd6ba 758 nom = models.CharField(max_length=255)
9afaa55e 759 type_paiement = models.CharField(max_length=30,
8c1ae2b3 760 choices=TYPE_PAIEMENT_CHOICES,
761 verbose_name="Type de paiement")
9afaa55e 762 nature_remuneration = models.CharField(max_length=30,
8c1ae2b3 763 choices=NATURE_REMUNERATION_CHOICES,
764 verbose_name="Nature de la rémunération")
765
766 class Meta:
767 ordering = ['nom']
768 verbose_name = "Type de rémunération"
769 verbose_name_plural = "Types de rémunération"
9afaa55e 770
771 def __unicode__(self):
6e4600ef 772 return u'%s' % (self.nom)
e9bbd6ba 773
d6985a3a 774class TypeRevalorisation(AUFMetadata):
7abc6d45 775 """Justification du changement de la Remuneration.
6e4600ef 776 (Actuellement utilisé dans aucun traitement informatique.)
7abc6d45 777 """
e9bbd6ba 778 nom = models.CharField(max_length=255)
8c1ae2b3 779
780 class Meta:
781 ordering = ['nom']
782 verbose_name = "Type de revalorisation"
783 verbose_name_plural = "Types de revalorisation"
e9bbd6ba 784
785 def __unicode__(self):
6e4600ef 786 return u'%s' % (self.nom)
787
d6985a3a 788class Service(AUFMetadata):
6e4600ef 789 """Unité administrative où les Postes sont rattachés.
790 """
791 nom = models.CharField(max_length=255)
9afaa55e 792
793 class Meta:
794 ordering = ['nom']
8c1ae2b3 795 verbose_name = "Service"
796 verbose_name_plural = "Services"
e9bbd6ba 797
6e4600ef 798 def __unicode__(self):
799 return u'%s' % (self.nom)
800
e9bbd6ba 801
802TYPE_ORGANISME_CHOICES = (
803 ('MAD', 'Mise à disposition'),
804 ('DET', 'Détachement'),
805)
806
d6985a3a 807class OrganismeBstg(AUFMetadata):
6e4600ef 808 """Organisation d'où provient un Employe mis à disposition (MAD) de
809 ou détaché (DET) à l'AUF à titre gratuit.
810
811 (BSTG = bien et service à titre gratuit.)
812 """
e9bbd6ba 813 nom = models.CharField(max_length=255)
814 type = models.CharField(max_length=10, choices=TYPE_ORGANISME_CHOICES)
6e4600ef 815 pays = models.ForeignKey(ref.Pays, to_field='code',
816 db_column='pays',
817 related_name='organismes_bstg',
818 null=True, blank=True)
9afaa55e 819
820 class Meta:
821 ordering = ['type', 'nom']
8c1ae2b3 822 verbose_name = "Organisme BSTG"
823 verbose_name_plural = "Organismes BSTG"
9afaa55e 824
6e4600ef 825 def __unicode__(self):
8c1ae2b3 826 return u'%s (%s)' % (self.nom, self.get_type_display())
83b7692b 827
d6985a3a 828class Statut(AUFMetadata):
6e4600ef 829 """Statut de l'Employe dans le cadre d'un Dossier particulier.
830 """
9afaa55e 831 # Identification
e9bbd6ba 832 code = models.CharField(max_length=25, unique=True)
833 nom = models.CharField(max_length=255)
e9bbd6ba 834
6e4600ef 835 class Meta:
836 ordering = ['code']
8c1ae2b3 837 verbose_name = "Statut d'employé"
838 verbose_name_plural = "Statuts d'employé"
6e4600ef 839
9afaa55e 840 def __unicode__(self):
841 return u'%s : %s' % (self.code, self.nom)
842
83b7692b 843
e9bbd6ba 844TYPE_CLASSEMENT_CHOICES = (
6e4600ef 845 ('S', 'S -Soutien'),
846 ('T', 'T - Technicien'),
847 ('P', 'P - Professionel'),
848 ('C', 'C - Cadre'),
849 ('D', 'D - Direction'),
850 ('SO', 'SO - Sans objet [expatriés]'),
851 ('HG', 'HG - Hors grille [direction]'),
e9bbd6ba 852)
83b7692b 853
6e7c919b 854
d6985a3a 855class Classement_(AUFMetadata):
6e4600ef 856 """Éléments de classement de la
857 "Grille générique de classement hiérarchique".
858
859 Utile pour connaître, pour un Dossier, le salaire de base théorique lié au
860 classement dans la grille. Le classement donne le coefficient utilisé dans:
861
862 salaire de base = coefficient * valeur du point de l'Implantation du Poste
863 """
9afaa55e 864 # Identification
e9bbd6ba 865 type = models.CharField(max_length=10, choices=TYPE_CLASSEMENT_CHOICES)
8c1ae2b3 866 echelon = models.IntegerField(verbose_name="Échelon")
867 degre = models.IntegerField(verbose_name="Degré")
8277a35b
NC
868 coefficient = models.FloatField(default=0, verbose_name="Coéfficient",
869 null=True)
9afaa55e 870 # Méta
6e4600ef 871 # annee # au lieu de date_debut et date_fin
872 commentaire = models.TextField(null=True, blank=True)
873
874 class Meta:
6e7c919b 875 abstract = True
6e4600ef 876 ordering = ['type','echelon','degre','coefficient']
8c1ae2b3 877 verbose_name = "Classement"
878 verbose_name_plural = "Classements"
e9bbd6ba 879
880 def __unicode__(self):
881 return u'%s.%s.%s (%s)' % (self.type, self.echelon, self.degre,
882 self.coefficient)
883
6e7c919b
NC
884class Classement(Classement_):
885 __doc__ = Classement_.__doc__
886
887
d6985a3a 888class TauxChange_(AUFMetadata):
7abc6d45 889 """Taux de change de la devise vers l'euro (EUR)
6e4600ef 890 pour chaque année budgétaire.
7abc6d45 891 """
9afaa55e 892 # Identification
8c1ae2b3 893 devise = models.ForeignKey('Devise', db_column='devise',
6e4600ef 894 related_name='+')
8c1ae2b3 895 annee = models.IntegerField(verbose_name="Année")
896 taux = models.FloatField(verbose_name="Taux vers l'euro")
6e7c919b 897
6e4600ef 898 class Meta:
6e7c919b 899 abstract = True
8c1ae2b3 900 ordering = ['-annee', 'devise__code']
901 verbose_name = "Taux de change"
902 verbose_name_plural = "Taux de change"
6e4600ef 903
904 def __unicode__(self):
8c1ae2b3 905 return u'%s : %s € (%s)' % (self.devise, self.taux, self.annee)
e9bbd6ba 906
6e7c919b
NC
907
908class TauxChange(TauxChange_):
909 __doc__ = TauxChange_.__doc__
910
701f3bea
OL
911class ValeurPointManager(NoDeleteManager):
912 def get_query_set(self):
913 return super(ValeurPointManager, self).get_query_set().select_related('devise', 'implantation')
914
6e7c919b 915
d6985a3a 916class ValeurPoint_(AUFMetadata):
6e4600ef 917 """Utile pour connaître, pour un Dossier, le salaire de base théorique lié
918 au classement dans la grille. La ValeurPoint s'obtient par l'implantation
8c1ae2b3 919 du Poste de ce Dossier : dossier.poste.implantation (pseudo code).
6e4600ef 920
921 salaire de base = coefficient * valeur du point de l'Implantation du Poste
922 """
701f3bea
OL
923
924 objects = ValeurPointManager()
925
8277a35b
NC
926 valeur = models.FloatField(null=True)
927 devise = models.ForeignKey('Devise', db_column='devise', null=True,
6e4600ef 928 related_name='+', default=5)
83b7692b 929 implantation = models.ForeignKey(ref.Implantation,
6e7c919b
NC
930 db_column='implantation',
931 related_name='%(app_label)s_valeur_point')
9afaa55e 932 # Méta
e9bbd6ba 933 annee = models.IntegerField()
9afaa55e 934
6e4600ef 935 class Meta:
701f3bea 936 ordering = ['-annee', 'implantation__nom']
6e7c919b 937 abstract = True
8c1ae2b3 938 verbose_name = "Valeur du point"
939 verbose_name_plural = "Valeurs du point"
6e0bbb73 940
e9d7483c 941 # TODO : cette fonction n'était pas présente dans la branche dev, utilité?
ee23ecbc
NC
942 def get_tauxchange_courant(self):
943 """
944 Recherche le taux courant associé à la valeur d'un point.
945 Tous les taux de l'année courante sont chargés, pour optimiser un
946 affichage en liste. (On pourrait probablement améliorer le manager pour
947 lui greffer le taux courant sous forme de JOIN)
948 """
949 for tauxchange in self.tauxchange:
950 if tauxchange.implantation_id == self.implantation_id:
951 return tauxchange
952 return None
953
9afaa55e 954 def __unicode__(self):
8c1ae2b3 955 return u'%s %s (%s)' % (self.valeur, self.devise, self.annee)
6e7c919b
NC
956
957
958class ValeurPoint(ValeurPoint_):
959 __doc__ = ValeurPoint_.__doc__
960
e9bbd6ba 961
d6985a3a 962class Devise(AUFMetadata):
6e4600ef 963 """Devise monétaire.
964 """
e9bbd6ba 965 code = models.CharField(max_length=10, unique=True)
966 nom = models.CharField(max_length=255)
967
6e4600ef 968 class Meta:
969 ordering = ['code']
8c1ae2b3 970 verbose_name = "Devise"
971 verbose_name_plural = "Devises"
6e4600ef 972
e9bbd6ba 973 def __unicode__(self):
974 return u'%s - %s' % (self.code, self.nom)
975
d6985a3a 976class TypeContrat(AUFMetadata):
6e4600ef 977 """Type de contrat.
978 """
e9bbd6ba 979 nom = models.CharField(max_length=255)
6e4600ef 980 nom_long = models.CharField(max_length=255)
49f9f116 981
8c1ae2b3 982 class Meta:
983 ordering = ['nom']
984 verbose_name = "Type de contrat"
985 verbose_name_plural = "Types de contrat"
986
9afaa55e 987 def __unicode__(self):
988 return u'%s' % (self.nom)
30be56d5 989
2d4d2fcf 990
991### AUTRES
992
d6985a3a 993class ResponsableImplantation(AUFMetadata):
30be56d5 994 """Le responsable d'une implantation.
995 Anciennement géré sur le Dossier du responsable.
996 """
6e4600ef 997 employe = models.ForeignKey('Employe', db_column='employe',
998 related_name='+',
999 null=True, blank=True)
1000 implantation = models.ForeignKey(ref.Implantation,
1001 db_column='implantation', related_name='+',
1002 unique=True)
30be56d5 1003
1004 def __unicode__(self):
1005 return u'%s : %s' % (self.implantation, self.employe)
1006
1007 class Meta:
1008 ordering = ['implantation__nom']
8c1ae2b3 1009 verbose_name = "Responsable d'implantation"
1010 verbose_name_plural = "Responsables d'implantation"